2016-02-13 17:06:36 -06:00
# -*- coding: utf-8 -*-
2018-12-24 08:53:28 -06:00
""" Session object for Socializer. A session is the only object to call VK API methods, save settings and access to the cache database and sound playback mechanisms. """
2019-01-01 19:42:53 -06:00
from __future__ import unicode_literals
2018-12-14 15:31:15 -06:00
import os
2018-12-06 15:37:16 -06:00
import logging
2019-01-10 17:29:17 -06:00
import warnings
2016-02-13 17:06:36 -06:00
import languageHandler
import paths
2019-01-23 08:22:22 -06:00
import config
2016-05-24 17:48:22 -05:00
import sound
2019-02-14 08:36:31 -06:00
from requests . exceptions import ProxyError , ConnectionError
2019-01-01 19:42:53 -06:00
from . config_utils import Configuration , ConfigurationResetException
2019-01-23 08:22:22 -06:00
from . import vkSessionHandler
2016-06-06 03:53:55 -05:00
from pubsub import pub
2018-12-09 05:21:52 -06:00
from vk_api . exceptions import LoginRequired , VkApiError
2016-06-06 03:53:55 -05:00
2016-05-10 20:23:48 -05:00
log = logging . getLogger ( " session " )
2016-02-13 17:06:36 -06:00
sessions = { }
2018-12-24 08:53:28 -06:00
# Save possible set of identifier keys for VK'S data types
2016-02-17 08:11:47 -06:00
# see https://vk.com/dev/datatypes for more information.
2018-12-22 05:40:39 -06:00
# I've added the Date identifier (this is a field in unix time format), for special objects (like friendship indicators) because these objects don't have an own identifier.
2016-05-10 20:23:48 -05:00
identifiers = [ " aid " , " gid " , " uid " , " pid " , " id " , " post_id " , " nid " , " date " ]
2016-02-17 08:11:47 -06:00
2018-12-22 05:40:39 -06:00
# Different VK post types, present in the newsfeed buffer. This is useful for filtering by post and remove deleted posts.
2018-12-31 11:50:11 -06:00
post_types = dict ( audio = " audio " , friend = " friends " , video = " video " , post = " post_type " , audio_playlist = " audio_playlist " )
2018-12-22 05:40:39 -06:00
2016-02-17 08:11:47 -06:00
def find_item ( list , item ) :
2018-12-24 08:53:28 -06:00
""" Find an item in a list by taking an identifier.
@list list : A list of dict objects .
@ item dict : A dictionary containing at least an identifier .
"""
# determine the kind of identifier that we are using
2016-02-17 08:11:47 -06:00
global identifiers
2016-05-10 20:23:48 -05:00
identifier = None
for i in identifiers :
2019-01-01 19:42:53 -06:00
if i in item :
2016-05-10 20:23:48 -05:00
identifier = i
break
2016-02-17 08:11:47 -06:00
if identifier == None :
2016-02-17 09:18:36 -06:00
# if there are objects that can't be processed by lack of identifier, let's print keys for finding one.
2019-01-01 19:42:53 -06:00
log . exception ( " Can ' t find an identifier for the following object: %r " % ( list ( item . keys ( ) ) , ) )
2016-02-17 08:11:47 -06:00
for i in list :
2019-01-01 19:42:53 -06:00
if identifier in i and i [ identifier ] == item [ identifier ] :
2016-02-17 08:11:47 -06:00
return True
return False
2016-02-13 17:06:36 -06:00
class vkSession ( object ) :
2018-12-24 08:53:28 -06:00
""" The only session available in socializer. Manages everything related to a model in an MVC app: calls to VK, sound handling, settings and a cache database. """
2016-02-13 17:06:36 -06:00
2016-02-23 17:49:55 -06:00
def order_buffer ( self , name , data , show_nextpage ) :
2018-12-24 08:53:28 -06:00
""" Put new items on the local cache database.
@name str : The name for the buffer stored in the dictionary .
@data list : A list with items and some information about cursors .
2016-02-13 17:06:36 -06:00
returns the number of items that has been added in this execution """
2018-12-22 05:40:39 -06:00
global post_types
2019-02-14 13:09:44 -06:00
# When this method is called by friends.getOnlyne, it gives only friend IDS so we need to retrieve full objects from VK.
# ToDo: It would be nice to investigate whether reusing some existing objects would be a good idea, whenever possible.
if name == " online_friends " :
newdata = self . vk . client . users . get ( user_ids = " , " . join ( [ str ( z ) for z in data ] ) , fields = " last_seen " )
data = newdata
2016-02-17 08:11:47 -06:00
first_addition = False
2016-02-13 17:06:36 -06:00
num = 0
2019-01-01 19:42:53 -06:00
if ( name in self . db ) == False :
2016-02-13 17:06:36 -06:00
self . db [ name ] = { }
self . db [ name ] [ " items " ] = [ ]
2016-02-17 08:11:47 -06:00
first_addition = True
2019-04-09 16:08:14 -05:00
# Handles chat messages case, as the buffer is inverted
if name . endswith ( " _messages " ) and show_nextpage == True :
show_nextpage = False
2016-02-13 17:06:36 -06:00
for i in data :
2019-02-14 13:09:44 -06:00
if " type " in i and not isinstance ( i [ " type " ] , int ) and ( i [ " type " ] == " wall_photo " or i [ " type " ] == " photo_tag " or i [ " type " ] == " photo " ) :
2016-05-10 20:23:48 -05:00
log . debug ( " Skipping unsupported item... %r " % ( i , ) )
continue
2018-12-22 05:40:39 -06:00
# for some reason, VK sends post data if the post has been deleted already.
# Example of this behaviour is when you upload an audio and inmediately delete the audio, VK still sends the post stating that you uploaded an audio file,
# But without the audio data, making socializer to render an empty post.
# Here we check if the post contains data of the type it advertises.
2019-02-05 12:20:50 -06:00
if i . get ( " type " ) != None and isinstance ( i [ " type " ] , str ) and post_types . get ( i [ " type " ] ) not in i :
2018-12-31 11:50:11 -06:00
log . error ( " Detected invalid or unsupported post. Skipping... " )
log . error ( i )
2018-12-22 05:40:39 -06:00
continue
2016-02-17 08:11:47 -06:00
if find_item ( self . db [ name ] [ " items " ] , i ) == False :
# if i not in self.db[name]["items"]:
2016-02-25 04:52:02 -06:00
if first_addition == True or show_nextpage == True :
2016-02-17 08:11:47 -06:00
if self . settings [ " general " ] [ " reverse_timelines " ] == False : self . db [ name ] [ " items " ] . append ( i )
else : self . db [ name ] [ " items " ] . insert ( 0 , i )
else :
if self . settings [ " general " ] [ " reverse_timelines " ] == False : self . db [ name ] [ " items " ] . insert ( 0 , i )
else : self . db [ name ] [ " items " ] . append ( i )
2016-02-13 17:06:36 -06:00
num = num + 1
2016-05-10 20:23:48 -05:00
log . debug ( " There are %d items in the %s buffer " % ( len ( self . db [ name ] [ " items " ] ) , name ) )
2016-02-13 17:06:36 -06:00
return num
def __init__ ( self , session_id ) :
self . session_id = session_id
self . logged = False
self . settings = None
self . vk = vkSessionHandler . vkObject ( )
self . db = { }
self . db [ " users " ] = { }
self . db [ " groups " ] = { }
2019-05-06 15:50:45 -05:00
self . db [ " group_info " ] = { }
2016-02-13 17:06:36 -06:00
@property
def is_logged ( self ) :
return self . logged
def get_configuration ( self ) :
""" Gets settings for a session. """
file_ = " %s /session.conf " % ( self . session_id , )
# try:
log . debug ( " Creating config file %s " % ( file_ , ) )
2018-12-14 15:31:15 -06:00
self . settings = Configuration ( os . path . join ( paths . config_path ( ) , file_ ) , os . path . join ( paths . app_path ( ) , " session.defaults " ) )
2019-01-23 08:22:22 -06:00
self . soundplayer = sound . soundSystem ( config . app [ " sound " ] )
2019-04-30 17:36:53 -05:00
pub . subscribe ( self . play_sound , " play-sound " )
2016-02-13 17:06:36 -06:00
# except:
# log.exception("The session configuration has failed.")
2019-04-30 17:36:53 -05:00
def play_sound ( self , sound ) :
self . soundplayer . play ( sound )
2016-02-13 17:06:36 -06:00
def login ( self ) :
2019-02-14 08:36:31 -06:00
""" Logging in VK.com. This is basically the first method interacting with VK. """
# If user is already logged in, we should skip this method.
2019-02-12 17:49:33 -06:00
if self . logged == True :
return
2016-06-06 03:53:55 -05:00
try :
2018-12-14 15:31:15 -06:00
config_filename = os . path . join ( paths . config_path ( ) , self . session_id , " vkconfig.json " )
2019-01-19 23:20:51 -06:00
self . vk . login ( self . settings [ " vk " ] [ " user " ] , self . settings [ " vk " ] [ " password " ] , token = self . settings [ " vk " ] [ " token " ] , secret = self . settings [ " vk " ] [ " secret " ] , device_id = self . settings [ " vk " ] [ " device_id " ] , alt_token = self . settings [ " vk " ] [ " use_alternative_tokens " ] , filename = config_filename )
2018-12-15 21:09:17 -06:00
self . settings [ " vk " ] [ " token " ] = self . vk . session_object . token [ " access_token " ]
2019-04-11 05:29:42 -05:00
try :
self . settings [ " vk " ] [ " secret " ] = self . vk . session_object . secret
self . settings [ " vk " ] [ " device_id " ] = self . vk . session_object . device_id
except AttributeError :
pass
2016-06-06 03:53:55 -05:00
self . settings . write ( )
2018-12-09 07:25:30 -06:00
self . logged = True
2018-12-10 00:47:25 -06:00
self . get_my_data ( )
2019-02-12 17:49:33 -06:00
except VkApiError as error :
if error . code == 5 : # this means invalid access token.
self . settings [ " vk " ] [ " user " ] = " "
self . settings [ " vk " ] [ " password " ] = " "
self . settings [ " vk " ] [ " token " ] = " "
self . settings [ " vk " ] [ " secret " ] = " "
self . settings [ " vk " ] [ " device_id " ] = " "
self . settings . write ( )
pub . sendMessage ( " authorisation-failed " )
2019-02-14 08:36:31 -06:00
else : # print out error so we we will handle it in future versions.
2019-02-12 17:49:33 -06:00
log . exception ( " Fatal error when authenticating the application. " )
log . exception ( error . code )
log . exception ( error . message )
2019-02-14 08:36:31 -06:00
except ( ProxyError , ConnectionError ) :
pub . sendMessage ( " connection_error " )
2016-02-13 17:06:36 -06:00
def post_wall_status ( self , message , * args , * * kwargs ) :
2016-02-17 08:11:47 -06:00
""" Sends a post to an user, group or community wall. """
2016-05-10 20:23:48 -05:00
log . debug ( " Making a post to the user ' s wall with the following params: %r " % ( kwargs , ) )
2016-02-13 17:06:36 -06:00
response = self . vk . client . wall . post ( message = message , * args , * * kwargs )
2016-02-23 17:49:55 -06:00
def get_newsfeed ( self , name = " newsfeed " , show_nextpage = False , endpoint = " " , * args , * * kwargs ) :
2016-05-10 20:23:48 -05:00
log . debug ( " Updating news feed... " )
2019-01-01 19:42:53 -06:00
if show_nextpage == True and " cursor " in self . db [ name ] :
2016-05-10 20:23:48 -05:00
log . debug ( " user has requested previous items " )
2016-02-23 17:49:55 -06:00
kwargs [ " start_from " ] = self . db [ name ] [ " cursor " ]
2016-05-10 20:23:48 -05:00
log . debug ( " Params for sending to vk: %r " % ( kwargs , ) )
2016-02-13 17:06:36 -06:00
data = getattr ( self . vk . client . newsfeed , " get " ) ( * args , * * kwargs )
if data != None :
2019-02-11 03:55:16 -06:00
self . process_usernames ( data )
2016-02-25 04:52:02 -06:00
num = self . order_buffer ( name , data [ " items " ] , show_nextpage )
2019-01-01 19:42:53 -06:00
log . debug ( " Keys of the returned data for debug purposes: %r " % ( list ( data . keys ( ) ) , ) )
if " next_from " in data :
2016-03-28 05:19:08 -06:00
self . db [ name ] [ " cursor " ] = data [ " next_from " ]
2019-02-11 03:55:16 -06:00
log . debug ( " Next cursor saved for data: {cursor} " . format ( cursor = data [ " next_from " ] ) )
2016-02-13 17:06:36 -06:00
return num
2016-02-23 17:49:55 -06:00
def get_page ( self , name = " " , show_nextpage = False , endpoint = " " , * args , * * kwargs ) :
2016-02-13 17:06:36 -06:00
data = None
2018-12-15 21:09:17 -06:00
if " audio " in endpoint and self . settings [ " vk " ] [ " use_alternative_tokens " ] :
log . info ( " Using alternative audio methods. " )
2018-12-09 05:21:52 -06:00
c = self . vk . client_audio
else :
c = self . vk . client
2019-01-01 19:42:53 -06:00
if " parent_endpoint " in kwargs :
2016-02-13 17:06:36 -06:00
p = kwargs [ " parent_endpoint " ]
2018-12-15 21:09:17 -06:00
if " audio " in p and self . settings [ " vk " ] [ " use_alternative_tokens " ] :
log . info ( " Using alternative audio methods. " )
2018-12-09 05:21:52 -06:00
c = self . vk . client_audio
2016-02-13 17:06:36 -06:00
kwargs . pop ( " parent_endpoint " )
2018-12-09 05:21:52 -06:00
try :
p = getattr ( c , p )
except AttributeError :
p = c
2019-02-26 08:35:42 -06:00
if name in self . db and " offset " in self . db [ name ] and show_nextpage == True :
2019-02-11 04:53:32 -06:00
kwargs . update ( offset = self . db [ name ] [ " offset " ] )
else :
kwargs . update ( offset = 0 )
2016-05-10 20:23:48 -05:00
log . debug ( " Calling endpoint %s with params %r " % ( p , kwargs , ) )
2016-02-13 17:06:36 -06:00
data = getattr ( p , endpoint ) ( * args , * * kwargs )
if data != None :
2019-02-11 04:53:32 -06:00
if " count " not in kwargs :
kwargs [ " count " ] = 100
2019-04-09 16:08:14 -05:00
# Let's handle a little exception when dealing with conversation buffers.
# the first results of the query should be reversed before being sent to order_buffer.
if type ( data ) == dict and " items " in data and endpoint == " getHistory " and kwargs [ " offset " ] == 0 :
data [ " items " ] . reverse ( )
2016-03-22 02:59:31 -06:00
if type ( data ) == dict :
num = self . order_buffer ( name , data [ " items " ] , show_nextpage )
2019-02-11 04:53:32 -06:00
self . db [ name ] [ " offset " ] = kwargs [ " offset " ] + kwargs [ " count " ]
2019-01-01 19:42:53 -06:00
if len ( data [ " items " ] ) > 0 and " first_name " in data [ " items " ] [ 0 ] :
2016-07-07 10:20:03 -05:00
data2 = { " profiles " : [ ] , " groups " : [ ] }
2016-05-25 11:33:57 -05:00
for i in data [ " items " ] :
2016-07-07 10:20:03 -05:00
data2 [ " profiles " ] . append ( i )
2016-07-07 13:23:49 -05:00
self . process_usernames ( data2 )
2019-01-01 19:42:53 -06:00
if " profiles " in data and " groups " in data :
2016-03-30 16:27:37 -06:00
self . process_usernames ( data )
2016-03-22 02:59:31 -06:00
else :
num = self . order_buffer ( name , data , show_nextpage )
2019-02-11 04:53:32 -06:00
self . db [ name ] [ " offset " ] = kwargs [ " offset " ] + kwargs [ " count " ]
2016-02-13 17:06:36 -06:00
return num
2016-05-17 12:46:57 -05:00
def get_messages ( self , name = " " , * args , * * kwargs ) :
data = self . vk . client . messages . getHistory ( * args , * * kwargs )
2018-11-04 01:39:06 -06:00
data [ " items " ] . reverse ( )
2016-05-17 12:46:57 -05:00
if data != None :
num = self . order_buffer ( name , data [ " items " ] , False )
return num
2016-02-13 17:06:36 -06:00
def get_users ( self , user_ids = None , group_ids = None ) :
2016-05-10 20:23:48 -05:00
log . debug ( " Getting user information from the VK servers " )
2016-02-13 17:06:36 -06:00
if user_ids != None :
u = self . vk . client . users . get ( user_ids = user_ids , fields = " uid, first_name, last_name " )
for i in u :
2019-01-03 17:53:29 -06:00
self . db [ " users " ] [ i [ " id " ] ] = dict ( nom = " {0} {1} " . format ( i [ " first_name " ] , i [ " last_name " ] ) )
2016-02-13 17:06:36 -06:00
if group_ids != None :
g = self . vk . client . groups . getById ( group_ids = group_ids , fields = " name " )
for i in g :
2019-01-10 17:29:17 -06:00
self . db [ " groups " ] [ i [ " id " ] ] = dict ( nom = i [ " name " ] , gen = i [ " name " ] , dat = i [ " name " ] , acc = i [ " name " ] , ins = i [ " name " ] , abl = i [ " name " ] )
def get_user ( self , user_id , key = " user1 " ) :
if user_id > 0 :
if user_id in self . db [ " users " ] :
user_data = { }
user_fields = " nom, gen, ins, dat, abl, acc " . split ( " , " )
for i in user_fields :
k = " {key} _ {case} " . format ( key = key , case = i )
v = " {first_name} {last_name} " . format ( first_name = self . db [ " users " ] [ user_id ] [ " first_name_ " + i ] , last_name = self . db [ " users " ] [ user_id ] [ " last_name_ " + i ] )
user_data [ k ] = v
2019-01-11 04:22:53 -06:00
return user_data
# if User_id is not present in db.
else :
user = dict ( id = user_id )
self . process_usernames ( data = dict ( profiles = [ user ] , groups = [ ] ) )
2019-01-13 13:13:05 -06:00
return self . get_user ( user_id )
2019-01-10 17:29:17 -06:00
else :
if abs ( user_id ) in self . db [ " groups " ] :
user_data = { }
user_fields = " nom, gen, ins, dat, abl, acc " . split ( " , " )
for i in user_fields :
k = " {key} _ {case} " . format ( key = key , case = i )
v = self . db [ " groups " ] [ abs ( user_id ) ] [ i ]
user_data [ k ] = v
2019-05-29 09:16:16 -05:00
else :
group = self . vk . client . groups . getById ( group_ids = - 1 * user_id ) [ 0 ]
self . process_usernames ( data = dict ( profiles = [ ] , groups = [ group ] ) )
return self . get_user ( user_id = user_id , key = key )
2019-01-10 17:29:17 -06:00
return user_data
2016-02-17 09:18:36 -06:00
def process_usernames ( self , data ) :
2016-07-07 10:20:03 -05:00
""" processes user IDS and saves them in a local storage system.
Every function wich needs to convert from an ID to user or community name will have to call the get_user_name function in this session object .
Every function that needs to save a set ot user ids for a future use needs to pass a data dictionary with a profiles key being a list of user objects .
2019-01-10 17:29:17 -06:00
It gets first and last name for people in the 6 russian cases and saves them for future reference . """
2016-05-10 20:23:48 -05:00
log . debug ( " Adding usernames to the local database... " )
2016-07-07 10:20:03 -05:00
ids = " "
2016-02-17 09:18:36 -06:00
for i in data [ " profiles " ] :
2019-01-01 19:42:53 -06:00
if ( i [ " id " ] in self . db [ " users " ] ) == False :
2016-07-07 10:20:03 -05:00
ids = ids + " {0} , " . format ( i [ " id " ] , )
gids = " "
2016-02-17 09:18:36 -06:00
for i in data [ " groups " ] :
2019-02-12 17:49:33 -06:00
if i [ " id " ] not in self . db [ " groups " ] :
self . db [ " groups " ] [ i [ " id " ] ] = dict ( nom = i [ " name " ] , gen = i [ " name " ] , dat = i [ " name " ] , acc = i [ " name " ] , ins = i [ " name " ] , abl = i [ " name " ] )
gids = " {0} , " . format ( i [ " id " ] , )
2019-01-10 17:29:17 -06:00
user_fields = " first_name_nom, last_name_nom, first_name_gen, last_name_gen, first_name_ins, last_name_ins, first_name_dat, last_name_dat, first_name_abl, last_name_abl, first_name_acc, last_name_acc, sex "
user_fields_list = user_fields . split ( " , " )
2016-07-10 22:51:06 -05:00
if ids != " " :
2019-01-10 17:29:17 -06:00
users = self . vk . client . users . get ( user_ids = ids , fields = user_fields )
for i in users :
if i [ " id " ] not in self . db [ " users " ] :
userdata = { }
for field in user_fields_list :
if field in i :
userdata [ field ] = i [ field ]
self . db [ " users " ] [ i [ " id " ] ] = userdata
2016-02-19 17:30:11 -06:00
def get_my_data ( self ) :
2016-05-10 20:23:48 -05:00
log . debug ( " Getting user identifier... " )
2016-03-14 16:07:44 -06:00
user = self . vk . client . users . get ( fields = " uid, first_name, last_name " )
2018-12-24 08:53:28 -06:00
self . user_id = user [ 0 ] [ " id " ]