Attachment 'TwitterStock_Version7.py'
Download 1 import logging, dbus, threading, gobject, time, gtk
2 import urllib
3 import hippo
4 import twyt.twitter as twyt
5 import twyt.data as twyt_data
6 from bigboard.stock import Stock
7 from bigboard.slideout import Slideout
8 import bigboard.accounts_dialog as accounts_dialog
9 import bigboard.libbig.gutil as gutil
10 from bigboard.libbig.logutil import log_except
11 import bigboard.libbig as libbig
12 from bigboard.libbig.http import AsyncHTTPFetcherWithAuth
13 from bigboard.big_widgets import PrelightingCanvasBox, CanvasVBox, CanvasHBox, Header, CanvasURLImage
14
15 _logger = logging.getLogger('bigboard.stocks.TwitterStock')
16
17 LOGIN_TO_TWITTER_STRING = "Login to Twitter"
18 FAILED_TO_LOGIN_STRING = "Failed to login."
19
20 TWITTER_KIND = "twitter"
21
22 TWITTER_STATUS_MAX_LENGTH = 140
23
24 FRIENDS_UPDATES_LIMIT = 3
25
26 def make_update_string(result, details):
27 string = ""
28 try:
29 if details:
30 utc = time.mktime(time.strptime(
31 "%s UTC" % result.created_at, "%a %b %d %H:%M:%S +0000 %Y %Z"))
32 stamp = time.strftime("%a %b %d %H:%M:%S %Y", time.localtime(utc))
33
34 string = "%s (%s via %s)" % (result.text, stamp, result.source)
35 else:
36 string = "%s" % (result.text)
37 except AttributeError, e:
38 _logger.debug("AttributeError when parsing result %s" % e)
39
40 return string
41
42 class UpdateSlideout(Slideout):
43 def __init__(self, result):
44 super(UpdateSlideout, self).__init__()
45 vbox = CanvasVBox(border_color=0x0000000ff, spacing=4, box_width=320)
46 self.get_root().append(vbox)
47 self.__header = Header(topborder=False)
48 # TODO: for some reason, specifying an additional class with the color did not set the color
49 text = hippo.CanvasText(classes='header', color=0x00AABBFF, text=result.user.screen_name, xalign=hippo.ALIGNMENT_START, padding=2)
50 self.__header.append(text, hippo.PACK_EXPAND)
51 vbox.append(self.__header)
52
53 self.id = result.id
54
55 hbox = CanvasHBox(spacing=4)
56
57 image = CanvasURLImage(result.user.profile_image_url, padding=2)
58 status = hippo.CanvasText(text=make_update_string(result, True), xalign=hippo.ALIGNMENT_START,
59 size_mode=hippo.CANVAS_SIZE_WRAP_WORD, padding=2)
60 hbox.append(image)
61 hbox.append(status, hippo.PACK_EXPAND)
62 vbox.append(hbox)
63
64 class CheckTwitterTask(libbig.polling.Task):
65 def __init__(self, twitter_stock):
66 libbig.polling.Task.__init__(self, 1000 * 120, initial_interval=0)
67 self.__twitter_stock = twitter_stock
68
69 def do_periodic_task(self):
70 self.__twitter_stock.do_periodic_updates()
71
72 class TwitterStock(Stock):
73
74 def __init__(self, *args, **kwargs):
75 Stock.__init__(self, *args, **kwargs)
76 _logger.debug("Hello Twitter")
77 self.__box = hippo.CanvasBox(orientation=hippo.ORIENTATION_VERTICAL)
78
79 self.__login_button = hippo.CanvasButton(text=LOGIN_TO_TWITTER_STRING)
80 self.__login_button.connect('activated', lambda button: self.__open_login_dialog())
81 self.__box.append(self.__login_button)
82
83 self.__twitter_status_counter_text = hippo.CanvasText(text=str(TWITTER_STATUS_MAX_LENGTH))
84
85 self.__twitter_status_input = hippo.CanvasEntry()
86 self.__twitter_status_input.get_property("widget").set_max_length(TWITTER_STATUS_MAX_LENGTH)
87 self.__twitter_status_input.connect("notify::text", self.__on_status_edited)
88 self.__twitter_status_input.connect("key-press-event", self.__on_status_submitted)
89
90 self._add_more_button(self.__on_more_button)
91
92 self.__friends_updates_box = hippo.CanvasBox(orientation=hippo.ORIENTATION_VERTICAL)
93
94 self.__slideout = None
95 self.__last_slideout_event_time = None
96
97 self.__twitter = twyt.Twitter()
98 self.__twitter.set_user_agent("gnome")
99
100 self.__fetcher = AsyncHTTPFetcherWithAuth()
101
102 self.__check_twitter_task = CheckTwitterTask(self)
103
104 # even though the account system can theoretically return multiple Twitter accounts, this stock
105 # only supports one -- the last one that was added
106 self.__twitter_account = None
107 self.__twitter_account_changed_signal_match = None
108
109 try:
110 onlineaccounts_proxy = dbus.SessionBus().get_object('org.gnome.OnlineAccounts', '/onlineaccounts')
111 except dbus.DBusException, e:
112 _logger.error("onlineaccounts DBus service not available, can't get twitter accounts")
113 return
114
115 all_twitter_account_paths = onlineaccounts_proxy.GetEnabledAccountsWithKinds([TWITTER_KIND])
116 for a_path in all_twitter_account_paths:
117 self.__on_account_added(a_path)
118
119 self.__connections = gutil.DisconnectSet()
120
121 id = onlineaccounts_proxy.connect_to_signal('AccountEnabled', self.__on_account_added)
122 self.__connections.add(onlineaccounts_proxy, id)
123 id = onlineaccounts_proxy.connect_to_signal('AccountDisabled', self.__on_account_removed)
124 self.__connections.add(onlineaccounts_proxy, id)
125
126 def _on_delisted(self):
127 if self.__twitter_account:
128 self.__on_account_removed(self.__twitter_account.GetObjectPath())
129 self.__connections.disconnect_all()
130
131 def get_content(self, size):
132 return self.__box
133
134 @log_except(_logger)
135 def __on_account_added(self, acct_path):
136 try:
137 _logger.debug("acct_path is %s" % acct_path)
138 acct = dbus.SessionBus().get_object('org.gnome.OnlineAccounts', acct_path)
139 except dbus.DBusException, e:
140 _logger.error("onlineaccount for path %s was not found" % acct_path)
141 return
142
143 if acct.GetKind() != TWITTER_KIND:
144 return
145
146 _logger.debug("in __on_account_added for %s %s" % (str(acct), acct.GetUsername()))
147
148 if self.__twitter_account:
149 self.__on_account_removed(self.__twitter_account.GetObjectPath())
150
151 self.__twitter_account = acct
152 self.__twitter_account_changed_signal_match = \
153 self.__twitter_account.connect_to_signal('Changed', self.__on_twitter_account_changed)
154 self.__on_twitter_account_changed()
155
156 @log_except(_logger)
157 def __on_account_removed(self, acct_path):
158 _logger.debug("in __on_account_removed")
159 if self.__twitter_account and self.__twitter_account.GetObjectPath() == acct_path:
160 _logger.debug("in __on_account_removed for %s", acct_path)
161 self.__twitter_account_changed_signal_match.remove()
162 self.__twitter_account_changed_signal_match = None
163 self.__twitter_account = None
164 self.__check_twitter_task.stop()
165 self.__box.remove_all()
166 self.__box.append(self.__login_button)
167
168 @log_except(_logger)
169 def __on_twitter_account_changed(self):
170 _logger.debug("will change stuff")
171 username = self.__twitter_account.GetUsername()
172 password = self.__twitter_account.GetPassword()
173 if password != "":
174 self.__box.remove_all()
175 checking = hippo.CanvasText(text="Checking credentials for " + username + "...",
176 size_mode=hippo.CANVAS_SIZE_WRAP_WORD, classes="info")
177 self.__box.append(checking)
178 self.__fetcher.fetch("http://twitter.com/account/verify_credentials.json",
179 username, password,
180 lambda url, data: self.__on_twitter_response(url, data, username, password),
181 lambda url, resp: self.__on_twitter_error(url, resp, username, password),
182 lambda url: self.__on_auth_failed(username, password),
183 data = urllib.urlencode({}))
184 else:
185 self.__add_login_button()
186
187 def __on_status_edited(self, input, param_spec):
188 self.__twitter_status_counter_text.set_property("text", str(TWITTER_STATUS_MAX_LENGTH - len(self.__twitter_status_input.get_property("text"))))
189
190 def __on_status_submitted(self, entry, event):
191 if event.key == hippo.KEY_RETURN:
192 status = self.__twitter_status_input.get_property("text").strip()
193 if status != "":
194 _logger.debug("will send status: %s" % status)
195 username = self.__twitter_account.GetUsername()
196 password = self.__twitter_account.GetPassword()
197 self.__fetcher.fetch("http://twitter.com/statuses/update.json",
198 username, password,
199 lambda url, data: self.__on_twitter_status_response(url, data, username, password),
200 lambda url, resp: self.__on_twitter_error(url, resp, username, password),
201 lambda url: self.__on_auth_failed(username, password),
202 data = urllib.urlencode({"status":status}))
203
204 self.__twitter_status_input.set_property("text", "")
205
206 def __on_twitter_response(self, url, data, username, password):
207 _logger.debug("Authentication must be good")
208 if self.__same_credentials(username, password):
209 self.__box.remove_all()
210 hello_message = hippo.CanvasText(text="Update status for " + self.__twitter_account.GetUsername() + ":",
211 size_mode=hippo.CANVAS_SIZE_WRAP_WORD)
212 self.__box.append(hello_message)
213 self.__box.append(self.__twitter_status_counter_text)
214 self.__box.append(self.__twitter_status_input)
215
216 self.__box.append(self.__friends_updates_box)
217
218 loading = hippo.CanvasText(text="Loading friends' updates...",
219 size_mode=hippo.CANVAS_SIZE_WRAP_WORD, classes="info")
220 self.__friends_updates_box.append(loading)
221
222 self.__check_twitter_task.start()
223
224 def do_periodic_updates(self):
225 t = threading.Thread(target=self.__get_friends_updates,
226 kwargs={"username": self.__twitter_account.GetUsername(), "password": self.__twitter_account.GetPassword()},
227 name="Twitter Updates Request")
228 t.setDaemon(True)
229 t.start()
230
231 def __get_friends_updates(self, username, password):
232 self.__twitter.set_auth(username, password)
233 answer = self.__twitter.status_friends_timeline()
234 results = twyt_data.StatusList(answer)
235 gobject.idle_add(self.__process_friends_updates, results, username, password)
236
237 def __process_friends_updates(self, results, username, password):
238 if self.__same_credentials(username, password):
239 self.__friends_updates_box.remove_all()
240 i = 0
241 for result in results:
242 if i >= FRIENDS_UPDATES_LIMIT: break
243
244 box = PrelightingCanvasBox(orientation=hippo.ORIENTATION_VERTICAL)
245
246 box.connect("button-press-event", self.create_update_slideout, result)
247 self.__friends_updates_box.append(box)
248 name = hippo.CanvasText(text=result.user.screen_name, xalign=hippo.ALIGNMENT_START, classes="friend-name")
249 status = hippo.CanvasText(text=make_update_string(result, False), xalign=hippo.ALIGNMENT_START,
250 size_mode=hippo.CANVAS_SIZE_WRAP_WORD)
251 box.append(name)
252 box.append(status)
253 i += 1
254
255 _logger.debug("%s" % result)
256 else:
257 _logger.debug("the credentials changed while we were getting friends updates from Twitter")
258
259 def __on_twitter_status_response(self, url, data, username, password):
260 _logger.debug("Twitter status update went fine")
261
262 def __on_twitter_error(self, url, resp, username, password):
263 status = resp and str(resp.status) or "No response"
264 _logger.debug("There was a Twitter error. Response: %s" % status)
265 if self.__same_credentials(username, password):
266 self.__add_login_button(True)
267
268 def __on_auth_failed(self, username, password):
269 _logger.debug("There was an authentication failure")
270 if self.__same_credentials(username, password):
271 self.__add_login_button(True)
272
273 def __same_credentials(self, username, password):
274 return self.__twitter_account and self.__twitter_account.GetUsername() == username and self.__twitter_account.GetPassword() == password
275
276 def __add_login_button(self, failed_to_login = False):
277 self.__check_twitter_task.stop()
278 self.__box.remove_all()
279 if failed_to_login:
280 error = hippo.CanvasText(text=FAILED_TO_LOGIN_STRING, size_mode=hippo.CANVAS_SIZE_WRAP_WORD, classes="error")
281 self.__box.append(error)
282 hello_message = hippo.CanvasText(text="Hello Twitter User " + self.__twitter_account.GetUsername(),
283 size_mode=hippo.CANVAS_SIZE_WRAP_WORD)
284 self.__box.append(hello_message)
285 self.__box.append(self.__login_button)
286
287 def __open_login_dialog(self):
288 accounts_dialog.open_dialog(["twitter"])
289
290 def __on_more_button(self):
291 if self.__twitter_account:
292 url = "http://twitter.com/home"
293 else:
294 url = "http://twitter.com"
295 libbig.show_url(url)
296
297 def create_update_slideout(self, widget, hippo_event, result):
298 if self.__slideout and self.__slideout.id == result.id and self.__last_slideout_event_time == gtk.get_current_event_time():
299 self.__slideout = None
300 return
301 self.__slideout = UpdateSlideout(result)
302 def on_slideout_close(s, action_taken):
303 self.__last_slideout_event_time = gtk.get_current_event_time()
304 if action_taken:
305 self._panel.action_taken()
306 s.destroy()
307 self._set_active(False)
308 self._set_active(True)
309 self.__slideout.connect('close', on_slideout_close)
310 y = widget.get_context().translate_to_screen(widget)[1]
311 if not self.__slideout.slideout_from(204, y):
312 self.__slideout.destroy()
313 self.__slideout = None
314 self._set_active(False)
315 return
Attached Files
To refer to attachments on a page, use attachment:filename, as shown below in the list of files. Do NOT use the URL of the [get] link, since this is subject to change and can break easily.You are not allowed to attach a file to this page.