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
1=== modified file 'Communicator.py'
2--- Communicator.py 2012-09-08 16:34:36 +0000
3+++ Communicator.py 2013-08-31 16:29:00 +0000
4@@ -113,6 +113,10 @@
5 gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
6 (gobject.TYPE_STRING,gobject.TYPE_STRING,)
7 ),
8+ 'attachment-received': (
9+ gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
10+ (gobject.TYPE_STRING,gobject.TYPE_STRING,)
11+ ),
12 }
13
14 def __init__(self, app_name=None, cert_file=None, apiroot="http://identi.ca/api", version=""):
15@@ -422,3 +426,6 @@
16 data = {'url':longurl, 'format':'simple'}
17 url = "http://is.gd/create.php?" + urlencode(data)
18 self.process_httprequest(url, 'shortened_url', longurl, True)
19+
20+ def fetch_attachment(self, url):
21+ self.process_httprequest(url, 'attachment-received', url, True)
22
23=== modified file 'Dent.py'
24--- Dent.py 2012-07-16 22:36:42 +0000
25+++ Dent.py 2013-08-31 16:29:00 +0000
26@@ -84,6 +84,7 @@
27 vbox = gtk.VBox(False,0)
28 #vbox.set_border_width(5)
29 user_box.pack_start(vbox,True,True,0)
30+
31 self.text_label = gtk.Label()
32 self.text_label.set_markup( data['markup'] )
33 self.text_label.set_alignment(0,0)
34@@ -214,6 +215,9 @@
35
36 vbox.pack_start(hbox1,False,False,0)
37 self.add(user_box)
38+
39+ def update_text(self, text):
40+ self.text_label.set_markup(text)
41
42 def event_box_realized(self,widget):
43 hand2 = gtk.gdk.Cursor(gtk.gdk.HAND2)
44
45=== modified file 'SettingsPage.py'
46--- SettingsPage.py 2012-07-16 22:36:42 +0000
47+++ SettingsPage.py 2013-08-31 16:29:00 +0000
48@@ -21,6 +21,10 @@
49 gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
50 (gobject.TYPE_BOOLEAN,)
51 ),
52+ 'option-expand-long-dents': (
53+ gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
54+ (gobject.TYPE_BOOLEAN,)
55+ ),
56 'option-ctrl-enter': (
57 gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
58 (gobject.TYPE_BOOLEAN,)
59@@ -148,6 +152,9 @@
60
61 self.api_retweet = gtk.CheckButton(_("Use retweet API instead of default redent style"))
62 self.api_retweet.connect("toggled", self.option_toggled,'option-api-retweet')
63+
64+ self.expand_long_dents = gtk.CheckButton(_("Expand too long dents from remote servers"))
65+ self.expand_long_dents.connect("toggled", self.option_toggled, 'option-expand-long-dents')
66
67 self.enable_notifications = gtk.CheckButton(_("Enable notifications"))
68 self.enable_notifications.connect("toggled", self.option_toggled,'option-notifications')
69@@ -178,6 +185,7 @@
70 options_box.pack_start(self.disable_https,False,False,0)
71 options_box.pack_start(self.ignore_activity,False,False,0)
72 options_box.pack_start(self.api_retweet,False,False,0)
73+ options_box.pack_start(self.expand_long_dents,False,False,0)
74 options_box.pack_start(self.open_fullscreen,False,False,0)
75
76 #does the app have notifications?
77@@ -296,6 +304,9 @@
78 def set_api_retweet(self,boolean):
79 self.api_retweet.set_active(boolean)
80
81+ def set_expand_long_dents(self,boolean):
82+ self.expand_long_dents.set_active(boolean)
83+
84 #More Code for notifications
85 def set_notifications(self,boolean):
86 self.enable_notifications.set_active(boolean)
87
88=== modified file 'XMLProcessor.py'
89--- XMLProcessor.py 2012-07-16 22:36:42 +0000
90+++ XMLProcessor.py 2013-08-31 16:29:00 +0000
91@@ -142,6 +142,29 @@
92 #what server is this shit from?
93 profile_url = self.get_node_text(status,"statusnet:profile_url")
94 dict['profile_url'] = profile_url
95+
96+ # put attachments in a neat array if there are any
97+ attachments = status.getElementsByTagName('attachments')
98+ if attachments.length:
99+ enclosures = attachments[0].getElementsByTagName('enclosure')
100+ dict['attachments'] = []
101+ for i in range(enclosures.length):
102+ item = enclosures.item(i)
103+ a = { 'mimetype': item.getAttribute('mimetype'),
104+ 'url': item.getAttribute('url')}
105+ size_str = item.getAttribute('size')
106+ if size_str.isdigit():
107+ a['size'] = int(size_str)
108+ dict['attachments'].append(a)
109+
110+ # this should say if dent is truncated... but doesn't
111+ #dict['truncated'] = self.get_node_text(status, "truncated")
112+ # this is what actually works (fugly hack!)
113+ sn_html = self.get_node_text(status, "statusnet:html")
114+ dict['truncated'] = False
115+ if sn_html.find("class=\"attachment more\" title=\"Show more\"") != -1:
116+ dict['truncated'] = True
117+
118 # we should clean the source ( if there is one )
119 if dict['source']!=None:
120 dict['source']=self.re_source_sub.sub( '', dict['source'])
121
122=== modified file 'heybuddy.py'
123--- heybuddy.py 2013-01-15 00:33:09 +0000
124+++ heybuddy.py 2013-08-31 16:29:00 +0000
125@@ -33,6 +33,7 @@
126 import re
127 import time
128 import subprocess
129+import HTMLParser
130 #this might be on Maemo
131 from PlatformSpecific import has_hildon,links_unavailable
132 #what are the custom classes that I need?
133@@ -137,7 +138,10 @@
134 self.regex_group = re.compile("(^| )!([\w-]+)")
135 self.regex_user=re.compile("(^|[^A-z0-9])@(\w+)")
136 self.regex_just_user=re.compile("@(\w+)")
137- self.regex_url=re.compile("(http[s]?://.*?)(\.$|\. |\s|$)",re.IGNORECASE)
138+ self.regex_url=re.compile("(http[s]?://.*?)(\.$|\. |\s|\)|$)",re.IGNORECASE)
139+ #regex's for parsing html attachments
140+ self.regex_find_body = re.compile("\<body\>(.*)\<\/body\>")
141+ self.regex_remove_tags = re.compile('<[^>]*>')
142 #get the initial dents
143 self.initial_dents = self.conf.get('settings','initial_dents',default='20' )
144 self.dent_limit = int(self.initial_dents)*2
145@@ -187,12 +191,16 @@
146 self.comm.connect('verify_credentialsXML',self.process_verifycredentialsXML)
147 self.comm.connect('configXML',self.process_configXML)
148 self.comm.connect('shortened_url',self.shortened_url)
149+ self.comm.connect('attachment-received',self.attachment_received)
150 #are we disabling https?
151 self.comm.set_disable_https( self.conf.get_bool_option('disable_https') )
152 #create an image cacher thingy
153 self.imagecache = ImageCache()
154 self.imagecache.set_disabled( self.conf.get_bool_option('no_avatars') )
155 self.imagecache.connect('get-widget-image', self.get_widget_image)
156+
157+ # keep track of dents to notify of updated attachments
158+ self.dent_map = {}
159
160 #Create notify-er if has pynotify
161 if has_pynotify:
162@@ -339,6 +347,8 @@
163 self.settingspage.set_no_avatars( self.options['no_avatar'] )
164 self.options['api_retweet'] = self.conf.get_bool_option('api_retweet')
165 self.settingspage.set_api_retweet( self.options['api_retweet'] )
166+ self.options['expand_long_dents'] = self.conf.get_bool_option('expand_long_dents')
167+ self.settingspage.set_expand_long_dents( self.options['expand_long_dents'] )
168 self.options['notifications']= self.conf.get_bool_option('notifications')
169 self.settingspage.set_notifications( self.options['notifications'] )
170 self.options['notify_replies']= self.conf.get_bool_option('notify_replies')
171@@ -355,6 +365,7 @@
172 self.settingspage.connect('option-context-backwards',self.option_changed, 'context_backwards')
173 self.settingspage.connect('option-no-avatars',self.option_changed, 'no_avatars')
174 self.settingspage.connect('option-api-retweet', self.option_changed, 'api_retweet')
175+ self.settingspage.connect('option-expand-long-dents', self.option_changed, 'expand_long_dents')
176 self.settingspage.connect('option-notifications',self.option_changed, 'notifications')
177 self.settingspage.connect('option-notify-replies',self.option_changed, 'notify_replies')
178 self.settingspage.connect('option-ignore-activity',self.option_changed, 'ignore_activity')
179@@ -822,6 +833,14 @@
180 dent.connect('favorite-clicked',self.favorite_clicked)
181 dent.connect('unfavorite-clicked',self.unfavorite_clicked)
182 dent.connect('open-link',self.open_link)
183+
184+ # if dent was truncated, fetch the rest which
185+ # should be in an html attachment
186+ if self.options['expand_long_dents'] and data['truncated'] and 'attachments' in data:
187+ for a in data['attachments']:
188+ if a['mimetype'] == 'text/html':
189+ self.fetch_attachment(dent, a['url'])
190+
191 if target_page!=self.userpage:
192 #get the image for this dent
193 self.imagecache.add_image_to_widget(data['profile_image_url'],dent)
194@@ -990,6 +1009,7 @@
195 if not filtered_status:
196 #get the markup
197 data['markup'] = self.markup_dent_text(data['text'])
198+
199 #did this dent connect
200 if not self.connect_dent(data,target_page,is_direct_dent=is_direct):
201 continue
202@@ -1365,6 +1385,24 @@
203
204 def shortened_url(self, widget, url, old_url):
205 self.mainwindow.shortened_url( url, old_url)
206+
207+ def fetch_attachment(self, dent, url):
208+ # if we aren't already fetching that url, then start fetching it
209+ if url not in self.dent_map:
210+ self.comm.fetch_attachment(url)
211+ self.dent_map[url] = []
212+ # in any case add this dent widget to the list to be notified
213+ self.dent_map[url].append(dent)
214+
215+ def attachment_received(self, widget, html, url):
216+ # do some ugly HTML parsing
217+ h = HTMLParser.HTMLParser()
218+ text = self.regex_remove_tags.sub('', self.regex_find_body.findall(html)[0])
219+ text = h.unescape(text)
220+ # notify all dent widgets with the updated text
221+ if url in self.dent_map:
222+ for dent in self.dent_map.pop(url):
223+ dent.update_text(self.markup_dent_text(text))
224
225 def remember_credentials(self, widget, remember):
226 self.options['remember_credentials'] = remember

Subscribers

People subscribed via source and target branches

to status/vote changes: