2014-11-13 03:41:29 +01:00
# -*- coding: utf-8 -*-
""" The main session object. Here are the twitter functions to interact with the " model " of TWBlue. """
2015-03-08 03:23:41 +01:00
import gc
2015-02-01 07:49:03 +01:00
import urllib2
2014-11-13 03:41:29 +01:00
import twitter
2015-02-05 00:00:03 +01:00
from keys import keyring
2014-11-13 03:41:29 +01:00
import session_exceptions as Exceptions
import paths
import output
import time
import sound
2015-01-19 00:19:39 +01:00
import logging
2014-11-13 03:41:29 +01:00
from twitter import utils
from twython import TwythonError , TwythonRateLimitError , TwythonAuthError
from config_utils import Configuration , ConfigurationResetException
2015-01-13 19:31:37 +01:00
from mysc . thread_utils import stream_threaded
2015-02-02 04:13:18 +01:00
from pubsub import pub
2015-01-19 00:19:39 +01:00
log = logging . getLogger ( " sessionmanager.session " )
2014-11-13 03:41:29 +01:00
sessions = { }
class Session ( object ) :
""" A session object where we will save configuration, the twitter object and a local storage for saving the items retrieved through the Twitter API methods """
# Decorators.
def _require_login ( fn ) :
""" Decorator for checking if the user is logged (a twitter object has credentials) on twitter.
Some functions may need this to avoid make unneeded twitter API calls . """
def f ( self , * args , * * kwargs ) :
if self . logged == True :
fn ( self , * args , * * kwargs )
else :
raise Exceptions . NotLoggedSessionError ( " You are not logged yet. " )
return f
def _require_configuration ( fn ) :
""" Check if the user has a configured session. """
def f ( self , * args , * * kwargs ) :
if self . settings != None :
fn ( self , * args , * * kwargs )
else :
raise Exceptions . NotConfiguredSessionError ( " Not configured. " )
return f
def order_buffer ( self , name , data ) :
""" Put the new items on the local database.
name str : The name for the buffer stored in the dictionary .
data list : A list with tweets .
returns the number of items that has been added in this execution """
num = 0
if self . db . has_key ( name ) == False :
self . db [ name ] = [ ]
for i in data :
if utils . find_item ( i [ " id " ] , self . db [ name ] ) == None :
if self . settings [ " general " ] [ " reverse_timelines " ] == False : self . db [ name ] . append ( i )
else : self . db [ name ] . insert ( 0 , i )
num = num + 1
return num
def order_cursored_buffer ( self , name , data ) :
""" Put the new items on the local database. Useful for cursored buffers (followers, friends, users of a list and searches)
name str : The name for the buffer stored in the dictionary .
data list : A list with items and some information about cursors .
returns the number of items that has been added in this execution """
num = 0
if self . db . has_key ( name ) == False :
self . db [ name ] = { }
self . db [ name ] [ " items " ] = [ ]
# if len(self.db[name]["items"]) > 0:
for i in data :
if utils . find_item ( i [ " id " ] , self . db [ name ] [ " items " ] ) == None :
if self . settings [ " general " ] [ " reverse_timelines " ] == False : self . db [ name ] [ " items " ] . append ( i )
else : self . db [ name ] [ " items " ] . insert ( 0 , i )
num = num + 1
return num
def __init__ ( self , session_id ) :
""" session_id (str): The name of the folder inside the config directory where the session is located. """
super ( Session , self ) . __init__ ( )
self . session_id = session_id
self . logged = False
self . settings = None
self . twitter = twitter . twitter . twitter ( )
self . db = { }
2015-01-13 19:31:37 +01:00
self . reconnection_function_active = False
2015-03-08 03:23:41 +01:00
self . counter = 0
2014-11-13 03:41:29 +01:00
2015-02-26 22:21:26 +01:00
@property
def is_logged ( self ) :
return self . logged
2014-11-13 03:41:29 +01:00
def get_configuration ( self ) :
2015-01-20 22:40:33 +01:00
""" Gets settings for a session. """
file_ = " %s /session.conf " % ( self . session_id , )
# try:
2015-01-19 00:19:39 +01:00
log . debug ( " Creating config file %s " % ( file_ , ) )
2014-11-13 03:41:29 +01:00
self . settings = Configuration ( paths . config_path ( file_ ) , paths . app_path ( " Conf.defaults " ) )
2015-01-20 22:40:33 +01:00
self . init_sound ( )
# except:
# log.exception("The session configuration has failed.")
# self.settings = None
def init_sound ( self ) :
self . sound = sound . soundSystem ( self . settings [ " sound " ] )
2014-11-13 03:41:29 +01:00
@_require_configuration
2015-03-08 07:36:41 +01:00
def login ( self , verify_credentials = True ) :
2014-11-13 03:41:29 +01:00
2015-01-13 19:31:37 +01:00
""" Login in to twitter using credentials from settings.
2014-11-13 03:41:29 +01:00
if the user account isn ' t authorised, it needs to call self.authorise() before login. " " "
if self . settings [ " twitter " ] [ " user_key " ] != None and self . settings [ " twitter " ] [ " user_secret " ] != None :
2015-01-19 00:19:39 +01:00
log . debug ( " Logging in to twitter... " )
2015-03-08 07:36:41 +01:00
self . twitter . login ( self . settings [ " twitter " ] [ " user_key " ] , self . settings [ " twitter " ] [ " user_secret " ] , verify_credentials )
2014-11-13 03:41:29 +01:00
self . logged = True
2015-01-19 00:19:39 +01:00
log . debug ( " Logged. " )
2014-11-13 03:41:29 +01:00
else :
self . logged = False
raise Exceptions . RequireCredentialsSessionError
@_require_configuration
def authorise ( self ) :
""" Authorises a Twitter account. This function needs to be called for each new session, after of self.get_configuration() and before of self.login() """
if self . logged == True :
raise Exceptions . AlreadyAuthorisedError ( " The authorisation process is not needed at this time. " )
else :
self . twitter . authorise ( self . settings )
2015-02-03 16:59:18 +01:00
def get_more_items ( self , update_function , users = False , name = None , * args , * * kwargs ) :
results = [ ]
data = getattr ( self . twitter . twitter , update_function ) ( * args , * * kwargs )
if users == True :
self . db [ name ] [ " cursor " ] = data [ " next_cursor " ]
for i in data [ " users " ] : results . append ( i )
else :
results . extend ( data [ 1 : ] )
return results
2014-11-13 03:41:29 +01:00
def api_call ( self , call_name , action = " " , _sound = None , report_success = False , report_failure = True , preexec_message = " " , * args , * * kwargs ) :
""" Make a call to the Twitter API. If there is a connectionError or another exception not related to Twitter, It will call to the method at least 25 times, waiting a while between calls. Useful for post methods.
If twitter returns an error , it will not call anymore the method .
call_name str : The method to call
action str : The thing what you are doing on twitter , it will be reported to the user if report_success is set to True .
for example " following @tw_blue2 " will be reported as " following @tw_blue2 succeeded " .
_sound str : a sound to play if the call is executed properly .
report_success and report_failure bool : These are self explanatory . True or false . It ' s all.
preexec_message str : A message to speak to the user while the call is doing the work , example : " try to follow to x user " . """
finished = False
tries = 0
if preexec_message :
output . speak ( preexec_message , True )
while finished == False and tries < 25 :
try :
val = getattr ( self . twitter . twitter , call_name ) ( * args , * * kwargs )
finished = True
except TwythonError as e :
output . speak ( e . message )
2015-03-02 03:41:02 +01:00
if e . error_code != 403 and e . error_code != 404 :
tries = tries + 1
time . sleep ( 5 )
elif report_failure and hasattr ( e , ' message ' ) :
2014-11-13 03:41:29 +01:00
output . speak ( _ ( " %s failed. Reason: %s " ) % ( action , e . message ) )
finished = True
2015-02-03 16:59:18 +01:00
except :
tries = tries + 1
time . sleep ( 5 )
2014-11-13 03:41:29 +01:00
if report_success :
output . speak ( _ ( " %s succeeded. " ) % action )
2015-01-22 15:54:41 +01:00
if _sound != None : self . sound . play ( _sound )
2014-11-13 03:41:29 +01:00
@_require_login
def get_favourites_timeline ( self , name , * args , * * kwargs ) :
""" Gets favourites for the authenticated user or a friend or follower or somewhat.
name str : Name for store all in the database . """
tl = self . call_paged ( self . twitter . twitter . get_favorites , * args , * * kwargs )
return self . order_buffer ( name , tl )
def call_paged ( self , update_function , * args , * * kwargs ) :
""" Makes a call to the Twitter API methods several times. Useful for get methods.
this function is needed for retrieving more than 200 items .
update_function str : The function to call . This function must be child of self . twitter . twitter
return a list with all items retrieved . """
max = int ( self . settings [ " general " ] [ " max_api_calls " ] ) - 1
results = [ ]
data = getattr ( self . twitter . twitter , update_function ) ( count = self . settings [ " general " ] [ " max_tweets_per_call " ] , * args , * * kwargs )
results . extend ( data )
for i in range ( 0 , max ) :
if i == 0 : max_id = results [ - 1 ] [ " id " ]
else : max_id = results [ 0 ] [ " id " ]
2015-01-28 00:09:28 +01:00
data = getattr ( self . twitter . twitter , update_function ) ( max_id = max_id , count = self . settings [ " general " ] [ " max_tweets_per_call " ] , * args , * * kwargs )
2014-11-13 03:41:29 +01:00
results . extend ( data )
results . reverse ( )
return results
@_require_login
def get_user_info ( self ) :
""" Retrieves some information required by TWBlue for setup. """
f = self . twitter . twitter . get_account_settings ( )
sn = f [ " screen_name " ]
2015-03-03 20:48:57 +01:00
self . settings [ " twitter " ] [ " user_name " ] = sn
2014-11-13 03:41:29 +01:00
self . db [ " user_name " ] = sn
self . db [ " user_id " ] = self . twitter . twitter . show_user ( screen_name = sn ) [ " id_str " ]
try :
self . db [ " utc_offset " ] = f [ " time_zone " ] [ " utc_offset " ]
except KeyError :
self . db [ " utc_offset " ] = - time . timezone
self . get_lists ( )
self . get_muted_users ( )
self . settings . write ( )
@_require_login
def get_lists ( self ) :
""" Gets the lists that the user is suscribed. """
self . db [ " lists " ] = self . twitter . twitter . show_lists ( reverse = True )
@_require_login
def get_muted_users ( self ) :
""" Gets muted users (oh really?). """
self . db [ " muted_users " ] = self . twitter . twitter . get_muted_users_ids ( ) [ " ids " ]
@_require_login
def get_stream ( self , name , function , * args , * * kwargs ) :
""" Retrieves the items for a regular stream.
name str : Name to save items on the database .
function str : A function to get the items . """
last_id = - 1
if self . db . has_key ( name ) :
try :
if self . db [ name ] [ 0 ] [ " id " ] > self . db [ name ] [ - 1 ] [ " id " ] :
last_id = self . db [ name ] [ 0 ] [ " id " ]
else :
last_id = self . db [ name ] [ - 1 ] [ " id " ]
except IndexError :
pass
tl = self . call_paged ( function , sinze_id = last_id , * args , * * kwargs )
self . order_buffer ( name , tl )
@_require_login
def get_cursored_stream ( self , name , function , items = " users " , * args , * * kwargs ) :
""" Gets items for API calls that requires using cursors to paginate the results.
name str : Name to save it in the database .
function str : Function that provides the items .
items : When the function returns the list with results , items will tell how the order function should be look .
for example get_followers_list returns a list and users are under list [ " users " ] , here the items should be point to " users " . """
items_ = [ ]
try :
2015-02-03 16:59:18 +01:00
if self . db [ name ] . has_key ( " cursor " ) :
cursor = self . db [ name ] [ " cursor " ]
2014-11-13 03:41:29 +01:00
else :
2015-02-03 16:59:18 +01:00
cursor = - 1
2014-11-13 03:41:29 +01:00
except KeyError :
2015-02-03 16:59:18 +01:00
cursor = - 1
tl = getattr ( self . twitter . twitter , function ) ( cursor = cursor , count = self . settings [ " general " ] [ " max_tweets_per_call " ] , * args , * * kwargs )
2014-11-13 03:41:29 +01:00
tl [ items ] . reverse ( )
num = self . order_cursored_buffer ( name , tl [ items ] )
2015-02-03 16:59:18 +01:00
self . db [ name ] [ " cursor " ] = tl [ " next_cursor " ]
2014-11-13 03:41:29 +01:00
return num
def start_streaming ( self ) :
""" Start the streaming for sending tweets in realtime. """
2015-01-13 19:31:37 +01:00
self . get_main_stream ( )
self . get_timelines ( )
def get_main_stream ( self ) :
2015-01-19 00:19:39 +01:00
log . debug ( " Starting the main stream... " )
2015-02-05 00:00:03 +01:00
self . main_stream = twitter . buffers . stream . streamer ( keyring . get ( " api_key " ) , keyring . get ( " api_secret " ) , self . settings [ " twitter " ] [ " user_key " ] , self . settings [ " twitter " ] [ " user_secret " ] , self )
2015-01-13 19:31:37 +01:00
stream_threaded ( self . main_stream . user , self . session_id )
def get_timelines ( self ) :
2015-01-19 00:19:39 +01:00
log . debug ( " Starting the timelines stream... " )
2015-02-05 00:00:03 +01:00
self . timelinesStream = twitter . buffers . indibidual . timelinesStreamer ( keyring . get ( " api_key " ) , keyring . get ( " api_secret " ) , self . settings [ " twitter " ] [ " user_key " ] , self . settings [ " twitter " ] [ " user_secret " ] , session = self )
2014-11-13 03:41:29 +01:00
ids = " "
for i in self . settings [ " other_buffers " ] [ " timelines " ] :
ids = ids + " %s , " % ( self . db [ i + " -timeline " ] [ 0 ] [ " user " ] [ " id_str " ] )
# if ids != "":
2015-03-08 03:23:41 +01:00
if ids != " " :
stream_threaded ( self . timelinesStream . statuses . filter , self . session_id , follow = ids )
2015-01-13 19:31:37 +01:00
def listen_stream_error ( self ) :
if hasattr ( self , " main_stream " ) :
2015-01-19 00:19:39 +01:00
log . debug ( " Disconnecting the main stream... " )
2015-01-13 19:31:37 +01:00
self . main_stream . disconnect ( )
del self . main_stream
if hasattr ( self , " timelinesStream " ) :
2015-01-19 00:19:39 +01:00
log . debug ( " disconnecting the timelines stream... " )
2015-01-13 19:31:37 +01:00
self . timelinesStream . disconnect ( )
del self . timelinesStream
def check_connection ( self ) :
2015-03-08 03:23:41 +01:00
instan = 0
self . counter + = 1
if self . counter > = 4 :
del self . twitter
self . logged = False
self . twitter = twitter . twitter . twitter ( )
2015-03-08 07:36:41 +01:00
self . login ( False )
2015-01-13 19:31:37 +01:00
if self . reconnection_function_active == True : return
self . reconnection_function_active = True
if not hasattr ( self , " main_stream " ) :
self . get_main_stream ( )
if not hasattr ( self , " timelinesStream " ) :
self . get_timelines ( )
self . reconnection_function_active = False
2015-02-01 07:49:03 +01:00
try :
urllib2 . urlopen ( " http://74.125.228.231 " , timeout = 5 )
except urllib2 . URLError :
2015-02-12 17:29:51 +01:00
pub . sendMessage ( " stream-error " , session = self . session_id )
def remove_stream ( self , stream ) :
if stream == " timelinesStream " :
self . timelinesStream . disconnect ( )
del self . timelinesStream
else :
self . main_stream . disconnect ( )
del self . main_stream