Merge lp:~erduende/gwibber/bitly-support into lp:gwibber

Proposed by Jesús Carmona
Status: Rejected
Rejected by: Ken VanDine
Proposed branch: lp:~erduende/gwibber/bitly-support
Merge into: lp:gwibber
Diff against target: 443 lines (+209/-26)
6 files modified
gwibber/microblog/dispatcher.py (+10/-0)
gwibber/microblog/urlshorter/__init__.py (+2/-1)
gwibber/microblog/urlshorter/bitly.py (+81/-0)
gwibber/microblog/util/const.py (+1/-0)
gwibber/preferences.py (+52/-1)
ui/gwibber-preferences-dialog.ui (+63/-24)
To merge this branch: bzr merge lp:~erduende/gwibber/bitly-support
Reviewer Review Type Date Requested Status
Ken VanDine Needs Resubmitting
Omer Akram (community) Needs Resubmitting
Review via email: mp+26412@code.launchpad.net

Description of the change

This patch adds bit.ly urlshorter support. In order to implement urlshorten service account authentication i have done some changes to preference window layout. Preferences windows now has a new tab named 'services' where you can find urlshorten configuration and perhaps add photo upload services in the future.

I have tried to follow your coding style but if you think something has to be changed, i'll change it to accomplish your requirements.

Waiting your considerations, i still have to finish text translations.

Sorry about my bad english ;)

To post a comment you must log in.
lp:~erduende/gwibber/bitly-support updated
749. By Jesús Carmona

Removed merge conflict tags from code

Revision history for this message
Omer Akram (om26er) wrote :

Can you please only make the branch for bit.ly currently there is no scope for an extra tab.

review: Needs Resubmitting
Revision history for this message
Ken VanDine (ken-vandine) wrote :

It would be great to support bit.ly, could you update this to work with current trunk and make it use oauth instead of username/password? Perhaps if you select bit.ly from the drop down you can show a link authorize it in a browser.

review: Needs Resubmitting

Unmerged revisions

749. By Jesús Carmona

Removed merge conflict tags from code

748. By Jesus Carmona <pixie@pixie-desktop>

Bazaar conflicts corrections??

747. By Jesús Carmona

Added auth error window before saving username and api key if validation data is incorrect

746. By Jesús Carmona

Removed gwibber's bitly account API KEY because it is not needed for validation

745. By Jesús Carmona

Added bit.ly account validation befrore preferences saving

- Created bit.ly gwibber account (gwibberservice)
- Added API KEY to bitly.py

744. By Jesus Carmona <pixie@pixie-desktop>

- Added support for Bit.ly url shorter service

