2014-11-12 20:41:29 -06:00
# -*- coding: utf-8 -*-
""" The main session object. Here are the twitter functions to interact with the " model " of TWBlue. """
2015-02-01 00:49:03 -06:00
import urllib2
2014-11-12 20:41:29 -06:00
import twitter
2015-02-04 17:00:03 -06:00
from keys import keyring
2014-11-12 20:41:29 -06:00
import session_exceptions as Exceptions
import paths
import output
import time
import sound
2015-01-18 17:19:39 -06:00
import logging
2014-11-12 20:41:29 -06:00
from twitter import utils
from twython import TwythonError , TwythonRateLimitError , TwythonAuthError
2015-03-30 10:55:56 -06:00
import config_utils
2015-05-01 01:48:42 -04:00
import shelve
2015-05-02 03:41:28 -04:00
import application
import os
2015-01-13 12:31:37 -06:00
from mysc . thread_utils import stream_threaded
2015-02-01 21:13:18 -06:00
from pubsub import pub
2015-01-18 17:19:39 -06:00
log = logging . getLogger ( " sessionmanager.session " )
2014-11-12 20:41:29 -06: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 ) :
2015-04-12 19:43:19 -04:00
""" Decorator for checking if the user is logged in(a twitter object has credentials) on twitter.
Some functions may need this to avoid making unneeded twitter API calls . """
2014-11-12 20:41:29 -06:00
def f ( self , * args , * * kwargs ) :
if self . logged == True :
fn ( self , * args , * * kwargs )
else :
2015-04-12 19:43:19 -04:00
raise Exceptions . NotLoggedSessionError ( " You are not logged in yet. " )
2014-11-12 20:41:29 -06:00
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 ) :
2015-04-12 19:43:19 -04:00
""" Put the new items in the local database.
2014-11-12 20:41:29 -06:00
name str : The name for the buffer stored in the dictionary .
data list : A list with tweets .
2015-04-12 19:43:19 -04:00
returns the number of items that have been added in this execution """
2014-11-12 20:41:29 -06:00
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 .
2015-04-12 19:43:19 -04:00
returns the number of items that have been added in this execution """
2014-11-12 20:41:29 -06:00
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 ( )
2015-05-01 01:48:42 -04:00
self . db = { }
2015-01-13 12:31:37 -06:00
self . reconnection_function_active = False
2015-03-07 20:23:41 -06:00
self . counter = 0
2015-04-27 16:08:02 -05:00
self . lists = [ ]
pub . subscribe ( self . add_friends , " friends-receibed " )
2014-11-12 20:41:29 -06:00
2015-02-26 15:21:26 -06:00
@property
def is_logged ( self ) :
return self . logged
2014-11-12 20:41:29 -06:00
def get_configuration ( self ) :
2015-01-20 15:40:33 -06:00
""" Gets settings for a session. """
file_ = " %s /session.conf " % ( self . session_id , )
# try:
2015-01-18 17:19:39 -06:00
log . debug ( " Creating config file %s " % ( file_ , ) )
2015-03-30 10:55:56 -06:00
self . settings = config_utils . load_config ( paths . config_path ( file_ ) , paths . app_path ( " Conf.defaults " ) )
2015-01-20 15:40:33 -06:00
self . init_sound ( )
2015-05-09 23:12:17 -04:00
self . deshelve ( )
2015-01-20 15:40:33 -06:00
# except:
# log.exception("The session configuration has failed.")
# self.settings = None
def init_sound ( self ) :
self . sound = sound . soundSystem ( self . settings [ " sound " ] )
2014-11-12 20:41:29 -06:00
@_require_configuration
2015-03-08 00:36:41 -06:00
def login ( self , verify_credentials = True ) :
2014-11-12 20:41:29 -06:00
2015-04-12 19:43:19 -04:00
""" Log into twitter using credentials from settings.
2014-11-12 20:41:29 -06: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-18 17:19:39 -06:00
log . debug ( " Logging in to twitter... " )
2015-03-08 00:36:41 -06:00
self . twitter . login ( self . settings [ " twitter " ] [ " user_key " ] , self . settings [ " twitter " ] [ " user_secret " ] , verify_credentials )
2014-11-12 20:41:29 -06:00
self . logged = True
2015-01-18 17:19:39 -06:00
log . debug ( " Logged. " )
2015-03-19 04:56:29 -06:00
self . counter = 0
2014-11-12 20:41:29 -06:00
else :
self . logged = False
raise Exceptions . RequireCredentialsSessionError
@_require_configuration
def authorise ( self ) :
2015-04-12 19:43:19 -04:00
""" Authorises a Twitter account. This function needs to be called for each new session, after self.get_configuration() and before self.login() """
2014-11-12 20:41:29 -06:00
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 09:59:18 -06: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-12 20:41:29 -06:00
def api_call ( self , call_name , action = " " , _sound = None , report_success = False , report_failure = True , preexec_message = " " , * args , * * kwargs ) :
2015-04-12 19:43:19 -04:00
""" Make a call to the Twitter API. If there is a connectionError or another exception not related to Twitter, It will call the method again at least 25 times, waiting a while between calls. Useful for post methods.
If twitter returns an error , it will not call the method anymore .
2014-11-12 20:41:29 -06:00
call_name str : The method to call
2015-04-12 19:43:19 -04:00
action str : What you are doing on twitter , it will be reported to the user if report_success is set to True .
2014-11-12 20:41:29 -06:00
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 .
2015-04-12 19:43:19 -04:00
report_success and report_failure bool : These are self explanatory . True or False .
preexec_message str : A message to speak to the user while the method is running , example : " trying to follow x user " . """
2014-11-12 20:41:29 -06:00
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-01 20:41:02 -06: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-12 20:41:29 -06:00
output . speak ( _ ( " %s failed. Reason: %s " ) % ( action , e . message ) )
finished = True
2015-02-03 09:59:18 -06:00
except :
tries = tries + 1
time . sleep ( 5 )
2014-11-12 20:41:29 -06:00
if report_success :
output . speak ( _ ( " %s succeeded. " ) % action )
2015-01-22 08:54:41 -06:00
if _sound != None : self . sound . play ( _sound )
2014-11-12 20:41:29 -06:00
2015-03-24 17:07:14 -06:00
def search ( self , name , * args , * * kwargs ) :
tl = self . twitter . twitter . search ( * args , * * kwargs )
tl [ " statuses " ] . reverse ( )
return tl [ " statuses " ]
2014-11-12 20:41:29 -06:00
@_require_login
def get_favourites_timeline ( self , name , * args , * * kwargs ) :
2015-04-12 19:43:19 -04:00
""" Gets favourites for the authenticated user or a friend or follower.
name str : Name for storage in the database . """
2014-11-12 20:41:29 -06:00
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
2015-04-12 19:43:19 -04:00
returns a list with all items retrieved . """
2014-11-12 20:41:29 -06:00
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-27 17:09:28 -06:00
data = getattr ( self . twitter . twitter , update_function ) ( max_id = max_id , count = self . settings [ " general " ] [ " max_tweets_per_call " ] , * args , * * kwargs )
2014-11-12 20:41:29 -06: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 13:48:57 -06:00
self . settings [ " twitter " ] [ " user_name " ] = sn
2014-11-12 20:41:29 -06: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 ) :
2015-04-12 19:43:19 -04:00
""" Gets the lists that the user is subscribed to and stores them in the database. Returns None. """
2014-11-12 20:41:29 -06:00
self . db [ " lists " ] = self . twitter . twitter . show_lists ( reverse = True )
@_require_login
def get_muted_users ( self ) :
""" Gets muted users (oh really?). """
2015-05-09 16:29:20 -04:00
# self.db["muted_users"] = self.twitter.twitter.get_muted_users_ids()["ids"]
2015-05-10 13:37:05 -04:00
#This really needs to be fixed (#30). Return an empty list as a hacky workaround, remove this comment and the line below when fixed.
self . db [ " muted_users " ] = [ ]
2014-11-12 20:41:29 -06:00
@_require_login
def get_stream ( self , name , function , * args , * * kwargs ) :
""" Retrieves the items for a regular stream.
2015-04-12 19:43:19 -04:00
name str : Name to save items to the database .
2014-11-12 20:41:29 -06:00
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 ) :
2015-04-12 19:43:19 -04:00
""" Gets items for API calls that require using cursors to paginate the results.
2014-11-12 20:41:29 -06:00
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 .
2015-04-12 19:43:19 -04:00
for example get_followers_list returns a list and users are under list [ " users " ] , here the items should point to " users " . """
2014-11-12 20:41:29 -06:00
items_ = [ ]
try :
2015-02-03 09:59:18 -06:00
if self . db [ name ] . has_key ( " cursor " ) :
cursor = self . db [ name ] [ " cursor " ]
2014-11-12 20:41:29 -06:00
else :
2015-02-03 09:59:18 -06:00
cursor = - 1
2014-11-12 20:41:29 -06:00
except KeyError :
2015-02-03 09:59:18 -06:00
cursor = - 1
tl = getattr ( self . twitter . twitter , function ) ( cursor = cursor , count = self . settings [ " general " ] [ " max_tweets_per_call " ] , * args , * * kwargs )
2014-11-12 20:41:29 -06:00
tl [ items ] . reverse ( )
num = self . order_cursored_buffer ( name , tl [ items ] )
2015-02-03 09:59:18 -06:00
self . db [ name ] [ " cursor " ] = tl [ " next_cursor " ]
2014-11-12 20:41:29 -06:00
return num
def start_streaming ( self ) :
""" Start the streaming for sending tweets in realtime. """
2015-01-13 12:31:37 -06:00
self . get_timelines ( )
2015-05-02 17:22:28 -05:00
self . get_main_stream ( )
2015-01-13 12:31:37 -06:00
def get_main_stream ( self ) :
2015-01-18 17:19:39 -06:00
log . debug ( " Starting the main stream... " )
2015-02-04 17:00:03 -06: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 12:31:37 -06:00
stream_threaded ( self . main_stream . user , self . session_id )
def get_timelines ( self ) :
2015-01-18 17:19:39 -06:00
log . debug ( " Starting the timelines stream... " )
2015-02-04 17:00:03 -06: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-12 20:41:29 -06:00
ids = " "
for i in self . settings [ " other_buffers " ] [ " timelines " ] :
ids = ids + " %s , " % ( self . db [ i + " -timeline " ] [ 0 ] [ " user " ] [ " id_str " ] )
2015-04-27 16:08:02 -05:00
for i in self . lists :
for z in i . users :
ids + = str ( z ) + " , "
2015-03-07 20:23:41 -06:00
if ids != " " :
stream_threaded ( self . timelinesStream . statuses . filter , self . session_id , follow = ids )
2015-01-13 12:31:37 -06:00
2015-04-27 16:08:02 -05:00
def add_friends ( self ) :
2015-05-02 17:22:28 -05:00
try :
self . timelinesStream . set_friends ( self . main_stream . friends )
except AttributeError :
pass
2015-04-27 16:08:02 -05:00
2015-01-13 12:31:37 -06:00
def listen_stream_error ( self ) :
if hasattr ( self , " main_stream " ) :
2015-01-18 17:19:39 -06:00
log . debug ( " Disconnecting the main stream... " )
2015-01-13 12:31:37 -06:00
self . main_stream . disconnect ( )
del self . main_stream
if hasattr ( self , " timelinesStream " ) :
2015-01-18 17:19:39 -06:00
log . debug ( " disconnecting the timelines stream... " )
2015-01-13 12:31:37 -06:00
self . timelinesStream . disconnect ( )
del self . timelinesStream
def check_connection ( self ) :
2015-03-07 20:23:41 -06:00
instan = 0
self . counter + = 1
if self . counter > = 4 :
del self . twitter
self . logged = False
self . twitter = twitter . twitter . twitter ( )
2015-03-08 00:36:41 -06:00
self . login ( False )
2015-01-13 12:31:37 -06: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-05-02 17:22:28 -05:00
if hasattr ( self , " timelinesStream " ) and not hasattr ( self . timelinesStream , " friends " ) :
self . add_friends ( )
2015-02-01 00:49:03 -06:00
try :
urllib2 . urlopen ( " http://74.125.228.231 " , timeout = 5 )
except urllib2 . URLError :
2015-02-12 10:29:51 -06: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 ( )
2015-05-01 01:48:42 -04:00
del self . main_stream
def shelve ( self ) :
" Shelve the database to allow for persistance. "
shelfname = paths . config_path ( str ( self . session_id ) + " .db " )
2015-05-09 23:12:17 -04:00
if self . settings [ " general " ] [ " persist_size " ] == 0 :
2015-05-09 23:13:17 -04:00
if os . path . exists ( shelfname ) :
2015-05-09 23:12:17 -04:00
os . remove ( shelfname )
return
2015-05-02 03:41:28 -04:00
try :
2015-05-02 04:04:29 -04:00
if not os . path . exists ( shelfname ) :
2015-05-02 04:00:29 -04:00
output . speak ( " Generating database, this might take a while. " , True )
2015-05-02 03:41:28 -04:00
shelf = shelve . open ( paths . config_path ( shelfname ) , ' c ' )
for key , value in self . db . items ( ) :
2015-05-02 03:53:20 -04:00
if type ( key ) != str and type ( key ) != unicode :
2015-05-02 04:00:29 -04:00
output . speak ( " Uh oh, while shelving the database, a key of type " + str ( type ( key ) ) + " has been found. It will be converted to type str, but this will cause all sorts of problems on deshelve. Please bring this to the attention of the " + application . name + " developers immediately. More information about the error will be written to the error log. " , True )
2015-05-02 03:53:20 -04:00
log . error ( " Uh oh, " + str ( key ) + " is of type " + str ( type ( key ) ) + " ! " )
2015-05-09 23:19:21 -04:00
if type ( value ) == list and self . settings [ " general " ] [ " persist_size " ] != - 1 and len ( value ) > self . settings [ " general " ] [ " persist_size " ] :
2015-05-09 23:04:06 -04:00
shelf [ str ( key ) ] = value [ self . settings [ " general " ] [ " persist_size " ] : ]
else :
shelf [ str ( key ) ] = value
2015-05-02 03:41:28 -04:00
shelf . close ( )
except :
output . speak ( " An exception occurred while shelving the " + application . name + " database. It will be deleted and rebuilt automatically. If this error persists, send the error log to the " + application . name + " developers. " , True )
log . exception ( " Exception while shelving " + shelfname )
os . remove ( shelfname )
2015-05-01 01:48:42 -04:00
def deshelve ( self ) :
" Import a shelved database. "
shelfname = paths . config_path ( str ( self . session_id ) + " .db " )
2015-05-09 23:12:17 -04:00
if self . settings [ " general " ] [ " persist_size " ] == 0 :
2015-05-09 23:13:47 -04:00
if os . path . exists ( shelfname ) :
2015-05-09 23:12:17 -04:00
os . remove ( shelfname )
return
2015-05-02 03:41:28 -04:00
try :
shelf = shelve . open ( paths . config_path ( shelfname ) , ' c ' )
for key , value in shelf . items ( ) :
2015-05-02 03:48:36 -04:00
self . db [ unicode ( key ) ] = value
2015-05-02 03:41:28 -04:00
shelf . close ( )
except :
output . speak ( " An exception occurred while deshelving the " + application . name + " database. It will be deleted and rebuilt automatically. If this error persists, send the error log to the " + application . name + " developers. " , True )
log . exception ( " Exception while deshelving " + shelfname )
2015-05-02 17:22:32 -05:00
os . remove ( shelfname )
2015-05-12 19:11:24 -04:00