2010-04-22 18:57:27 +00:00
|
|
|
# Python module that handles calls to the twitter API as a separate thread
|
|
|
|
|
|
|
|
import re
|
2010-05-11 21:38:26 +00:00
|
|
|
import gtk, gobject
|
2010-05-04 15:03:32 +00:00
|
|
|
from threading import Thread,RLock
|
|
|
|
from twitter import Api
|
2010-05-17 21:31:42 +00:00
|
|
|
from urllib2 import HTTPError,URLError,urlopen
|
|
|
|
from avcache import AvCache
|
2010-05-04 15:03:32 +00:00
|
|
|
|
2010-05-17 15:24:32 +00:00
|
|
|
|
2010-05-11 21:38:26 +00:00
|
|
|
class CustomApi(Api):
|
|
|
|
'''
|
|
|
|
This is a Twitter API with an RLock for multi-threaded access
|
|
|
|
Also included is logic for processing the list names when the object is
|
|
|
|
instantiated
|
|
|
|
'''
|
2010-05-04 15:03:32 +00:00
|
|
|
|
|
|
|
def __init__(self, username, password):
|
|
|
|
Api.__init__(self, username, password)
|
|
|
|
self.lock = RLock()
|
2010-05-11 21:38:26 +00:00
|
|
|
self.sig_proxy = SigProxy()
|
2010-05-04 15:03:32 +00:00
|
|
|
|
2010-05-11 21:38:26 +00:00
|
|
|
self.username = username
|
|
|
|
|
|
|
|
thread = GetUserLists(api=self)
|
|
|
|
thread.sig_proxy.connect('lists-ready', self.on_lists_ready)
|
|
|
|
thread.start()
|
|
|
|
|
|
|
|
|
|
|
|
def on_lists_ready(self, widget, lists, ignored):
|
|
|
|
list_names = []
|
|
|
|
for l in lists['lists']:
|
|
|
|
list_names.append(l.name)
|
|
|
|
list_names.sort()
|
|
|
|
self.sig_proxy.emit('lists-ready', self.username, list_names)
|
2010-05-04 15:03:32 +00:00
|
|
|
|
2010-05-11 21:38:26 +00:00
|
|
|
# End class CustomApi
|
2010-05-04 15:03:32 +00:00
|
|
|
|
2010-04-22 18:57:27 +00:00
|
|
|
|
2010-05-06 17:18:02 +00:00
|
|
|
class ApiThread(Thread):
|
|
|
|
def __init__(self, api):
|
2010-04-22 18:57:27 +00:00
|
|
|
Thread.__init__(self)
|
|
|
|
self.api = api
|
2010-05-06 17:18:02 +00:00
|
|
|
self.setDaemon(True)
|
|
|
|
|
|
|
|
# End class ApiThread
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class GetTweets(ApiThread):
|
|
|
|
def __init__(self, api, list_name, pane, num_entries, username):
|
|
|
|
ApiThread.__init__(self, api)
|
2010-04-22 18:57:27 +00:00
|
|
|
self.list_name = list_name
|
|
|
|
self.pane = pane
|
|
|
|
self.num_entries = num_entries
|
|
|
|
self.username = username
|
|
|
|
|
|
|
|
|
|
|
|
def run(self):
|
2010-05-04 15:03:32 +00:00
|
|
|
with self.api.lock:
|
2010-05-04 18:32:43 +00:00
|
|
|
try:
|
|
|
|
# username/Home entries need to load the appropriate Home feed
|
|
|
|
if self.list_name == self.username + '/Home':
|
|
|
|
statuses = self.api.GetHomeTimeline(count=self.num_entries)
|
|
|
|
|
|
|
|
# For @username, check if it is one of our usernames, or
|
|
|
|
# just needs to be searched on
|
|
|
|
elif self.list_name == '@' + self.username:
|
|
|
|
statuses = self.api.GetMentions(count=self.num_entries)
|
|
|
|
elif re.match('@', self.list_name):
|
|
|
|
statuses = results_to_statuses(self.api.Search(self.list_name, rpp=self.num_entries))
|
|
|
|
|
|
|
|
# Direct Messages should match like /Home, above
|
|
|
|
elif self.list_name == self.username + '/Direct Messages':
|
|
|
|
statuses = dms_to_statuses(self.api.GetDirectMessages())
|
|
|
|
|
|
|
|
# User lookups go straight to the user
|
|
|
|
elif re.match(r'user: ', self.list_name):
|
|
|
|
statuses = self.api.GetUserTimeline(re.sub(r'^user: ', r'', self.list_name), count=self.num_entries)
|
|
|
|
|
|
|
|
# Lists load the appropriate list from the appropriate account
|
|
|
|
elif re.match(r'list: ', self.list_name):
|
|
|
|
real_list = re.sub(r'list: .*/(.*)', r'\1', self.list_name)
|
|
|
|
statuses = self.api.GetListStatuses(real_list, per_page=self.num_entries)
|
|
|
|
|
|
|
|
# Everything else is a straight search
|
|
|
|
else:
|
|
|
|
statuses = results_to_statuses(self.api.Search(self.list_name, rpp=self.num_entries))
|
|
|
|
|
2010-05-06 17:18:02 +00:00
|
|
|
except (HTTPError, URLError):
|
2010-05-04 18:32:43 +00:00
|
|
|
statuses = None
|
2010-04-22 18:57:27 +00:00
|
|
|
|
2010-05-17 21:31:42 +00:00
|
|
|
# For each user id present, populate the AvCache with an image
|
|
|
|
if statuses:
|
|
|
|
for status in statuses:
|
|
|
|
add_to_cache(status)
|
|
|
|
|
2010-04-22 19:13:49 +00:00
|
|
|
gtk.gdk.threads_enter()
|
|
|
|
try:
|
|
|
|
self.pane.update_window(statuses)
|
|
|
|
finally:
|
|
|
|
gtk.gdk.threads_leave()
|
2010-04-22 18:57:27 +00:00
|
|
|
|
|
|
|
|
|
|
|
### End class GetTweets
|
|
|
|
|
|
|
|
|
|
|
|
|
2010-05-06 17:18:02 +00:00
|
|
|
class GetSingleTweet(ApiThread):
|
2010-05-06 22:00:12 +00:00
|
|
|
def __init__(self, api, pane, single_tweet):
|
2010-05-06 17:18:02 +00:00
|
|
|
ApiThread.__init__(self, api)
|
2010-04-27 20:21:21 +00:00
|
|
|
self.pane = pane
|
|
|
|
self.single_tweet = single_tweet
|
|
|
|
|
|
|
|
|
|
|
|
def run(self):
|
|
|
|
statuses = []
|
2010-05-04 15:03:32 +00:00
|
|
|
with self.api.lock:
|
2010-05-04 18:32:43 +00:00
|
|
|
try:
|
|
|
|
statuses.append(self.api.GetStatus(self.single_tweet))
|
2010-05-06 17:18:02 +00:00
|
|
|
except (HTTPError, URLError):
|
2010-05-04 18:32:43 +00:00
|
|
|
statuses = None
|
2010-04-27 20:21:21 +00:00
|
|
|
|
2010-05-17 21:31:42 +00:00
|
|
|
# In case we've never seen this user, grab their profile image
|
|
|
|
for status in statuses:
|
|
|
|
add_to_cache(status)
|
|
|
|
|
2010-04-27 20:21:21 +00:00
|
|
|
gtk.gdk.threads_enter()
|
|
|
|
try:
|
|
|
|
self.pane.update_window(statuses)
|
|
|
|
finally:
|
|
|
|
gtk.gdk.threads_leave()
|
|
|
|
|
|
|
|
|
|
|
|
### End class GetSingleTweet
|
|
|
|
|
|
|
|
|
|
|
|
|
2010-05-06 17:18:02 +00:00
|
|
|
class GetFollowing(ApiThread):
|
2010-04-27 20:21:21 +00:00
|
|
|
def __init__(self, api, pane, user):
|
2010-05-06 17:18:02 +00:00
|
|
|
ApiThread.__init__(self, api)
|
2010-04-27 20:21:21 +00:00
|
|
|
self.pane = pane
|
|
|
|
self.user = user
|
|
|
|
|
|
|
|
|
|
|
|
def run(self):
|
|
|
|
screen_name = re.sub('user: ', '', self.user)
|
|
|
|
|
|
|
|
try:
|
2010-05-04 15:03:32 +00:00
|
|
|
with self.api.lock:
|
|
|
|
relationship = self.api.ShowFriendships(target_screen_name=screen_name)
|
2010-04-27 20:21:21 +00:00
|
|
|
following = relationship.source.following
|
2010-05-06 17:18:02 +00:00
|
|
|
except (HTTPError, URLError):
|
2010-04-27 20:21:21 +00:00
|
|
|
following = false
|
|
|
|
|
|
|
|
self.pane.set_following(following)
|
|
|
|
|
|
|
|
### End class GetFollowing
|
|
|
|
|
|
|
|
|
|
|
|
|
2010-05-06 17:18:02 +00:00
|
|
|
class GetVerified(ApiThread):
|
2010-04-27 20:21:21 +00:00
|
|
|
def __init__(self, api, pane, user):
|
2010-05-06 17:18:02 +00:00
|
|
|
ApiThread.__init__(self, api)
|
2010-04-27 20:21:21 +00:00
|
|
|
self.pane = pane
|
|
|
|
self.user = user
|
|
|
|
|
|
|
|
|
|
|
|
def run(self):
|
|
|
|
screen_name = re.sub('user: ', '', self.user)
|
|
|
|
|
|
|
|
try:
|
2010-05-04 15:03:32 +00:00
|
|
|
with self.api.lock:
|
|
|
|
user = self.api.GetUser(screen_name)
|
2010-04-27 20:21:21 +00:00
|
|
|
verified = user.verified
|
2010-05-06 17:18:02 +00:00
|
|
|
except (HTTPError, URLError):
|
2010-04-27 20:21:21 +00:00
|
|
|
verified = false
|
|
|
|
|
|
|
|
self.pane.set_verified(verified)
|
|
|
|
|
|
|
|
### End class GetVerified
|
|
|
|
|
|
|
|
|
2010-05-11 21:38:26 +00:00
|
|
|
class GetUserLists(ApiThread):
|
|
|
|
def __init__(self, api):
|
|
|
|
ApiThread.__init__(self, api)
|
|
|
|
self.sig_proxy = SigProxy()
|
|
|
|
|
|
|
|
|
|
|
|
def run(self):
|
|
|
|
lists = []
|
|
|
|
done = False
|
|
|
|
|
|
|
|
while not done:
|
|
|
|
done = True
|
|
|
|
try:
|
|
|
|
with self.api.lock:
|
|
|
|
lists = self.api.GetUserLists()
|
|
|
|
except (HTTPError, URLError):
|
|
|
|
done = False
|
|
|
|
|
|
|
|
self.sig_proxy.emit('lists-ready', lists, None)
|
|
|
|
|
|
|
|
### End class GetUserLists
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class SigProxy(gtk.Alignment):
|
|
|
|
|
|
|
|
def __init__(self):
|
|
|
|
gtk.Alignment.__init__(self)
|
|
|
|
|
|
|
|
# End class SigProxy
|
|
|
|
|
|
|
|
gobject.signal_new("lists-ready", SigProxy,
|
|
|
|
gobject.SIGNAL_RUN_LAST,
|
|
|
|
gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,gobject.TYPE_PYOBJECT))
|
|
|
|
|
2010-04-27 20:21:21 +00:00
|
|
|
|
2010-04-22 18:57:27 +00:00
|
|
|
# We use these classes to emulate a Status object when we need
|
|
|
|
# one to be built out of something else.
|
|
|
|
class Status():
|
|
|
|
def __init__(self):
|
|
|
|
self.user = User()
|
|
|
|
self.id = None
|
|
|
|
self.created_at = None
|
|
|
|
self.in_reply_to_screen_name = None
|
|
|
|
self.in_reply_to_status_id = None
|
|
|
|
|
|
|
|
|
|
|
|
class User():
|
|
|
|
def __init__(self):
|
|
|
|
self.screen_name = None
|
|
|
|
self.name = None
|
|
|
|
|
|
|
|
|
|
|
|
# To keep things simple elsewhere and improve code reuse
|
|
|
|
# we'll build a list of home-cooked Status objects out of results.
|
|
|
|
# Why is this even necessary?
|
|
|
|
# Why can't we have more consistency out of the Twitter API?
|
|
|
|
def results_to_statuses(results):
|
|
|
|
statuses = []
|
|
|
|
for result in results.results:
|
|
|
|
status = Status()
|
|
|
|
status.id = result.id
|
|
|
|
status.user = User()
|
|
|
|
status.user.screen_name = result.from_user
|
|
|
|
status.user.name = ""
|
|
|
|
status.in_reply_to_screen_name = result.to_user
|
|
|
|
# The Twitter Search API has different timestamps than the
|
|
|
|
# REST API... balls
|
|
|
|
# fixme:
|
|
|
|
# Gotta be a cleaner way to do this, but I can't think of it
|
|
|
|
# right now
|
|
|
|
created_at = re.sub(',', '', result.created_at)
|
|
|
|
created_split = re.split(' ', created_at)
|
|
|
|
status.created_at = created_split[0] + ' ' + created_split[2] + ' ' + created_split[1] + ' ' + created_split[4] + ' ' + created_split[5] + ' ' + created_split[3]
|
|
|
|
status.text = result.text
|
|
|
|
statuses.append(status)
|
|
|
|
return statuses
|
|
|
|
|
|
|
|
|
|
|
|
def dms_to_statuses(direct_messages):
|
|
|
|
statuses = []
|
|
|
|
for dm in direct_messages:
|
|
|
|
status = Status()
|
|
|
|
status.id = dm.id
|
|
|
|
status.user = User()
|
|
|
|
status.user.screen_name = dm.sender_screen_name
|
|
|
|
status.user.name = dm.sender.name
|
|
|
|
status.created_at = dm.created_at
|
|
|
|
status.text = dm.text
|
|
|
|
statuses.append(status)
|
|
|
|
return statuses
|
2010-05-17 21:31:42 +00:00
|
|
|
|
|
|
|
|
|
|
|
def add_to_cache(status):
|
|
|
|
with AvCache().lock:
|
|
|
|
hit = AvCache().map.has_key(status.user.screen_name)
|
|
|
|
if not hit:
|
|
|
|
try:
|
|
|
|
url = urlopen(status.user.profile.image_url)
|
|
|
|
data = url.read()
|
|
|
|
loader = gtk.gdk.PixbufLoader()
|
|
|
|
loader.write(str(data))
|
|
|
|
image = loader.get_pixbuf()
|
|
|
|
loader.close()
|
|
|
|
|
|
|
|
with AvCache().lock:
|
|
|
|
AvCache().map[status.user.screen_name] = image
|
|
|
|
except URLError:
|
|
|
|
pass # Nothing needs be done, just catch & ignore
|