Merge lp:~sazius/heybuddy/minor_fixes into lp:heybuddy

Proposed by Mats Sjöberg
Status: Merged
Merged at revision: 347
Proposed branch: lp:~sazius/heybuddy/minor_fixes
Merge into: lp:heybuddy
Diff against target: 226 lines (+84/-1)
5 files modified
Communicator.py (+7/-0)
Dent.py (+4/-0)
SettingsPage.py (+11/-0)
XMLProcessor.py (+23/-0)
heybuddy.py (+39/-1)
To merge this branch: bzr merge lp:~sazius/heybuddy/minor_fixes
Reviewer Review Type Date Requested Status
Mats Sjöberg (community) Needs Resubmitting
jezra Pending
Review via email: mp+180620@code.launchpad.net

Description of the change

When your StatusNet instance has a shorter character limit than someone posting remotely the posts will get truncated. This is very annoying. These posts, however contain an "attachment" in the XML which is an URL to the full text. This patch makes heybuddy fetch that for truncated posts.

(A minor fix is also that an URL in parentheses, e.g. "(http://foo/bar)" will no longer have the ")" erroneously included in the link.)

To post a comment you must log in.
lp:~sazius/heybuddy/minor_fixes updated
349. By Mats Sjöberg

Added a check if attachment size is a digit to avoid annoying error messages.

Revision history for this message
jezra (jezra) wrote :

Uh oh, this sounds like a "feature". Because this requires adding a new module and making an extra http request, there needs to be a preference option to enable this (it should be disabled by default).

Revision history for this message
Mats Sjöberg (sazius) wrote :

> Uh oh, this sounds like a "feature". Because this requires adding a new module
> and making an extra http request, there needs to be a preference option to
> enable this (it should be disabled by default).

You might argue if it is _really_ a new "feature" or not... :-) But yeah, maybe it's best to have it a preference option since it does add HTTP traffic. I'll add that and make a new merge request when done.

lp:~sazius/heybuddy/minor_fixes updated
350. By Mats Sjöberg

Removed debugging output.

351. By Mats Sjöberg <email address hidden>

Dent expansion now with 100% more configurability!

Revision history for this message
Mats Sjöberg (sazius) wrote :

OK, I now added a config check box, disabled by default. Also please note, that I'm not a Python and even less PyGTK expert so the code is far from perfect.

review: Needs Resubmitting
Revision history for this message
jezra (jezra) wrote :

Did you have a tasty beverage while coding?

Revision history for this message
Mats Sjöberg (sazius) wrote :

Yes, somewhat unusually I was drinking wine. Now I'm having a local ESB :-)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'Communicator.py'
--- Communicator.py 2012-09-08 16:34:36 +0000
+++ Communicator.py 2013-08-31 16:29:00 +0000
@@ -113,6 +113,10 @@
113 gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,113 gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
114 (gobject.TYPE_STRING,gobject.TYPE_STRING,)114 (gobject.TYPE_STRING,gobject.TYPE_STRING,)
115 ),115 ),
116 'attachment-received': (
117 gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
118 (gobject.TYPE_STRING,gobject.TYPE_STRING,)
119 ),
116 }120 }
117 121
118 def __init__(self, app_name=None, cert_file=None, apiroot="http://identi.ca/api", version=""):122 def __init__(self, app_name=None, cert_file=None, apiroot="http://identi.ca/api", version=""):
@@ -422,3 +426,6 @@
422 data = {'url':longurl, 'format':'simple'}426 data = {'url':longurl, 'format':'simple'}
423 url = "http://is.gd/create.php?" + urlencode(data) 427 url = "http://is.gd/create.php?" + urlencode(data)
424 self.process_httprequest(url, 'shortened_url', longurl, True) 428 self.process_httprequest(url, 'shortened_url', longurl, True)
429
430 def fetch_attachment(self, url):
431 self.process_httprequest(url, 'attachment-received', url, True)
425432
=== modified file 'Dent.py'
--- Dent.py 2012-07-16 22:36:42 +0000
+++ Dent.py 2013-08-31 16:29:00 +0000
@@ -84,6 +84,7 @@
84 vbox = gtk.VBox(False,0)84 vbox = gtk.VBox(False,0)
85 #vbox.set_border_width(5)85 #vbox.set_border_width(5)
86 user_box.pack_start(vbox,True,True,0)86 user_box.pack_start(vbox,True,True,0)
87
87 self.text_label = gtk.Label() 88 self.text_label = gtk.Label()
88 self.text_label.set_markup( data['markup'] )89 self.text_label.set_markup( data['markup'] )
89 self.text_label.set_alignment(0,0)90 self.text_label.set_alignment(0,0)
@@ -214,6 +215,9 @@
214 215
215 vbox.pack_start(hbox1,False,False,0)216 vbox.pack_start(hbox1,False,False,0)
216 self.add(user_box)217 self.add(user_box)
218
219 def update_text(self, text):
220 self.text_label.set_markup(text)
217 221
218 def event_box_realized(self,widget):222 def event_box_realized(self,widget):
219 hand2 = gtk.gdk.Cursor(gtk.gdk.HAND2)223 hand2 = gtk.gdk.Cursor(gtk.gdk.HAND2)
220224
=== modified file 'SettingsPage.py'
--- SettingsPage.py 2012-07-16 22:36:42 +0000
+++ SettingsPage.py 2013-08-31 16:29:00 +0000
@@ -21,6 +21,10 @@
21 gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,21 gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
22 (gobject.TYPE_BOOLEAN,)22 (gobject.TYPE_BOOLEAN,)
23 ),23 ),
24 'option-expand-long-dents': (
25 gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
26 (gobject.TYPE_BOOLEAN,)
27 ),
24 'option-ctrl-enter': (28 'option-ctrl-enter': (
25 gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,29 gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
26 (gobject.TYPE_BOOLEAN,)30 (gobject.TYPE_BOOLEAN,)
@@ -148,6 +152,9 @@
148 152
149 self.api_retweet = gtk.CheckButton(_("Use retweet API instead of default redent style"))153 self.api_retweet = gtk.CheckButton(_("Use retweet API instead of default redent style"))
150 self.api_retweet.connect("toggled", self.option_toggled,'option-api-retweet')154 self.api_retweet.connect("toggled", self.option_toggled,'option-api-retweet')
155
156 self.expand_long_dents = gtk.CheckButton(_("Expand too long dents from remote servers"))
157 self.expand_long_dents.connect("toggled", self.option_toggled, 'option-expand-long-dents')
151 158
152 self.enable_notifications = gtk.CheckButton(_("Enable notifications"))159 self.enable_notifications = gtk.CheckButton(_("Enable notifications"))
153 self.enable_notifications.connect("toggled", self.option_toggled,'option-notifications')160 self.enable_notifications.connect("toggled", self.option_toggled,'option-notifications')
@@ -178,6 +185,7 @@
178 options_box.pack_start(self.disable_https,False,False,0)185 options_box.pack_start(self.disable_https,False,False,0)
179 options_box.pack_start(self.ignore_activity,False,False,0)186 options_box.pack_start(self.ignore_activity,False,False,0)
180 options_box.pack_start(self.api_retweet,False,False,0)187 options_box.pack_start(self.api_retweet,False,False,0)
188 options_box.pack_start(self.expand_long_dents,False,False,0)
181 options_box.pack_start(self.open_fullscreen,False,False,0)189 options_box.pack_start(self.open_fullscreen,False,False,0)
182190
183 #does the app have notifications?191 #does the app have notifications?
@@ -296,6 +304,9 @@
296 def set_api_retweet(self,boolean):304 def set_api_retweet(self,boolean):
297 self.api_retweet.set_active(boolean)305 self.api_retweet.set_active(boolean)
298306
307 def set_expand_long_dents(self,boolean):
308 self.expand_long_dents.set_active(boolean)
309
299 #More Code for notifications 310 #More Code for notifications
300 def set_notifications(self,boolean):311 def set_notifications(self,boolean):
301 self.enable_notifications.set_active(boolean)312 self.enable_notifications.set_active(boolean)
302313
=== modified file 'XMLProcessor.py'
--- XMLProcessor.py 2012-07-16 22:36:42 +0000
+++ XMLProcessor.py 2013-08-31 16:29:00 +0000
@@ -142,6 +142,29 @@
142 #what server is this shit from?142 #what server is this shit from?
143 profile_url = self.get_node_text(status,"statusnet:profile_url")143 profile_url = self.get_node_text(status,"statusnet:profile_url")
144 dict['profile_url'] = profile_url144 dict['profile_url'] = profile_url
145
146 # put attachments in a neat array if there are any
147 attachments = status.getElementsByTagName('attachments')
148 if attachments.length:
149 enclosures = attachments[0].getElementsByTagName('enclosure')
150 dict['attachments'] = []
151 for i in range(enclosures.length):
152 item = enclosures.item(i)
153 a = { 'mimetype': item.getAttribute('mimetype'),
154 'url': item.getAttribute('url')}
155 size_str = item.getAttribute('size')
156 if size_str.isdigit():
157 a['size'] = int(size_str)
158 dict['attachments'].append(a)
159
160 # this should say if dent is truncated... but doesn't
161 #dict['truncated'] = self.get_node_text(status, "truncated")
162 # this is what actually works (fugly hack!)
163 sn_html = self.get_node_text(status, "statusnet:html")
164 dict['truncated'] = False
165 if sn_html.find("class=\"attachment more\" title=\"Show more\"") != -1:
166 dict['truncated'] = True
167
145 # we should clean the source ( if there is one )168 # we should clean the source ( if there is one )
146 if dict['source']!=None:169 if dict['source']!=None:
147 dict['source']=self.re_source_sub.sub( '', dict['source'])170 dict['source']=self.re_source_sub.sub( '', dict['source'])
148171
=== modified file 'heybuddy.py'
--- heybuddy.py 2013-01-15 00:33:09 +0000
+++ heybuddy.py 2013-08-31 16:29:00 +0000
@@ -33,6 +33,7 @@
33import re33import re
34import time34import time
35import subprocess35import subprocess
36import HTMLParser
36#this might be on Maemo37#this might be on Maemo
37from PlatformSpecific import has_hildon,links_unavailable38from PlatformSpecific import has_hildon,links_unavailable
38#what are the custom classes that I need?39#what are the custom classes that I need?
@@ -137,7 +138,10 @@
137 self.regex_group = re.compile("(^| )!([\w-]+)")138 self.regex_group = re.compile("(^| )!([\w-]+)")
138 self.regex_user=re.compile("(^|[^A-z0-9])@(\w+)")139 self.regex_user=re.compile("(^|[^A-z0-9])@(\w+)")
139 self.regex_just_user=re.compile("@(\w+)")140 self.regex_just_user=re.compile("@(\w+)")
140 self.regex_url=re.compile("(http[s]?://.*?)(\.$|\. |\s|$)",re.IGNORECASE)141 self.regex_url=re.compile("(http[s]?://.*?)(\.$|\. |\s|\)|$)",re.IGNORECASE)
142 #regex's for parsing html attachments
143 self.regex_find_body = re.compile("\<body\>(.*)\<\/body\>")
144 self.regex_remove_tags = re.compile('<[^>]*>')
141 #get the initial dents145 #get the initial dents
142 self.initial_dents = self.conf.get('settings','initial_dents',default='20' )146 self.initial_dents = self.conf.get('settings','initial_dents',default='20' )
143 self.dent_limit = int(self.initial_dents)*2147 self.dent_limit = int(self.initial_dents)*2
@@ -187,12 +191,16 @@
187 self.comm.connect('verify_credentialsXML',self.process_verifycredentialsXML)191 self.comm.connect('verify_credentialsXML',self.process_verifycredentialsXML)
188 self.comm.connect('configXML',self.process_configXML)192 self.comm.connect('configXML',self.process_configXML)
189 self.comm.connect('shortened_url',self.shortened_url)193 self.comm.connect('shortened_url',self.shortened_url)
194 self.comm.connect('attachment-received',self.attachment_received)
190 #are we disabling https?195 #are we disabling https?
191 self.comm.set_disable_https( self.conf.get_bool_option('disable_https') )196 self.comm.set_disable_https( self.conf.get_bool_option('disable_https') )
192 #create an image cacher thingy197 #create an image cacher thingy
193 self.imagecache = ImageCache()198 self.imagecache = ImageCache()
194 self.imagecache.set_disabled( self.conf.get_bool_option('no_avatars') )199 self.imagecache.set_disabled( self.conf.get_bool_option('no_avatars') )
195 self.imagecache.connect('get-widget-image', self.get_widget_image)200 self.imagecache.connect('get-widget-image', self.get_widget_image)
201
202 # keep track of dents to notify of updated attachments
203 self.dent_map = {}
196 204
197 #Create notify-er if has pynotify205 #Create notify-er if has pynotify
198 if has_pynotify:206 if has_pynotify:
@@ -339,6 +347,8 @@
339 self.settingspage.set_no_avatars( self.options['no_avatar'] )347 self.settingspage.set_no_avatars( self.options['no_avatar'] )
340 self.options['api_retweet'] = self.conf.get_bool_option('api_retweet')348 self.options['api_retweet'] = self.conf.get_bool_option('api_retweet')
341 self.settingspage.set_api_retweet( self.options['api_retweet'] )349 self.settingspage.set_api_retweet( self.options['api_retweet'] )
350 self.options['expand_long_dents'] = self.conf.get_bool_option('expand_long_dents')
351 self.settingspage.set_expand_long_dents( self.options['expand_long_dents'] )
342 self.options['notifications']= self.conf.get_bool_option('notifications')352 self.options['notifications']= self.conf.get_bool_option('notifications')
343 self.settingspage.set_notifications( self.options['notifications'] )353 self.settingspage.set_notifications( self.options['notifications'] )
344 self.options['notify_replies']= self.conf.get_bool_option('notify_replies')354 self.options['notify_replies']= self.conf.get_bool_option('notify_replies')
@@ -355,6 +365,7 @@
355 self.settingspage.connect('option-context-backwards',self.option_changed, 'context_backwards')365 self.settingspage.connect('option-context-backwards',self.option_changed, 'context_backwards')
356 self.settingspage.connect('option-no-avatars',self.option_changed, 'no_avatars')366 self.settingspage.connect('option-no-avatars',self.option_changed, 'no_avatars')
357 self.settingspage.connect('option-api-retweet', self.option_changed, 'api_retweet')367 self.settingspage.connect('option-api-retweet', self.option_changed, 'api_retweet')
368 self.settingspage.connect('option-expand-long-dents', self.option_changed, 'expand_long_dents')
358 self.settingspage.connect('option-notifications',self.option_changed, 'notifications')369 self.settingspage.connect('option-notifications',self.option_changed, 'notifications')
359 self.settingspage.connect('option-notify-replies',self.option_changed, 'notify_replies')370 self.settingspage.connect('option-notify-replies',self.option_changed, 'notify_replies')
360 self.settingspage.connect('option-ignore-activity',self.option_changed, 'ignore_activity')371 self.settingspage.connect('option-ignore-activity',self.option_changed, 'ignore_activity')
@@ -822,6 +833,14 @@
822 dent.connect('favorite-clicked',self.favorite_clicked)833 dent.connect('favorite-clicked',self.favorite_clicked)
823 dent.connect('unfavorite-clicked',self.unfavorite_clicked)834 dent.connect('unfavorite-clicked',self.unfavorite_clicked)
824 dent.connect('open-link',self.open_link)835 dent.connect('open-link',self.open_link)
836
837 # if dent was truncated, fetch the rest which
838 # should be in an html attachment
839 if self.options['expand_long_dents'] and data['truncated'] and 'attachments' in data:
840 for a in data['attachments']:
841 if a['mimetype'] == 'text/html':
842 self.fetch_attachment(dent, a['url'])
843
825 if target_page!=self.userpage:844 if target_page!=self.userpage:
826 #get the image for this dent845 #get the image for this dent
827 self.imagecache.add_image_to_widget(data['profile_image_url'],dent)846 self.imagecache.add_image_to_widget(data['profile_image_url'],dent)
@@ -990,6 +1009,7 @@
990 if not filtered_status:1009 if not filtered_status:
991 #get the markup1010 #get the markup
992 data['markup'] = self.markup_dent_text(data['text'])1011 data['markup'] = self.markup_dent_text(data['text'])
1012
993 #did this dent connect1013 #did this dent connect
994 if not self.connect_dent(data,target_page,is_direct_dent=is_direct):1014 if not self.connect_dent(data,target_page,is_direct_dent=is_direct):
995 continue1015 continue
@@ -1365,6 +1385,24 @@
1365 1385
1366 def shortened_url(self, widget, url, old_url):1386 def shortened_url(self, widget, url, old_url):
1367 self.mainwindow.shortened_url( url, old_url)1387 self.mainwindow.shortened_url( url, old_url)
1388
1389 def fetch_attachment(self, dent, url):
1390 # if we aren't already fetching that url, then start fetching it
1391 if url not in self.dent_map:
1392 self.comm.fetch_attachment(url)
1393 self.dent_map[url] = []
1394 # in any case add this dent widget to the list to be notified
1395 self.dent_map[url].append(dent)
1396
1397 def attachment_received(self, widget, html, url):
1398 # do some ugly HTML parsing
1399 h = HTMLParser.HTMLParser()
1400 text = self.regex_remove_tags.sub('', self.regex_find_body.findall(html)[0])
1401 text = h.unescape(text)
1402 # notify all dent widgets with the updated text
1403 if url in self.dent_map:
1404 for dent in self.dent_map.pop(url):
1405 dent.update_text(self.markup_dent_text(text))
1368 1406
1369 def remember_credentials(self, widget, remember):1407 def remember_credentials(self, widget, remember):
1370 self.options['remember_credentials'] = remember1408 self.options['remember_credentials'] = remember

Subscribers

People subscribed via source and target branches

to status/vote changes: