64f64042e49b02672a259196fd4aecf2d18099f0
[gwibber.git] / gwibber / microblog / twitter.py
1
2 """
3
4 Twitter interface for Gwibber
5 SegPhault (Ryan Paul) - 12/22/2007
6
7 """
8
9 from . import can, support
10 import urllib2, urllib, base64, re, simplejson
11 import gettext
12 _ = gettext.lgettext
13
14
15 PROTOCOL_INFO = {
16   "name": "Twitter",
17   "version": 0.1,
18   
19   "config": [
20     "private:password",
21     "username",
22     "message_color",
23     "receive_enabled",
24     "send_enabled",
25     "search_enabled",
26     "receive_count",
27   ],
28
29   "features": [
30     can.SEND,
31     can.RECEIVE,
32     can.SEARCH,
33     can.TAG,
34     can.REPLY,
35     can.RESPONSES,
36     can.DELETE,
37     can.RETWEET,
38     can.LIKE,
39     #can.THREAD,
40     can.THREAD_REPLY,
41     can.SEARCH_URL,
42     can.USER_MESSAGES,
43   ],
44 }
45
46 NICK_PARSE = re.compile("\B@([A-Za-z0-9_]+|@[A-Za-z0-9_]$)")
47 HASH_PARSE = re.compile("\B#([A-Za-z0-9_\-]+|@[A-Za-z0-9_\-]$)")
48
49 class Message:
50   def __init__(self, client, data):
51    try:
52     self.client = client
53     self.account = client.account
54     self.protocol = client.account["protocol"]
55     self.username = client.account["username"]
56     self.bgcolor = "message_color"
57     self.id = data["id"] or ''
58     self.time = support.parse_time(data["created_at"])
59     self.is_private  = False
60
61     self.can_retweet = True
62
63     try:
64       self.source = data["source"]
65     except: pass
66
67     if "user" in data:
68       user = data["user"]
69       self.reply_nick = data["in_reply_to_screen_name"]
70       self.reply_url = "https://twitter.com/%s/statuses/%s" % (data["in_reply_to_screen_name"], data["in_reply_to_status_id"])
71       self.reply_id = data["in_reply_to_status_id"]
72     elif "sender" in data:
73       user = data["sender"]
74       self.reply_nick = None
75       self.reply_url = None
76     elif "name" in data:
77       user = data
78
79     self.sender = user["name"]
80     self.sender_nick = user["screen_name"]
81     self.sender_id = user["id"]
82     self.sender_location = user["location"]
83     self.sender_followers_count = user["followers_count"]
84     self.image = user["profile_image_url"]
85     self.url = "https://twitter.com/%s/statuses/%s" % (user["screen_name"], data["id"])
86     self.profile_url = "gwibber:user/%s/%s" % (self.account.id, user["screen_name"])
87     self.external_profile_url = "https://twitter.com/%s" % user["screen_name"]
88
89     if "text" in data:
90       self.text = data["text"]
91       self.html_string = '<span class="text">%s</span>' % \
92           HASH_PARSE.sub('#<a class="inlinehash" href="gwibber:tag/\\1">\\1</a>',
93           NICK_PARSE.sub('@<a class="inlinenick" href="gwibber:user/'+self.account.id+'/\\1">\\1</a>',
94           support.linkify(self.text)))
95       self.is_reply = re.compile("@%s[\W]+|@%s$" % (self.username, self.username)).search(self.text)
96       self.reply_nick = ''
97       self.reply_url = ''
98     else:
99       # if reached a protected gwibber:user tab then do some things differently
100       if "name" in data:
101         self.url = self.profile_url = self.external_profile_url = "https://twitter.com/%s" % data["screen_name"]
102         self.is_reply = False
103         if data["protected"] == True:
104           self.text = _("This user has protected their updates.") + ' ' + _("You need to send a request before you can view this person's timeline.") + ' ' + _("Send request...")
105           self.html_string = '<p><b>' + _("This user has protected their updates.") + '</b><p>' + _("You need to send a request before you can view this person's timeline.") + '<p><a href="' + self.url + '">' + _("Send request...") + '</a>'
106         else:
107           self.text = self.html_string = ''
108
109     if "in_reply_to_screen_name" in data and "in_reply_to_status_id" in data and data["in_reply_to_status_id"]:
110       self.reply_nick = data["in_reply_to_screen_name"]
111       self.reply_url = "https://twitter.com/%s/statuses/%s" % (self.reply_nick, data["in_reply_to_status_id"])
112    except Exception:
113     from traceback import format_exc
114     print(format_exc())
115
116 class SearchResult:
117   def __init__(self, client, data, query = None):
118     self.client = client
119     self.account = client.account
120     self.protocol = client.account["protocol"]
121     self.username = client.account["username"]
122     self.sender = data["from_user"]
123     self.sender_nick = data["from_user"]
124     self.sender_id = data["from_user_id"]
125     self.time = support.parse_time(data["created_at"])
126     self.text = data["text"]
127     self.id = data["id"]
128     self.image = data["profile_image_url"]
129     self.bgcolor = "message_color"
130     self.url = "https://twitter.com/%s/statuses/%s" % (data["from_user"], data["id"])
131     self.profile_url = "gwibber:user/%s/%s" % (self.account.id, data["from_user"])
132     self.external_profile_url = "https://twitter.com/%s" % data["from_user"]
133     self.can_retweet = True
134
135     if query: html = support.highlight_search_results(self.text, query)
136     else: html = self.text
137     
138     self.html_string = '<span class="text">%s</span>' % \
139       HASH_PARSE.sub('#<a class="inlinehash" href="gwibber:tag/\\1">\\1</a>',
140       NICK_PARSE.sub('@<a class="inlinenick" href="gwibber:user/'+self.account.id+'/\\1">\\1</a>',
141         support.linkify(self.text)))
142
143     self.is_reply = re.compile("@%s[\W]+|@%s$" % (self.username, self.username)).search(self.text) 
144
145 class Client:
146   def __init__(self, acct):
147     self.account = acct
148
149   def send_enabled(self):
150     return self.account["send_enabled"] and \
151       self.account["username"] != None and \
152       self.account["private:password"] != None
153
154   def receive_enabled(self):
155     return self.account["receive_enabled"] and \
156       self.account["username"] != None and \
157       self.account["private:password"] != None
158
159   def get_auth(self):
160     return "Basic %s" % base64.encodestring(
161       ("%s:%s" % (self.account["username"], self.account["private:password"]))).strip()
162
163   def connect(self, url, data = None):
164     return urllib2.urlopen(urllib2.Request(
165       url, data, headers = {"Authorization": self.get_auth()})).read()
166
167   def get_messages(self):
168     return simplejson.loads(self.connect(
169       "https://twitter.com/statuses/friends_timeline.json" +'?'+
170       urllib.urlencode({"count": self.account["receive_count"] or "20"})))
171
172   def get_user_messages(self, screen_name):
173     try:
174       return simplejson.loads(self.connect(
175         "https://twitter.com/statuses/user_timeline/"+ screen_name + ".json" +'?'+
176           urllib.urlencode({"count": self.account["receive_count"] or "20"})))
177     except Exception:
178       profile = [simplejson.loads(self.connect(
179         "https://twitter.com/users/show/"+ screen_name +".json"))]
180       return profile
181
182   def get_replies(self):
183     return simplejson.loads(self.connect(
184       "https://twitter.com/statuses/replies.json" +'?'+
185         urllib.urlencode({"count": self.account["receive_count"] or "20"})))
186
187   def get_direct_messages(self):
188     return simplejson.loads(self.connect(
189       "https://twitter.com/direct_messages.json"))
190
191   def get_search_data(self, query):
192     return simplejson.loads(urllib2.urlopen(
193       urllib2.Request("http://search.twitter.com/search.json",
194         urllib.urlencode({"q": query}))).read())
195
196   def search(self, query):
197     for data in self.get_search_data(query)["results"]:
198       yield SearchResult(self, data, query)
199
200   def search_url(self, query):
201     urls = support.unshorten_url(query)
202     for data in self.get_search_data(" OR ".join(urls))["results"]:
203       if any(item in data["text"] for item in urls):
204         yield SearchResult(self, data, query)
205
206   def tag(self, query):
207     for data in self.get_search_data("#%s" % query)["results"]:
208       yield SearchResult(self, data, "#%s" % query)
209
210   def responses(self):
211     for data in self.get_replies():
212       yield Message(self, data)
213
214     for data in self.get_direct_messages():
215       m = Message(self, data)
216       m.is_private = True
217       yield m
218
219   def receive(self):
220     for data in self.get_messages():
221       yield Message(self, data)
222
223   def user_messages(self, screen_name):
224     for data in self.get_user_messages(screen_name):
225       yield Message(self, data)
226
227   def delete(self, message):
228     return simplejson.loads(self.connect(
229       "https://twitter.com/statuses/destroy/%s.json" % message.id, {}))
230
231   def like(self, message):
232     return simplejson.loads(self.connect(
233       "https://twitter.com/favorites/create/%s.json" % message.id, {}))
234
235   def send(self, message):
236     data = simplejson.loads(self.connect(
237       "https://twitter.com/statuses/update.json",
238         urllib.urlencode({"status":message, "source": "gwibbernet"})))
239     return Message(self, data)
240
241   def send_thread(self, message, target):
242     data = simplejson.loads(self.connect(
243       "https://twitter.com/statuses/update.json",
244         urllib.urlencode({"status":message,
245           "in_reply_to_status_id":target.id, "source": "gwibbernet"})))
246     return Message(self, data)
247
248   #def amfollowing(self, id):
249   #  data = simplejson.loads(self.connect(
250   #      "https://twitter.com/friendships/exists.json",
251   #      urllib.urlencode( { "id" : id ,} )))
252
253   def follow(self, id):
254     data = simplejson.loads(self.connect(
255         "https://twitter.com/friendships/create/%s.json" % id,
256         urllib.urlencode( { } )))
257     return True
258
259   def unfollow(self, id):
260     data = simplejson.loads(self.connect(
261         "https://twitter.com/friendships/destroy/%s.json" % id,
262         urllib.urlencode( { } )))
263     return True
264
265
266