- Added "services" tab to preferences ui
- Moved url shorter preferences to "services" tab
- Show/hide login and api key fields if protocol needs them
- Added "Get api key" link to bit.ly service
- Stores bit.ly username in couchdb settings db and api key in gnomekeyring

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'gwibber/microblog/dispatcher.py'
2--- gwibber/microblog/dispatcher.py 2010-04-14 15:30:37 +0000
3+++ gwibber/microblog/dispatcher.py 2010-05-31 11:52:27 +0000
4@@ -20,6 +20,11 @@
5 from util.const import *
6
7 try:
8+ import gnomekeyring
9+except:
10+ gnomekeyring = None
11+
12+try:
13 import indicate
14 except:
15 indicate = None
16@@ -613,6 +618,11 @@
17 if self.IsShort(url): return url
18 try:
19 s = urlshorter.PROTOCOLS[service].URLShorter()
20+
21+ if urlshorter.PROTOCOLS[service].PROTOCOL_INFO.get('authtype') == 'login':
22+ urlshorter_key = gnomekeyring.find_items_sync(gnomekeyring.ITEM_GENERIC_SECRET,{"id": SETTINGS['urlshorter_user']})[0].secret
23+ s.auth(SETTINGS['urlshorter_user'], urlshorter_key)
24+
25 return s.short(url)
26 except: return url
27
28
29=== modified file 'gwibber/microblog/urlshorter/__init__.py'
30--- gwibber/microblog/urlshorter/__init__.py 2009-04-15 22:56:03 +0000
31+++ gwibber/microblog/urlshorter/__init__.py 2010-05-31 11:52:27 +0000
32@@ -1,5 +1,5 @@
33
34-import cligs, isgd, tinyurlcom, trim, ur1ca
35+import cligs, isgd, tinyurlcom, trim, ur1ca, bitly
36 #import snipurlcom, zima
37
38 PROTOCOLS = {
39@@ -10,4 +10,5 @@
40 "tr.im": trim,
41 "ur1.ca": ur1ca,
42 #"zi.ma": zima,
43+ "bit.ly": bitly,
44 }
45
46=== added file 'gwibber/microblog/urlshorter/bitly.py'
47--- gwibber/microblog/urlshorter/bitly.py 1970-01-01 00:00:00 +0000
48+++ gwibber/microblog/urlshorter/bitly.py 2010-05-31 11:52:27 +0000
49@@ -0,0 +1,81 @@
50+
51+"""
52+
53+Bit.ly interface for Gwibber
54+erduende (Jesus Carmona) - 05/14/2010
55+
56+"""
57+
58+import gtk
59+import urllib2
60+import json
61+
62+
63+PROTOCOL_INFO = {
64+
65+ "name": "bit.ly",
66+ "version": 0.1,
67+ "fqdn" : "http://bit.ly",
68+
69+ "config": [
70+ "private:api_key",
71+ "username",
72+ ],
73+
74+ "authtype": "login",
75+
76+}
77+
78+API_SERVER = 'http://api.bit.ly/v3'
79+API_KEY = "R_58b2b15d49b0f84f90fb03fcf8f82d23"
80+
81+class URLShorter:
82+
83+ def short(self, text):
84+ shorten = json.loads(urllib2.urlopen(API_SERVER+"/shorten?login="+self.user+"&apiKey="+self.key+"&uri=%s&format=json" % urllib2.quote(text)).read())
85+ if shorten.get('status_code') == 200:
86+ return shorten['data']['url']
87+ else:
88+ return text
89+
90+ def validate(self):
91+ validate = json.loads(urllib2.urlopen(API_SERVER+"/validate?x_login=%s&x_apiKey=%s&login=%s&apiKey=%s&format=json" % (self.urlshorter_login_entry.get_text(), self.urlshorter_apikey_entry.get_text(), self.urlshorter_login_entry.get_text(), self.urlshorter_apikey_entry.get_text())).read())
92+ if validate.get('status_code') == 200:
93+ return validate['data']['valid']
94+ else:
95+ return False
96+
97+ def auth(self, user=None, key=None):
98+ self.user = user
99+ self.key = key
100+
101+
102+ def auth_ui(self, login=None, api_key=None):
103+ self.urlshorter_auth_table = gtk.Table(3, 2, True)
104+ urlshorter_login_label = gtk.Label("bit.ly login:")
105+ self.urlshorter_login_entry = gtk.Entry()
106+ if login:
107+ self.urlshorter_login_entry.set_text(login)
108+ self.urlshorter_auth_table.attach(urlshorter_login_label, 0, 1, 0, 1)
109+ self.urlshorter_auth_table.attach(self.urlshorter_login_entry, 1, 2, 0, 1)
110+
111+ urlshorter_apikey_label = gtk.Label("bit.ly API key:")
112+ self.urlshorter_apikey_entry = gtk.Entry()
113+ if api_key:
114+ self.urlshorter_apikey_entry.set_text(api_key)
115+ self.urlshorter_auth_table.attach(urlshorter_apikey_label, 0, 1, 1, 2)
116+ self.urlshorter_auth_table.attach(self.urlshorter_apikey_entry, 1, 2, 1, 2)
117+
118+ urlshorter_getkey_link = gtk.LinkButton("http://bit.ly/a/your_api_key", "Get your API key")
119+ self.urlshorter_auth_table.attach(urlshorter_getkey_link, 1, 2, 2, 3, gtk.EXPAND)
120+
121+ return self.urlshorter_auth_table
122+
123+ def clear_ui(self):
124+ self.urlshorter_auth_table.destroy()
125+
126+ def get_username(self):
127+ return self.urlshorter_login_entry.get_text()
128+
129+ def get_password(self):
130+ return self.urlshorter_apikey_entry.get_text()
131
132=== modified file 'gwibber/microblog/util/const.py'
133--- gwibber/microblog/util/const.py 2010-04-15 05:47:35 +0000
134+++ gwibber/microblog/util/const.py 2010-05-31 11:52:27 +0000
135@@ -13,6 +13,7 @@
136 "show_fullname": True,
137 "shorten_urls": True,
138 "urlshorter": "is.gd",
139+ "urlshorter_user": None,
140 "reply_append_colon": True,
141 "retweet_style": "recycle",
142 "global_retweet": False,
143
144=== modified file 'gwibber/preferences.py'
145--- gwibber/preferences.py 2010-05-03 04:25:30 +0000
146+++ gwibber/preferences.py 2010-05-31 11:52:27 +0000
147@@ -29,6 +29,11 @@
148 from gwibber import util
149 import gtk, gconf
150
151+try:
152+ import gnomekeyring
153+except:
154+ gnomekeyring = None
155+
156 import gettext
157 from gettext import lgettext as _
158 if hasattr(gettext, 'bind_textdomain_codeset'):
159@@ -37,6 +42,8 @@
160
161 from microblog.util.const import *
162 from microblog.urlshorter import PROTOCOLS as urlshorters
163+from microblog.util.exceptions import GwibberProtocolError
164+import microblog.urlshorter
165
166 from dbus.mainloop.glib import DBusGMainLoop
167
168@@ -84,8 +91,24 @@
169 for urlshorter in urlshorters.keys(): self.urlshorter_selector.append_text(urlshorter)
170 self.ui.get_object("urlshorter_container").pack_start(self.urlshorter_selector, True, True)
171 self.urlshorter_selector.set_active_iter(dict([(x[0].strip(), x.iter) for x in self.urlshorter_selector.get_model()]).get(self.settings["urlshorter"], self.urlshorter_selector.get_model().get_iter_root()))
172+ self.urlshorter_selector.connect("changed", self.on_urlshorter_selector_changed, None)
173 self.urlshorter_selector.show_all()
174
175+ active_urlshorter = urlshorters.get(self.urlshorter_selector.get_active_text())
176+ if active_urlshorter.PROTOCOL_INFO.get('authtype') == 'login':
177+ try:
178+ urlshorter_user = str(self.settings.get("urlshorter_user"))
179+ urlshorter_key = gnomekeyring.find_items_sync(gnomekeyring.ITEM_GENERIC_SECRET, {"id": urlshorter_user})[0].secret
180+ except gnomekeyring.NoMatchError:
181+ urlshorter_user = None
182+ urlshorter_key = None
183+
184+ self.urlshorter_authenticated = active_urlshorter.URLShorter()
185+ #self.urlshorter_auth_ui = self.urlshorter_authenticated.auth_ui(urlshorter_user, urlshorter_key)
186+ self.ui.get_object("urlshorter_table").attach(self.urlshorter_authenticated.auth_ui(urlshorter_user, urlshorter_key), 0, 1, 2, 3)
187+ else:
188+ self.urlshorter_authenticated = None
189+
190 self.retweet_style_selector = gtk.combo_box_new_text()
191 for format in RETWEET_FORMATS: self.retweet_style_selector.append_text(format)
192 self.ui.get_object("retweet_style_container").pack_start(self.retweet_style_selector, True, True)
193@@ -94,7 +117,26 @@
194
195 def on_save_button_clicked(self, widget, data=None):
196 self.settings["interval"] = int(self.ui.get_object("interval").get_value())
197-
198+
199+ # Only process authenticated url shorten service if url shorten is enabled
200+ if self.ui.get_object("shorten_urls").get_property("active"):
201+ if self.urlshorter_authenticated:
202+ if self.urlshorter_authenticated.validate():
203+ self.settings["urlshorter_user"] = self.urlshorter_authenticated.get_username()
204+ gnomekeyring.item_create_sync(
205+ gnomekeyring.get_default_keyring_sync(),
206+ gnomekeyring.ITEM_GENERIC_SECRET,
207+ "Gwibber pref: %s" % ('urlshorter'),
208+ {"id": self.urlshorter_authenticated.get_username()},
209+ self.urlshorter_authenticated.get_password(), True)
210+ else:
211+ GwibberProtocolError(type='auth', protocol='bit.ly', username=self.urlshorter_authenticated.get_username())
212+ return
213+ else:
214+ # TODO: Erease urlshorter key from keyring
215+ self.settings["urlshorter_user"] = None
216+
217+
218 # Only change autostart if it was already set before or if the user enabled it
219 if self.gc.get("/apps/gwibber/autostart") is None:
220 if self.ui.get_object("autostart").get_property("active"):
221@@ -120,3 +162,12 @@
222 def on_prefs_dialog_destroy_event(self, widget, data=None):
223 gtk.main_quit()
224
225+ def on_urlshorter_selector_changed(self, widget, data=None):
226+ if urlshorters.get(widget.get_active_text()).PROTOCOL_INFO.get('authtype') == 'login':
227+ self.urlshorter_authenticated = urlshorters.get(widget.get_active_text()).URLShorter()
228+ self.ui.get_object("urlshorter_table").attach(self.urlshorter_authenticated.auth_ui(), 0, 1, 2, 3)
229+ self.ui.get_object("urlshorter_table").show_all()
230+ else:
231+ if self.urlshorter_authenticated:
232+ self.urlshorter_authenticated.clear_ui()
233+ self.urlshorter_authenticated = None
234
235=== modified file 'ui/gwibber-preferences-dialog.ui'
236--- ui/gwibber-preferences-dialog.ui 2010-03-05 17:54:43 +0000
237+++ ui/gwibber-preferences-dialog.ui 2010-05-31 11:52:27 +0000
238@@ -19,7 +19,6 @@
239 <child internal-child="vbox">
240 <object class="GtkVBox" id="dialog-vbox1">
241 <property name="visible">True</property>
242- <property name="orientation">vertical</property>
243 <property name="spacing">2</property>
244 <child>
245 <object class="GtkNotebook" id="notebook1">
246@@ -30,7 +29,6 @@
247 <property name="visible">True</property>
248 <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
249 <property name="border_width">5</property>
250- <property name="orientation">vertical</property>
251 <property name="spacing">10</property>
252 <child>
253 <object class="GtkFrame" id="frame1">
254@@ -217,13 +215,11 @@
255 <object class="GtkVBox" id="vbox1">
256 <property name="visible">True</property>
257 <property name="border_width">5</property>
258- <property name="orientation">vertical</property>
259 <property name="spacing">5</property>
260 <child>
261 <object class="GtkVBox" id="vbox7">
262 <property name="visible">True</property>
263 <property name="border_width">5</property>
264- <property name="orientation">vertical</property>
265 <property name="spacing">5</property>
266 <child>
267 <object class="GtkCheckButton" id="show_fullname">
268@@ -257,7 +253,6 @@
269 <child>
270 <object class="GtkVBox" id="vbox3">
271 <property name="visible">True</property>
272- <property name="orientation">vertical</property>
273 <child>
274 <object class="GtkCheckButton" id="global_retweet">
275 <property name="label" translatable="yes">Send retweets to all services</property>
276@@ -273,7 +268,6 @@
277 <child>
278 <object class="GtkVBox" id="retweet_style_container">
279 <property name="visible">True</property>
280- <property name="orientation">vertical</property>
281 <child>
282 <placeholder/>
283 </child>
284@@ -315,7 +309,6 @@
285 <object class="GtkTable" id="table16">
286 <property name="visible">True</property>
287 <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
288- <property name="n_rows">3</property>
289 <property name="homogeneous">True</property>
290 <child>
291 <object class="GtkCheckButton" id="reply_append_colon">
292@@ -326,11 +319,57 @@
293 <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
294 <property name="draw_indicator">True</property>
295 </object>
296- <packing>
297- <property name="top_attach">2</property>
298- <property name="bottom_attach">3</property>
299- </packing>
300 </child>
301+ </object>
302+ </child>
303+ </object>
304+ </child>
305+ <child type="label">
306+ <object class="GtkLabel" id="label79">
307+ <property name="visible">True</property>
308+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
309+ <property name="label" translatable="yes">&lt;b&gt;Advanced&lt;/b&gt;</property>
310+ <property name="use_markup">True</property>
311+ </object>
312+ </child>
313+ </object>
314+ <packing>
315+ <property name="expand">False</property>
316+ <property name="position">2</property>
317+ </packing>
318+ </child>
319+ </object>
320+ <packing>
321+ <property name="position">1</property>
322+ </packing>
323+ </child>
324+ <child type="tab">
325+ <object class="GtkLabel" id="messages_label">
326+ <property name="visible">True</property>
327+ <property name="label" translatable="yes">Messages</property>
328+ </object>
329+ <packing>
330+ <property name="position">1</property>
331+ <property name="tab_fill">False</property>
332+ </packing>
333+ </child>
334+ <child>
335+ <object class="GtkVBox" id="vbox6">
336+ <property name="visible">True</property>
337+ <child>
338+ <object class="GtkFrame" id="frame6">
339+ <property name="visible">True</property>
340+ <property name="label_xalign">0</property>
341+ <property name="shadow_type">none</property>
342+ <child>
343+ <object class="GtkAlignment" id="alignment5">
344+ <property name="visible">True</property>
345+ <property name="left_padding">12</property>
346+ <child>
347+ <object class="GtkTable" id="urlshorter_table">
348+ <property name="visible">True</property>
349+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
350+ <property name="n_rows">3</property>
351 <child>
352 <object class="GtkCheckButton" id="shorten_urls">
353 <property name="label" translatable="yes">Automatically shorten pasted URLs using:</property>
354@@ -344,7 +383,6 @@
355 <child>
356 <object class="GtkVBox" id="urlshorter_container">
357 <property name="visible">True</property>
358- <property name="orientation">vertical</property>
359 <property name="spacing">5</property>
360 <child>
361 <placeholder/>
362@@ -355,36 +393,39 @@
363 <property name="bottom_attach">2</property>
364 </packing>
365 </child>
366+ <child>
367+ <placeholder/>
368+ </child>
369 </object>
370 </child>
371 </object>
372 </child>
373 <child type="label">
374- <object class="GtkLabel" id="label79">
375+ <object class="GtkLabel" id="label2">
376 <property name="visible">True</property>
377- <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
378- <property name="label" translatable="yes">&lt;b&gt;Advanced&lt;/b&gt;</property>
379+ <property name="label" translatable="yes">&lt;b&gt;URL Shortener&lt;/b&gt;</property>
380 <property name="use_markup">True</property>
381 </object>
382 </child>
383 </object>
384 <packing>
385 <property name="expand">False</property>
386- <property name="position">2</property>
387+ <property name="fill">False</property>
388+ <property name="position">0</property>
389 </packing>
390 </child>
391 </object>
392 <packing>
393- <property name="position">1</property>
394+ <property name="position">2</property>
395 </packing>
396 </child>
397 <child type="tab">
398- <object class="GtkLabel" id="messages_label">
399+ <object class="GtkLabel" id="services_label">
400 <property name="visible">True</property>
401- <property name="label" translatable="yes">Messages</property>
402+ <property name="label" translatable="yes">Services</property>
403 </object>
404 <packing>
405- <property name="position">1</property>
406+ <property name="position">2</property>
407 <property name="tab_fill">False</property>
408 </packing>
409 </child>
410@@ -392,7 +433,6 @@
411 <object class="GtkVBox" id="vbox5">
412 <property name="visible">True</property>
413 <property name="border_width">5</property>
414- <property name="orientation">vertical</property>
415 <property name="spacing">10</property>
416 <child>
417 <object class="GtkFrame" id="frame3">
418@@ -408,7 +448,6 @@
419 <object class="GtkVBox" id="theme_container">
420 <property name="visible">True</property>
421 <property name="tooltip_text" translatable="yes">Select a message theme</property>
422- <property name="orientation">vertical</property>
423 <property name="spacing">5</property>
424 <child>
425 <placeholder/>
426@@ -432,7 +471,7 @@
427 </child>
428 </object>
429 <packing>
430- <property name="position">2</property>
431+ <property name="position">3</property>
432 </packing>
433 </child>
434 <child type="tab">
435@@ -441,7 +480,7 @@
436 <property name="label" translatable="yes">Style</property>
437 </object>
438 <packing>
439- <property name="position">2</property>
440+ <property name="position">3</property>
441 <property name="tab_fill">False</property>
442 </packing>
443 </child>