Merge lp:~bigwhale/gwibber/follow-unfollow into lp:gwibber
- follow-unfollow
- Merge into trunk
Status: | Rejected |
---|---|
Rejected by: | Robert Bruce Park |
Proposed branch: | lp:~bigwhale/gwibber/follow-unfollow |
Merge into: | lp:gwibber |
Diff against target: |
751 lines (+412/-42) 9 files modified
gwibber/actions.py (+49/-2) gwibber/client.py (+1/-4) gwibber/gwui.py (+17/-10) gwibber/microblog/dispatcher.py (+71/-21) gwibber/microblog/plugins/twitter/__init__.py (+68/-3) gwibber/microblog/storage.py (+144/-1) gwibber/microblog/util/const.py (+50/-0) ui/templates/base.mako (+10/-1) ui/themes/ubuntu/main.css (+2/-0) |
To merge this branch: | bzr merge lp:~bigwhale/gwibber/follow-unfollow |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Robert Bruce Park | Disapprove | ||
gwibber-committers | Pending | ||
Review via email: mp+42346@code.launchpad.net |
Commit message
Description of the change
See branch details for info.
Review and 'external' testing is wanted. :)
- 929. By David Klasinc
-
Merged with the latest trunk.
David Klasinc (bigwhale) wrote : | # |
Yeah, this was already in the old Gwibber. ;) Not sure why this request was left here dangling in the limbo. :)
Robert Bruce Park (robru) wrote : | # |
Oh great, it seemed like a large diff, I was worried that you'd be upset about the merge being forgotten about for so long ;-)
Unmerged revisions
- 929. By David Klasinc
-
Merged with the latest trunk.
- 928. By David Klasinc
-
Fixed follow/unfollow for search results, other minor cleanups.
- 927. By David Klasinc
-
Removed unsed list.
- 926. By David Klasinc
-
Fetching of user data, friends/followers data is now inserted into database with new messages, direct messages can only be sent to those who follow you.
- 925. By David Klasinc
-
Fixed broken if sentence.
- 924. By David Klasinc
-
Removed debug messages and fixed an issue with friends/followers.
- 923. By David Klasinc
-
Relevant comment changed.
- 922. By David Klasinc
-
Cleaned up the code a little, implemented recursive fetching of friend/follower ids.
- 921. By David Klasinc
-
Removed friends/followers stream buttons.
- 920. By David Klasinc
-
Follow/Unfollow menu actions are now implemented.
Preview Diff
1 | === modified file 'gwibber/actions.py' |
2 | --- gwibber/actions.py 2010-10-19 18:28:11 +0000 |
3 | +++ gwibber/actions.py 2011-02-16 07:38:14 +0000 |
4 | @@ -86,6 +86,53 @@ |
5 | if not msg.get("sender", {}).get("is_me", 0) and not msg.get("private", 0): |
6 | return True |
7 | |
8 | +class follow(MessageAction): |
9 | + icon = "bookmark_add" |
10 | + label = _("_Follow") |
11 | + |
12 | + @classmethod |
13 | + def action(self, w, client, msg): |
14 | + client.service.PerformOp(json.dumps({ |
15 | + "account": msg["account"], |
16 | + "operation": "follow", |
17 | + "args": {"screen_name": msg["sender"]["nick"]}, |
18 | + "transient": False, |
19 | + })) |
20 | + |
21 | + image = resources.get_ui_asset("gwibber.svg") |
22 | + expire_timeout = 5000 |
23 | + gwibber.microblog.util.notify(_("Follow"), _("Trying to follow %s" % msg["sender"].get("nick")), image, expire_timeout) |
24 | + |
25 | + @classmethod |
26 | + def include(self, client, msg): |
27 | + if "follow" in client.model.services[msg["service"]]["features"]: |
28 | + if not msg.get("sender", {}).get("is_friend", 0) and not msg.get("sender", {}).get("is_me", 0): |
29 | + return True |
30 | + |
31 | + |
32 | +class unfollow(MessageAction): |
33 | + icon = "bookmark_add" |
34 | + label = _("_Unfollow") |
35 | + |
36 | + @classmethod |
37 | + def action(self, w, client, msg): |
38 | + client.service.PerformOp(json.dumps({ |
39 | + "account": msg["account"], |
40 | + "operation": "unfollow", |
41 | + "args": {"screen_name": msg["sender"]["nick"]}, |
42 | + "transient": False, |
43 | + })) |
44 | + |
45 | + image = resources.get_ui_asset("gwibber.svg") |
46 | + expire_timeout = 5000 |
47 | + gwibber.microblog.util.notify(_("Unfollow"), _("Trying to unfollow %s" % msg["sender"].get("nick")), image, expire_timeout) |
48 | + |
49 | + @classmethod |
50 | + def include(self, client, msg): |
51 | + if "unfollow" in client.model.services[msg["service"]]["features"]: |
52 | + if msg.get("sender", {}).get("is_friend", 0) and not msg.get("sender", {}).get("is_me", 0): |
53 | + return True |
54 | + |
55 | class private(MessageAction): |
56 | icon = "mail-reply-sender" |
57 | label = _("_Direct Message") |
58 | @@ -97,7 +144,7 @@ |
59 | @classmethod |
60 | def include(self, client, msg): |
61 | if "private" in client.model.services[msg["service"]]["features"]: |
62 | - if not msg.get("sender", {}).get("is_me", 0) and not msg.get("private", 0): |
63 | + if not msg.get("sender", {}).get("is_me", 0) and msg.get("sender", {}).get("is_follower", 0) and not msg.get("private", 0): |
64 | return True |
65 | |
66 | class like(MessageAction): |
67 | @@ -250,4 +297,4 @@ |
68 | def include(self, client, msg): |
69 | return gwibber.microblog.util.service_is_running("org.gnome.Tomboy") |
70 | |
71 | -MENU_ITEMS = [reply, retweet, private, read, user, like, delete, tomboy, translate] |
72 | +MENU_ITEMS = [reply, retweet, follow, unfollow, private, read, user, like, delete, tomboy, translate] |
73 | |
74 | === modified file 'gwibber/client.py' |
75 | --- gwibber/client.py 2010-12-11 04:57:03 +0000 |
76 | +++ gwibber/client.py 2011-02-16 07:38:14 +0000 |
77 | @@ -196,7 +196,7 @@ |
78 | # Load the user's saved streams |
79 | streams = json.loads(self.model.settings["streams"]) |
80 | streams = [dict((str(k), v) for k, v in s.items()) for s in streams] |
81 | - |
82 | + |
83 | # Use the multicolumn mode if there are multiple saved streams |
84 | view_class = getattr(gwui, "MultiStreamUi" if len(streams) > 1 else "SingleStreamUi") |
85 | |
86 | @@ -522,9 +522,6 @@ |
87 | def perform_action(mi, act, widget, msg): act(widget, self, msg) |
88 | |
89 | for a in actions.MENU_ITEMS: |
90 | - if a.action.__self__.__name__ == "private" and msg["sender"].get("is_me", 0): |
91 | - continue |
92 | - |
93 | if a.include(self, msg): |
94 | image = gtk.image_new_from_icon_name(a.icon, gtk.ICON_SIZE_MENU) |
95 | mi = gtk.ImageMenuItem() |
96 | |
97 | === modified file 'gwibber/gwui.py' |
98 | --- gwibber/gwui.py 2010-11-16 18:55:08 +0000 |
99 | +++ gwibber/gwui.py 2011-02-16 07:38:14 +0000 |
100 | @@ -141,15 +141,22 @@ |
101 | |
102 | if len(default_streams) > 1: |
103 | for feature in default_streams: |
104 | - aname = self.features[feature]["stream"] |
105 | - item["items"].append({ |
106 | - "name": _(aname.capitalize()), |
107 | - "account": aId, |
108 | - "stream": aname, |
109 | - "transient": False, |
110 | - "color": util.Color(account["color"]), |
111 | - "service": account["service"], |
112 | - }) |
113 | + # |
114 | + # Ugly hack that will go away when I implement friends / followers |
115 | + # lists. For now it prevents displaying an icon for those streams. |
116 | + # |
117 | + # -- BigW 2010/11/15 |
118 | + # |
119 | + if not (feature == "friends" or feature == "followers"): |
120 | + aname = self.features[feature]["stream"] |
121 | + item["items"].append({ |
122 | + "name": _(aname.capitalize()), |
123 | + "account": aId, |
124 | + "stream": aname, |
125 | + "transient": False, |
126 | + "color": util.Color(account["color"]), |
127 | + "service": account["service"], |
128 | + }) |
129 | |
130 | item["items"].append({ |
131 | "name": _("Sent"), |
132 | @@ -772,7 +779,7 @@ |
133 | # get message data for selected stream |
134 | # stream, account, time, transient, recipient, orderby, order, limit |
135 | msgs = json.loads(self.model.streams.Messages(streams[0]["stream"] or "all", streams[0]["account"] or "all", time, streams[0]["transient"] or "0", streams[0].has_key("recipient") and streams[0]["recipient"] or "0", orderby, order, limit)) |
136 | - |
137 | + |
138 | for item in msgs: |
139 | message = item |
140 | message["dupes"] = [] |
141 | |
142 | === modified file 'gwibber/microblog/dispatcher.py' |
143 | --- gwibber/microblog/dispatcher.py 2010-12-08 19:49:04 +0000 |
144 | +++ gwibber/microblog/dispatcher.py 2011-02-16 07:38:14 +0000 |
145 | @@ -54,7 +54,16 @@ |
146 | text_cleaner = re.compile(u"[: \n\t\r♻♺]+|@[^ ]+|![^ ]+|#[^ ]+") # signs, @nickname, !group, #tag |
147 | new_messages = [] |
148 | |
149 | - if message_data is not None: |
150 | + # To avoid doubling of log and return calls |
151 | + if opname in ["friends", "followers", "follow", "unfollow"] and message_data is not None: |
152 | + if isinstance(message_data[0], dict) and message_data[0].has_key("error"): |
153 | + new_messages.insert(0, ( |
154 | + "error", |
155 | + json.dumps(message_data[0]) |
156 | + )) |
157 | + else: |
158 | + new_messages = message_data |
159 | + elif message_data is not None: |
160 | for m in message_data: |
161 | try: |
162 | if isinstance(m, dict) and m.has_key("mid"): |
163 | @@ -145,7 +154,7 @@ |
164 | # if account doesn't have the required feature or is disabled, return |
165 | if enabled in acct: |
166 | if not acct[enabled]: return |
167 | - else: |
168 | + else: |
169 | return |
170 | # if there is an account for a service that gwibber doesn't no about, return |
171 | if not acct["service"] in SERVICES: return |
172 | @@ -243,6 +252,7 @@ |
173 | self.searches = storage.SearchManager(self.db) |
174 | self.streams = storage.StreamManager(self.db) |
175 | self.messages = storage.MessageManager(self.db) |
176 | + self.friends = storage.FriendsManager(self.db) |
177 | self.collector = OperationCollector(self) |
178 | |
179 | # Monitor the connection |
180 | @@ -332,16 +342,15 @@ |
181 | def PerformOp(self, opdata): |
182 | try: o = json.loads(opdata) |
183 | except: return |
184 | - |
185 | log.logger.debug("** Starting Single Operation **") |
186 | self.LoadingStarted() |
187 | |
188 | params = ["account", "operation", "args", "transient"] |
189 | operation = None |
190 | - |
191 | + |
192 | if "account" in o and self.collector.get_account(o["account"]): |
193 | account = self.collector.get_account(o["account"]) |
194 | - |
195 | + |
196 | if "id" in o: |
197 | operation = self.collector.get_operation_by_id(o["id"]) |
198 | elif "operation" in o and self.collector.validate_operation(account, o["operation"]): |
199 | @@ -495,6 +504,7 @@ |
200 | icon = util.resources.get_ui_asset("gwibber.svg") |
201 | util.notify(error["account"]["service"], error["message"], icon, 2000) |
202 | self.notified_errors[error["account"]["service"]] = error["message"] |
203 | + print self.notified_errors |
204 | |
205 | def perform_async_operation(self, iterable): |
206 | t = MapAsync(perform_operation, iterable, self.loading_complete, self.loading_failed, self.workerpool) |
207 | @@ -502,15 +512,36 @@ |
208 | |
209 | def loading_complete(self, output): |
210 | self.refresh_count += 1 |
211 | - |
212 | + |
213 | items = [] |
214 | errors = [] |
215 | for o in output: |
216 | for o2 in o[1]: |
217 | if len(o2) > 1: |
218 | - if o2[0] != "error": |
219 | - with sqlite3.connect(SQLITE_DB_FILENAME) as db: |
220 | - if len(db.execute("""select * from messages where mid = '%s' and account = '%s' and stream = '%s'""" % (o2[1], o2[2], o2[6])).fetchall()) > 0: |
221 | + if o2[0] == "friendships": # Deal with follow/unfollow operations |
222 | + if o2[1]["type"] == "follow": |
223 | + self.friends.Follow(o2[1]["account"], o2[1]["service"], o2[1]["user_id"], o2[1]["nick"]) |
224 | + elif o2[1]["type"] == "unfollow": |
225 | + self.friends.Unfollow(o2[1]["account"], o2[1]["service"], o2[1]["user_id"], o2[1]["nick"]) |
226 | + elif o2[0] == "friends": # Operation friends will grab all the friend IDs |
227 | + with sqlite3.connect(SQLITE_DB_FILENAME) as db: |
228 | + output = db.execute("UPDATE friends SET friend = 0 WHERE account = '%s' AND service = '%s'" % (o2[1]["account"], o2[1]["service"])) |
229 | + for id in o2[1]["ids"]: |
230 | + output = db.execute("UPDATE friends SET friend = 1 WHERE account = '%s' AND service = '%s' AND user_id = '%s'" % (o2[1]["account"], o2[1]["service"], id)) |
231 | + if not output.rowcount: |
232 | + output = db.execute("INSERT INTO friends (account, service, user_id, friend) VALUES (?, ?, ?, 1)", (o2[1]["account"], o2[1]["service"], id)) |
233 | + |
234 | + elif o2[0] == "followers": # Operation followers will grab all the followers IDs |
235 | + with sqlite3.connect(SQLITE_DB_FILENAME) as db: |
236 | + output = db.execute("UPDATE friends SET follower = 0 WHERE account = '%s' AND service = '%s'" % (o2[1]["account"], o2[1]["service"])) |
237 | + for id in o2[1]["ids"]: |
238 | + output = db.execute("UPDATE friends SET follower = 1 WHERE account = '%s' AND service = '%s' AND user_id = '%s'" % (o2[1]["account"], o2[1]["service"], id)) |
239 | + if not output.rowcount: |
240 | + output = db.execute("INSERT INTO friends (account, service, user_id, follower) VALUES (?, ?, ?, 1)", (o2[1]["account"], o2[1]["service"], id)) |
241 | + |
242 | + elif o2[0] != "error": |
243 | + with sqlite3.connect(SQLITE_DB_FILENAME) as db: |
244 | + if len(db.execute("""SELECT * FROM MESSAGES WHERE mid = '%s' AND account = '%s' AND stream = '%s'""" % (o2[1], o2[2], o2[6])).fetchall()) > 0: |
245 | self.messages.Message("update", o2[-1]) |
246 | else: |
247 | self.messages.Message("new", o2[-1]) |
248 | @@ -518,24 +549,46 @@ |
249 | else: |
250 | errors.append(o2) |
251 | with sqlite3.connect(SQLITE_DB_FILENAME) as db: |
252 | - oldid = db.execute("select max(ROWID) from messages").fetchone()[0] or 0 |
253 | + oldid = db.execute("SELECT MAX(ROWID) FROM messages").fetchone()[0] or 0 |
254 | |
255 | output = db.executemany("INSERT OR REPLACE INTO messages (%s) VALUES (%s)" % ( |
256 | ",".join(self.messages.columns), |
257 | ",".join("?" * len(self.messages.columns))), items) |
258 | + # |
259 | + # For every message, update or insert new data into friends table |
260 | + # Skip searches for now, because of a twitter bug. |
261 | + # |
262 | + # |
263 | + for item in items: |
264 | + data = json.loads(item[13]) |
265 | + if not data["operation"] == "search": |
266 | + if not data["sender"]["is_me"]: |
267 | + output = db.execute(""" |
268 | + UPDATE friends SET name = ?, url = ?, image = ?, nick = ?, location = ? |
269 | + WHERE account = '%s' AND service = '%s' AND user_id = '%s'""" % (data["account"], data["service"], data["sender"]["id"]), |
270 | + (data["sender"]["name"], data["sender"]["url"], data["sender"]["image"], |
271 | + data["sender"]["nick"], data["sender"]["location"])) |
272 | + |
273 | + if not output.rowcount: |
274 | + output = db.execute(""" |
275 | + INSERT INTO friends (account, service, user_id, name, url, image, nick, location, friend, follower) |
276 | + VALUES (?, ?, ?, ?, ?, ?, ?, ?, 0, 0)""", |
277 | + (data["account"], data["service"], data["sender"]["id"], |
278 | + data["sender"]["name"], data["sender"]["url"], data["sender"]["image"], |
279 | + data["sender"]["nick"], data["sender"]["location"])) |
280 | |
281 | for s in "messages", "replies", "private": |
282 | - if s == "messages": |
283 | - to_me = 0 |
284 | + if s == "messages": |
285 | + to_me = 0 |
286 | else: to_me = 1 |
287 | count = db.execute("SELECT count(mid) FROM messages WHERE stream = ? AND from_me = 0 and to_me = ? AND ROWID > ?", (s,to_me,oldid)).fetchone()[0] or 0 |
288 | self.unseen_counts[s] = self.unseen_counts[s] + int(count) |
289 | - |
290 | + |
291 | self.update_indicators(self.unseen_counts) |
292 | |
293 | new_items = db.execute(""" |
294 | select * from (select * from messages where operation == "receive" and ROWID > %s and to_me = 0 ORDER BY time DESC LIMIT 10) as a union |
295 | - select * from (select * from messages where operation IN ("receive","private") and ROWID > %s and to_me != 0 ORDER BY time DESC LIMIT 10) as b |
296 | + select * from (select * from messages where operation == "receive" and ROWID > %s and to_me != 0 ORDER BY time DESC LIMIT 10) as b |
297 | ORDER BY time ASC""" % (oldid, oldid)).fetchall() |
298 | |
299 | for i in new_items: |
300 | @@ -592,7 +645,7 @@ |
301 | def on_indicator_interest_removed(self, server, interest): |
302 | self.IndicatorInterestRemoved() |
303 | |
304 | - def on_indicator_server_activate(self, indicator, timestamp=None): |
305 | + def on_indicator_server_activate(self, indicator, timestamp): |
306 | dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) |
307 | client_bus = dbus.SessionBus() |
308 | log.logger.debug("Raising gwibber client") |
309 | @@ -606,7 +659,7 @@ |
310 | except dbus.DBusException: |
311 | log.logger.error("Indicator activate failed:\n%s", traceback.format_exc()) |
312 | |
313 | - def on_indicator_activate(self, indicator, timestamp=None): |
314 | + def on_indicator_activate(self, indicator, timestamp): |
315 | if not indicate: return |
316 | stream = indicator.get_property("stream") |
317 | log.logger.debug("Raising gwibber client, focusing %s stream", stream) |
318 | @@ -659,10 +712,7 @@ |
319 | sender_name = message["sender"].get("nick", message["sender"].get("name", "")) |
320 | |
321 | #image = util.resources.get_ui_asset("icons/breakdance/scalable/%s.svg" % message["service"]) |
322 | - if message["sender"].has_key("image"): |
323 | - image = util.resources.get_avatar_path(message["sender"]["image"]) |
324 | - else: |
325 | - image = util.resources.get_ui_asset("icons/breakdance/scalable/%s.svg" % message["service"]) |
326 | + image = util.resources.get_avatar_path(message["sender"]["image"]) |
327 | if not image: |
328 | image = util.resources.get_ui_asset("icons/breakdance/scalable/%s.svg" % message["service"]) |
329 | util.notify(sender_name, message["text"], image, 2000) |
330 | @@ -685,7 +735,7 @@ |
331 | gobject.source_remove(self.refresh_timer_id) |
332 | log.logger.debug("Refresh interval is set to %s", SETTINGS["interval"]) |
333 | operations = [] |
334 | - |
335 | + |
336 | for o in self.collector.get_operations(): |
337 | interval = FEATURES[o[1]].get("interval", 1) |
338 | if self.refresh_count % interval == 0: |
339 | |
340 | === modified file 'gwibber/microblog/plugins/twitter/__init__.py' |
341 | --- gwibber/microblog/plugins/twitter/__init__.py 2010-12-14 20:08:02 +0000 |
342 | +++ gwibber/microblog/plugins/twitter/__init__.py 2011-02-16 07:38:14 +0000 |
343 | @@ -33,6 +33,8 @@ |
344 | "private", |
345 | "public", |
346 | "delete", |
347 | + "follow", |
348 | + "unfollow", |
349 | "retweet", |
350 | "like", |
351 | "send_thread", |
352 | @@ -41,6 +43,8 @@ |
353 | "sinceid", |
354 | "lists", |
355 | "list", |
356 | + "friends", |
357 | + "followers", |
358 | ], |
359 | |
360 | "default_streams": [ |
361 | @@ -48,6 +52,8 @@ |
362 | "responses", |
363 | "private", |
364 | "lists", |
365 | + "friends", |
366 | + "followers", |
367 | ], |
368 | } |
369 | |
370 | @@ -109,7 +115,7 @@ |
371 | "url": "/".join((URL_PREFIX, user["screen_name"])), |
372 | "is_me": user["screen_name"] == self.account["username"], |
373 | } |
374 | - |
375 | + |
376 | def _message(self, data): |
377 | if type(data) == type(None): |
378 | return [] |
379 | @@ -189,7 +195,7 @@ |
380 | request = oauth.OAuthRequest.from_consumer_and_token(self.consumer, self.token, |
381 | http_method=post and "POST" or "GET", http_url=url, parameters=util.compact(args)) |
382 | request.sign_request(self.sigmethod, self.consumer, self.token) |
383 | - |
384 | + |
385 | if post: |
386 | data = network.Download(request.to_url(), util.compact(args), post).get_json() |
387 | else: |
388 | @@ -215,9 +221,21 @@ |
389 | logstr = """%s: %s - %s""" % (PROTOCOL_INFO["name"], _("Request failed"), data) |
390 | log.logger.error("%s", logstr) |
391 | return [{"error": {"type": "request", "account": self.account, "message": data}}] |
392 | - |
393 | + |
394 | + if parse == "follow" or parse == "unfollow": |
395 | + if isinstance(data, dict) and data.get("error", 0): |
396 | + logstr = """%s: %s - %s""" % (PROTOCOL_INFO["name"], _("Unble to %s user" % parse), data["error"]) |
397 | + log.logger.error("%s", logstr) |
398 | + return [{"error": {"type": "auth", "account": self.account, "message": data["error"]}}] |
399 | + else: |
400 | + return [["friendships", {"type": parse, "account": self.account["id"], "service": self.account["service"],"user_id": data["id"], "nick": data["screen_name"]}]] |
401 | + |
402 | if parse == "list": |
403 | return [self._list(l) for l in data["lists"]] |
404 | + |
405 | + if parse == "friends" or parse == "followers": |
406 | + return data |
407 | + |
408 | if single: return [getattr(self, "_%s" % parse)(data)] |
409 | if parse: return [getattr(self, "_%s" % parse)(m) for m in data] |
410 | else: return [] |
411 | @@ -268,6 +286,12 @@ |
412 | def like(self, message): |
413 | return self._get("favorites/create/%s.json" % message["mid"], None, post=True, do=1) |
414 | |
415 | + def follow(self, screen_name): |
416 | + return self._get("friendships/create.json", screen_name=screen_name, post=True, parse="follow") |
417 | + |
418 | + def unfollow(self, screen_name): |
419 | + return self._get("friendships/destroy.json", screen_name=screen_name, post=True, parse="unfollow") |
420 | + |
421 | def send(self, message): |
422 | return self._get("statuses/update.json", post=True, single=True, |
423 | status=message) |
424 | @@ -279,3 +303,44 @@ |
425 | def send_thread(self, message, target): |
426 | return self._get("statuses/update.json", post=True, single=True, |
427 | status=message, in_reply_to_status_id=target["mid"]) |
428 | + |
429 | +# |
430 | +# If there is a nicer way of doing this, I'll be happy to implement it :) |
431 | +# In case you are Justin Bieber or Lady Gaga, you will have to wait a long time |
432 | +# in order for your followers to load. |
433 | +# |
434 | +# -- BigW 2010/11/17 |
435 | + |
436 | + def friends(self): |
437 | + data = self._get("friends/ids/%s.json" % self.account["username"], cursor=-1, parse="followers") |
438 | + ids = data["ids"] |
439 | + cursor = data["next_cursor"] |
440 | + while cursor > 0: |
441 | + data = self._get("friends/ids/%s.json" % self.account["username"], cursor=cursor, parse="followers") |
442 | + ids = ids + data["ids"] |
443 | + cursor = data["next_cursor"] |
444 | + |
445 | + f = [( |
446 | + "friends", |
447 | + {"service": "twitter", |
448 | + "account": self.account["id"], |
449 | + "ids": ids,} |
450 | + )] |
451 | + return f |
452 | + |
453 | + def followers(self): |
454 | + data = self._get("followers/ids/%s.json" % self.account["username"], cursor=-1, parse="followers") |
455 | + ids = data["ids"] |
456 | + cursor = data["next_cursor"] |
457 | + while cursor > 0: |
458 | + data = self._get("followers/ids/%s.json" % self.account["username"], cursor=cursor, parse="followers") |
459 | + ids = ids + data["ids"] |
460 | + cursor = data["next_cursor"] |
461 | + |
462 | + f = [( |
463 | + "followers", |
464 | + {"service": "twitter", |
465 | + "account": self.account["id"], |
466 | + "ids": ids,} |
467 | + )] |
468 | + return f |
469 | |
470 | === modified file 'gwibber/microblog/storage.py' |
471 | --- gwibber/microblog/storage.py 2010-10-15 03:55:43 +0000 |
472 | +++ gwibber/microblog/storage.py 2011-02-16 07:38:14 +0000 |
473 | @@ -4,6 +4,7 @@ |
474 | import gtk, gobject, dbus, dbus.service |
475 | import util, util.keyring, atexit |
476 | from util import log |
477 | +from util.const import * |
478 | from dbus.mainloop.glib import DBusGMainLoop |
479 | |
480 | DBusGMainLoop(set_as_default=True) |
481 | @@ -50,7 +51,32 @@ |
482 | @dbus.service.method("com.Gwibber.Messages", in_signature="s", out_signature="s") |
483 | def Get(self, id): |
484 | results = self.db.execute("SELECT data FROM messages WHERE id=?", (id,)).fetchone() |
485 | - if results: return results[0] |
486 | + # |
487 | + # Inject is_friend and is_follower in the message data. |
488 | + # |
489 | + if results: |
490 | + data = json.loads(results[0]) |
491 | + # |
492 | + # Messages from search results have different user_id's, see twitterAPI |
493 | + # bug: http://code.google.com/p/twitter-api/issues/detail?id=214 |
494 | + # So we try to find a match based on screen_name/nick OR user_id. |
495 | + # |
496 | + tmpres = self.db.execute(""" |
497 | + SELECT user_id, friend, follower FROM friends WHERE |
498 | + account = '%s' AND service = '%s' AND (user_id = %s OR nick = '%s')""" % |
499 | + (data["account"], data["service"], data["sender"]["id"], data["sender"]["nick"])).fetchone() |
500 | + if tmpres: |
501 | + data["sender"]["is_friend"] = tmpres[1] |
502 | + data["sender"]["is_follower"] = tmpres[2] |
503 | + else: |
504 | + # |
505 | + # For searches: this is also set if we never reveived any messages |
506 | + # from a friend or follower and we don't have their nickname in the DB. |
507 | + # |
508 | + data["sender"]["is_friend"] = 0 |
509 | + data["sender"]["is_follower"] = 0 |
510 | + results = json.dumps(data) |
511 | + return results |
512 | return "" |
513 | |
514 | class SearchManager(dbus.service.Object): |
515 | @@ -201,6 +227,31 @@ |
516 | results = self.db.execute(query, (time, transient)) |
517 | return "[%s]" % ", ".join([i[0] for i in results.fetchall()]) |
518 | |
519 | + # |
520 | + # Nasty little hack that will inject is_friend and is_follower in the |
521 | + # sender data. This probably needs some serious rewriting. |
522 | + # |
523 | + # I am keeping this here as a placeholder if we want to distinguish |
524 | + # friends/followers while rendering streams. |
525 | + # |
526 | + # -- BigW |
527 | + # |
528 | + #tmp = [] |
529 | + #for i in results.fetchall(): |
530 | + # data = json.loads(i[0]) |
531 | + # tmpres = self.db.execute(""" |
532 | + # SELECT user_id, friend, follower FROM friends WHERE |
533 | + # account = '%s' AND service = '%s' AND user_id = %s""" % |
534 | + # (data["account"], data["service"], data["sender"]["id"])).fetchone() |
535 | + # if tmpres: |
536 | + # data["sender"]["is_friend"] = tmpres[1] |
537 | + # data["sender"]["is_follower"] = tmpres[2] |
538 | + # else: |
539 | + # data["sender"]["is_friend"] = 0 |
540 | + # data["sender"]["is_follower"] = 0 |
541 | + # tmp.append(json.dumps(data)) |
542 | + #return "[%s]" % ", ".join([i for i in tmp]) |
543 | + |
544 | @dbus.service.method("com.Gwibber.Streams", in_signature="s", out_signature="s") |
545 | def Create(self, search): |
546 | with self.db: |
547 | @@ -340,3 +391,95 @@ |
548 | data["send_enabled"] = True |
549 | if data: |
550 | self.Update(json.dumps(data)) |
551 | + |
552 | +class FriendsManager(dbus.service.Object): |
553 | + __dbus_object_path__ = "/com/gwibber/Friends" |
554 | + |
555 | + def __init__(self, db): |
556 | + self.bus = dbus.SessionBus() |
557 | + bus_name = dbus.service.BusName("com.Gwibber.Friends", bus=self.bus) |
558 | + dbus.service.Object.__init__(self, bus_name, self.__dbus_object_path__) |
559 | + |
560 | + self.db = db |
561 | + |
562 | + if not self.db.execute("PRAGMA table_info(friends)").fetchall(): |
563 | + log.logger.debug("Friends table not found.") |
564 | + self.setup_table() |
565 | + |
566 | + def setup_table(self): |
567 | + log.logger.debug("Creating new friends table.") |
568 | + with self.db: |
569 | + self.db.execute(""" |
570 | + CREATE TABLE friends ( |
571 | + account text, |
572 | + service text, |
573 | + user_id int, |
574 | + friend int, |
575 | + follower int, |
576 | + name text, |
577 | + url text, |
578 | + image text, |
579 | + nick text, |
580 | + location text)""") |
581 | + self.db.execute("CREATE UNIQUE INDEX friends_idx ON friends (account, service, user_id)") |
582 | + |
583 | + @dbus.service.signal("com.Gwibber.Friends", signature="ss") |
584 | + def Friend(self, change, data): pass |
585 | + |
586 | + @dbus.service.signal("com.Gwibber.Friends", signature="ssds") |
587 | + def Follow(self, account, service, user_id, nick = None): |
588 | + with sqlite3.connect(SQLITE_DB_FILENAME) as db: |
589 | + if nick: |
590 | + query = """UPDATE friends SET friend = 1, nick = '%s' |
591 | + WHERE account = '%s' AND service = '%s' |
592 | + AND user_id = '%s'""" % (nick, account, service, user_id) |
593 | + else: |
594 | + query = """UPDATE friends SET friend = 1 |
595 | + WHERE account = '%s' AND service = '%s' |
596 | + AND user_id = '%s'""" % (account, service, user_id) |
597 | + |
598 | + result = db.execute(query) |
599 | + if not result.rowcount: |
600 | + if nick: |
601 | + result = db.execute("INSERT INTO friends (account, service, user_id, nick, friend) VALUES (?, ?, ?, ?, 1)", (account, service, user_id, nick)) |
602 | + else: |
603 | + result = db.execute("INSERT INTO friends (account, service, user_id, friend) VALUES (?, ?, ?, 1)", (account, service, user_id)) |
604 | + |
605 | + if result: return result |
606 | + return "" |
607 | + |
608 | + @dbus.service.signal("com.Gwibber.Friends", signature="ssds") |
609 | + def Unfollow(self, account, service, user_id, nick = None): |
610 | + with sqlite3.connect(SQLITE_DB_FILENAME) as db: |
611 | + if nick: |
612 | + query = """UPDATE friends SET friend = 0, nick = '%s' |
613 | + WHERE account = '%s' AND service = '%s' |
614 | + AND user_id = '%s'""" % (nick, account, service, user_id) |
615 | + else: |
616 | + query = """UPDATE friends SET friend = 0 |
617 | + WHERE account = '%s' AND service = '%s' |
618 | + AND user_id = '%s'""" % (account, service, user_id) |
619 | + |
620 | + result = db.execute(query) |
621 | + if not result.rowcount: |
622 | + if nick: |
623 | + result = db.execute("INSERT INTO friends (account, service, user_id, nick, friend) VALUES (?, ?, ?, ?, 0)", (account, service, user_id, nick)) |
624 | + else: |
625 | + result = db.execute("INSERT INTO friends (account, service, user_id, friend) VALUES (?, ?, ?, 0)", (account, service, user_id)) |
626 | + |
627 | + if result: return result |
628 | + return "" |
629 | + |
630 | + @dbus.service.method("com.Gwibber.Friends", in_signature="s", out_signature="s") |
631 | + def IsFriend(self, id): |
632 | + with sqlite3.connect(SQLITE_DB_FILENAME) as db: |
633 | + results = db.execute("SELECT user_id FROM friends WHERE user_id=? AND friend=1", (id,)).fetchone() |
634 | + if results: return results[0] |
635 | + return "" |
636 | + |
637 | + @dbus.service.method("com.Gwibber.Friends", in_signature="s", out_signature="s") |
638 | + def IsFollower(self, id): |
639 | + with sqlite3.connect(SQLITE_DB_FILENAME) as db: |
640 | + results = db.execute("SELECT user_id FROM friends WHERE user_id=? AND follower=1", (id,)).fetchone() |
641 | + if results: return results[0] |
642 | + return "" |
643 | |
644 | === modified file 'gwibber/microblog/util/const.py' |
645 | --- gwibber/microblog/util/const.py 2010-12-14 20:33:03 +0000 |
646 | +++ gwibber/microblog/util/const.py 2011-02-16 07:38:14 +0000 |
647 | @@ -238,6 +238,30 @@ |
648 | "stream": null, |
649 | "transient": false |
650 | }, |
651 | + |
652 | + "follow": { |
653 | + "account_tree": false, |
654 | + "dynamic": false, |
655 | + "enabled": null, |
656 | + "first_only": false, |
657 | + "function": null, |
658 | + "return_value": false, |
659 | + "search": false, |
660 | + "stream": null, |
661 | + "transient": false |
662 | + }, |
663 | + |
664 | + "unfollow": { |
665 | + "account_tree": false, |
666 | + "dynamic": false, |
667 | + "enabled": null, |
668 | + "first_only": false, |
669 | + "function": null, |
670 | + "return_value": false, |
671 | + "search": false, |
672 | + "stream": null, |
673 | + "transient": false |
674 | + }, |
675 | |
676 | "search": { |
677 | "account_tree": false, |
678 | @@ -333,6 +357,32 @@ |
679 | "search": false, |
680 | "stream": "user", |
681 | "transient": true |
682 | + }, |
683 | + |
684 | + "friends": { |
685 | + "account_tree": false, |
686 | + "dynamic": false, |
687 | + "enabled": "recieve", |
688 | + "first_only": false, |
689 | + "function": null, |
690 | + "return_value": true, |
691 | + "search": false, |
692 | + "stream": null, |
693 | + "transient": false, |
694 | + "interval": 50 |
695 | + }, |
696 | + |
697 | + "followers": { |
698 | + "account_tree": false, |
699 | + "dynamic": false, |
700 | + "enabled": "recieve", |
701 | + "first_only": false, |
702 | + "function": null, |
703 | + "return_value": true, |
704 | + "search": false, |
705 | + "stream": null, |
706 | + "transient": false, |
707 | + "interval": 50 |
708 | } |
709 | } |
710 | """ |
711 | |
712 | === modified file 'ui/templates/base.mako' |
713 | --- ui/templates/base.mako 2011-02-15 14:02:42 +0000 |
714 | +++ ui/templates/base.mako 2011-02-16 07:38:14 +0000 |
715 | @@ -12,6 +12,15 @@ |
716 | % endif |
717 | </%def> |
718 | |
719 | +<%def name="recipient_profile_url(data)" filter="trim"> |
720 | + % if "user_messages" in services[data["service"]]["features"]: |
721 | + gwibber:/user?acct=${data["account"]}&name=${data["recipient"]["nick"]} |
722 | + % else: |
723 | + ${data['recipient']['url']} |
724 | + % endif |
725 | +</%def> |
726 | + |
727 | + |
728 | <%def name="comment(data)"> |
729 | % if "sender" in data: |
730 | % if "name" in data["sender"]: |
731 | @@ -75,7 +84,7 @@ |
732 | % endif |
733 | % else: |
734 | % if "image" in data["recipient"]: |
735 | - <a href="${profile_url(data)}"> |
736 | + <a href="${recipient_profile_url(data)}"> |
737 | <div class="imgbox" title="${data["sender"].get("nick", "")}" style="background-image: url(${data["sender"]["image"]});"> |
738 | <div class="imgbox" title="${data["recipient"].get("nick", "")}" style="margin: 25px 0px 0px 25px; width: 25px; height: 25px; background-image: url(${data["recipient"]["image"]});"></div></div> |
739 | </a> |
740 | |
741 | === modified file 'ui/themes/ubuntu/main.css' |
742 | --- ui/themes/ubuntu/main.css 2010-02-10 22:33:02 +0000 |
743 | +++ ui/themes/ubuntu/main.css 2011-02-16 07:38:14 +0000 |
744 | @@ -1,5 +1,7 @@ |
745 | * { |
746 | color: black; |
747 | + font-family: ubuntu; |
748 | + font-size: 14px; |
749 | } |
750 | |
751 | .imgbox { |
Thanks for taking the time to submit this patch, unfortunately Gwibber has gone through extensive changes recently and your patch no longer applies to the latest codebase.
Unfortunately, the feature you were attempting to add is currently only half-implemented. We have follow/unfollow support in the Twitter backend but there is not any UI for it.
I invite you to take a look at the latest lp:gwibber and see if you might be interested in hooking up the UI for this ;-)