Merge lp:~seriy-pr/gwibber/vkontakte-ru-plugin into lp:gwibber

Proposed by Sergey Prokhorov
Status: Rejected
Rejected by: Ken VanDine
Proposed branch: lp:~seriy-pr/gwibber/vkontakte-ru-plugin
Merge into: lp:gwibber
Diff against target: 1212 lines (+1149/-2)
7 files modified
.bzrignore (+0/-2)
gwibber/microblog/plugins/vkontakte/__init__.py (+593/-0)
gwibber/microblog/plugins/vkontakte/gtk/vkontakte/__init__.py (+185/-0)
gwibber/microblog/plugins/vkontakte/ui/gwibber-accounts-vkontakte.ui (+227/-0)
gwibber/microblog/plugins/vkontakte/ui/icons/scalable/vkontakte.svg (+55/-0)
gwibber/microblog/plugins/vkontakte/vk_api_wrapper.py (+87/-0)
gwibber/microblog/util/const.py.in (+2/-0)
To merge this branch: bzr merge lp:~seriy-pr/gwibber/vkontakte-ru-plugin
Reviewer Review Type Date Requested Status
Ken VanDine Needs Fixing
Review via email: mp+61045@code.launchpad.net

This proposal supersedes a proposal from 2011-01-27.

Description of the change

Added plugin for vkontakte.ru ( http://vk.com/ ) - most popular social network in Russia, Ukraine and many ex-USSR countries (>100 mln users, http://www.alexa.com/siteinfo/vkontakte.ru #44).

This branch contains a fix of whishlist bug #572753
Also there is a related blueprint https://blueprints.edge.launchpad.net/gwibber/+spec/vkontakte.ru

Some peoples report that plugin works for them: https://bugs.edge.launchpad.net/gwibber/+bug/572753/comments/18 and http://forum.ubuntu.ru/index.php?topic=65621.msg1004248#msg1004248 + there is non-oficial DEB build for it: http://www.mediafire.com/?z2ye2u9chtda9vu

To post a comment you must log in.
969. By Sergey Prokhorov

Simplify lazy loading mechanizm

970. By Sergey Prokhorov

Linkify comments and links description

971. By Sergey Prokhorov

Add mentions support

972. By Sergey Prokhorov

Fix notifications

973. By Sergey Prokhorov

Fix right-aligned text on "\ufeff" char appears on message

974. By Sergey Prokhorov

Add support for vkontakte hashtags.

975. By Sergey Prokhorov

Merged with trunk

976. By Sergey Prokhorov

Merged with head

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

I think this would be best as a standalone plugin, packaged separately from gwibber. We want to move most of the plugins out of the gwibber source and into separate projects. You can look at https://launchpad.net/gwibber-service-sina as an example. I think we will be keeping twitter, facebook and g+ (when it exists) in gwibber mainline and move the rest to new source packages. If you have questions, please join us in #gwibber on Freenode.

Thanks for the awesome work!

review: Needs Fixing
Revision history for this message
Sergey Prokhorov (seriy-pr) wrote :

> I think this would be best as a standalone plugin, packaged separately from
> gwibber. We want to move most of the plugins out of the gwibber source and
> into separate projects. You can look at https://launchpad.net/gwibber-
> service-sina as an example. I think we will be keeping twitter, facebook and
> g+ (when it exists) in gwibber mainline and move the rest to new source
> packages. If you have questions, please join us in #gwibber on Freenode.
>
> Thanks for the awesome work!

Ok, I think this is good idea. Also, we already has ppa for vkontakte plugin https://launchpad.net/~gwibber-vkontakte-plugin/+archive/ppa. I begin to create a separate project for plugin next week I think.

977. By Sergey Prokhorov

merged with trunk

978. By Sergey Prokhorov

Make code less ugly + replace <br> with <br/>

979. By Sergey Prokhorov

merged with trunk

980. By Sergey Prokhorov

move icons to plugin directory

981. By Sergey Prokhorov

setup.py don't used in gwibber now

982. By Sergey Prokhorov

merged with trunk

983. By Sergey Prokhorov

Fix logging issue

984. By Sergey Prokhorov

Fix indentation

985. By Sergey Prokhorov

Fix logger issue

986. By Sergey Prokhorov

More logging fixes

987. By Sergey Prokhorov

fix code conventions

988. By Sergey Prokhorov

Fix issue of renaming the vk api wrapper

989. By Sergey Prokhorov

Fix indentation on gui module

990. By Sergey Prokhorov

Compatible with 3.3.92 on ubuntu 12.04

Unmerged revisions

990. By Sergey Prokhorov

Compatible with 3.3.92 on ubuntu 12.04

989. By Sergey Prokhorov

Fix indentation on gui module

988. By Sergey Prokhorov

Fix issue of renaming the vk api wrapper

987. By Sergey Prokhorov

fix code conventions

986. By Sergey Prokhorov

More logging fixes

985. By Sergey Prokhorov

Fix logger issue

984. By Sergey Prokhorov

Fix indentation

983. By Sergey Prokhorov

Fix logging issue

982. By Sergey Prokhorov

merged with trunk

981. By Sergey Prokhorov

setup.py don't used in gwibber now

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file '.bzrignore'
--- .bzrignore 2012-01-12 17:31:35 +0000
+++ .bzrignore 2012-03-17 22:54:18 +0000
@@ -1,5 +1,3 @@
1unknown:
2.bzrignore
3gwibber/semantic.cache1gwibber/semantic.cache
4semantic.cache2semantic.cache
5*.gmo3*.gmo
64
=== added directory 'gwibber/microblog/plugins/vkontakte'
=== added file 'gwibber/microblog/plugins/vkontakte/__init__.py'
--- gwibber/microblog/plugins/vkontakte/__init__.py 1970-01-01 00:00:00 +0000
+++ gwibber/microblog/plugins/vkontakte/__init__.py 2012-03-17 22:54:18 +0000
@@ -0,0 +1,593 @@
1# -*- coding: utf-8 -*-
2'''
3Created on 05.12.2010
4
5@author: Sergey Prokhorov <root@seriyps.ru>
6'''
7import json
8import inspect
9from gwibber.microblog.util.const import *
10from gwibber.microblog import util
11import time
12import urllib
13import re
14import logging
15
16
17from vk_api_wrapper import VkApi, VkException
18# Try to import * from custom, install custom.py to include packaging
19# customizations like distro API keys, etc
20try:
21 from gwibber.microblog.util.custom import *
22except:
23 pass
24
25logger = logging.getLogger("Vkontakte")
26logger.debug("Initializing.")
27
28#XXX: remove me! see gwibber.microblog.util.const.VK_APP_ID
29VK_APP_ID = "2036925"
30
31PROTOCOL_INFO = {
32 "name": "Vkontakte",
33 "version": "0.3",
34
35 "config": [
36 "color",
37 "receive_enabled",
38 "receive_groups",
39 "send_enabled",
40 "username",
41 "uid",
42 "private:access_token"
43 ],
44
45 "authtype": "vkontakte",
46 "color": "#45688E", # #6D8FB3
47
48 "features": [
49 "send",
50 "reply",
51 "receive",
52 #"thread",
53 "delete",
54 #"send_thread",
55 "like",
56 "sincetime"
57 ],
58
59 "default_streams": [
60 "receive",
61 "images",
62 "links",
63 "videos",
64 ]
65}
66
67URL_PREFIX = 'http://vkontakte.ru/'
68
69
70class attachment_processor(object):
71 """Generate attachment fields
72 http://vkontakte.ru/developers.php?o=-1&p=%D0%9E%D0%BF%D0%B8%D1%81%D0%B0%D0%BD%D0%B8%D0%B5+%D0%BF%D0%BE%D0%BB%D1%8F+attachment
73 """
74
75 def __init__(self, client):
76 self.client = client
77
78 def _process_photo(self, attachment):
79 return ("photo",
80 photoAttachment(
81 url="{0}photo{1}_{2}".format(URL_PREFIX,
82 attachment["owner_id"],
83 attachment["pid"]),
84 picture=attachment["src"]))
85
86 _process_posted_photo = _process_photo
87
88 def _process_graffiti(self, attachment):
89 return ("photo",
90 photoAttachment(
91 url="{0}graffiti{1}?from_id={2}".format(URL_PREFIX,
92 attachment["gid"],
93 attachment["owner_id"]),
94 picture=attachment["src"]))
95
96 def _process_video(self, attachment):
97 lnk = "{0}video{1}_{2}".format(URL_PREFIX, attachment["owner_id"],
98 attachment["vid"])
99 video_data = self.client.api.video.get(videos="{0}_{1}".format(
100 attachment["owner_id"],
101 attachment["vid"]))["response"]
102 return ("video",
103 videoAttachment(
104 url=lnk,
105 name=attachment["title"],
106 source=lnk,
107 picture=video_data[1]["image"]))
108
109 def _process_link(self, attachment):
110 l = linkAttachment(
111 name=attachment["title"],
112 url=attachment["url"],
113 description=util.linkify(attachment["description"], escape=False))
114 if "image_src" in attachment:
115 l.picture = attachment["image_src"]
116 return "link", l
117
118 def process(self, attachment, message):
119 """find is processor has matched handler, and if yes,
120 generate attachment with this handler and merge it with
121 original message"""
122 type_ = attachment["type"]
123 processor_name = "_process_{0}".format(type_)
124 if hasattr(self, processor_name):
125 key, val = getattr(self, processor_name)(attachment[type_])
126 setattr(message, key, val)
127 message.type = key
128 return message
129
130
131###########
132# MAGICK!!!
133###########
134'''vkontakte API don't return comments for wall posts,
135but return comments count, so, we need download comments
136for each posts with comments count > 0 by separate API query.
137But, surprise! API don't return commenters profile data, only
138commenter ID. That means, that we need download commenter profile
139for each commenter. Only one good thing is that we can
140download many profiles by single query. So, when process
141wall posts, we insert "lazyUserLoader" fake objects in places, where
142commenter profile must be. And ALL profiles loads only when
143some slave object need profile data.
144'''
145
146
147class lazy_profiles_loader(object):
148 '''Dispatcher, that store slaves and start
149 worker when one of slaves need data'''
150
151 def __init__(self, client):
152 '''
153 @param client: Client's instance'''
154 self.client = client
155 self.masks = {}
156 self.results = {}
157
158 def new_mask(self, id):
159 mask = lazyUserLoader(id, self)
160 self.masks[id] = mask
161 return mask
162
163 def run(self):
164 '''magick (see comments upper) lazy worker (transform map of
165 profile id's to map of profiles)'''
166 ids = [str(k) for k in self.masks.keys()]
167 uids = ",".join(ids)
168 try:
169 profiles = self.client.api.getProfiles(
170 uids=uids, fields="uid,photo,first_name,last_name")
171 except VkException, e:
172 self.client._format_error(e) #log message to console
173 return {}
174 for profile in profiles['response']:
175 self.results[profile["uid"]] = self.client._user(profile)
176
177 def get(self, id):
178 if not self.results:
179 self.run()
180 return self.results.get(id, None)
181
182
183###########
184# END MAGICK!!!
185###########
186
187###########
188# declarative description of Gwibber message JSON structure
189# XXX: this can/must be reused in other Gwibber plugins!!!
190###########
191
192# base class
193class jsonable(object):
194 """
195 Allow subclass instances be represented as combination
196 of python std types (list, str, dict, int, float) that
197 can be used by json.dumps()
198 eg
199 ========================
200 class C2(jsonable):
201 prop0=""
202
203 class C1(jsonable):
204 prop1="" # str
205 prop2=0 # int
206 prop3=0.0 # float
207 prop4={} # as is
208 prop5=("choice1", "choice2", "choice3") # one of
209 prop6=[C2, ] # list of
210
211 c1=C1(prop1="p1_val") # pass to constructor
212 c1.prop2=100500 # pass as property assignment
213 ...
214 prop5=[C2(prop0="c2_p0_val1"), C2(prop0="c2_p0_val2")]
215
216 >>> c1.to_jsonable() # return dict object
217 {
218 "prop1": "p1_val",
219 "prop2": 100500,
220 ...
221 "prop6":[
222 {
223 "prop0": "c2_p0_val1"
224 },
225 {
226 "prop0": "c2_p0_val2"
227 }
228 ]
229 }
230 ===========================
231 Idea came from http://code.google.com/p/gdata-python-client/source/browse/src/atom/core.py
232 "XmlElement" class
233 """
234 _members = None
235
236 def __init__(self, **kwargs):
237 if self.__class__._members is None:
238 self.__class__._members = tuple(self.__class__._list_members())
239 for member_name, member_type in self.__class__._members:
240 if member_name in kwargs:
241 setattr(self, member_name, kwargs[member_name])
242 else:
243 if isinstance(member_type, list):
244 setattr(self, member_name, [])
245 else:
246 setattr(self, member_name, None)
247
248 @classmethod
249 def _list_members(cls):
250 """Introspect class properties"""
251 for pair in inspect.getmembers(cls):
252 if not pair[0].startswith('_'):
253 member_type = pair[1]
254 if (isinstance(member_type, (tuple, list, str, unicode, dict,
255 int, float, bool))
256 or (inspect.isclass(member_type)
257 and issubclass(member_type, jsonable))):
258 yield pair
259
260 def to_jsonable(self):
261 """Recursively transform objects hierarchy to
262 hierarchy of python std types"""
263 res = {}
264 for m_name, m_type in self._members:
265 child = getattr(self, m_name)
266 if child is None:
267 continue
268 if isinstance(m_type, (str, unicode)):
269 res[m_name] = unicode(child)
270 elif isinstance(m_type, int):
271 res[m_name] = int(child)
272 elif isinstance(m_type, float):
273 res[m_name] = float(child)
274 elif isinstance(m_type, bool):
275 res[m_name] = bool(child)
276 elif isinstance(m_type, dict): # copy as is
277 res[m_name] = child
278 elif isinstance(m_type, list):
279 res[m_name] = []
280 for subchild in child:
281 res[m_name].append(subchild.to_jsonable())
282 elif isinstance(m_type, tuple):
283 if child in m_type:
284 res[m_name] = child
285 elif inspect.isclass(m_type) and issubclass(m_type, jsonable):
286 res[m_name] = child.to_jsonable() # recursively
287 return res
288
289 def __str__(self):
290 return str(self.to_jsonable())
291
292
293# Gwibber message structure hierarchy:
294
295class errMessage(jsonable):
296 type = ""
297 account = {}
298 message = ""
299
300
301class error(jsonable):
302 error = errMessage
303
304
305class user(jsonable):
306 name = ""
307 id = ""
308 is_me = False
309 image = ""
310 url = ""
311
312
313class lazyUserLoader(jsonable):
314 """Loads user profile only when to_json() called"""
315 def __init__(self, id, master, **kwargs):
316 super(lazyUserLoader, self).__init__(**kwargs)
317 self._id = id
318 self._master = master
319
320 def to_jsonable(self):
321 res = self._master.get(self._id)
322 return res.to_jsonable() if isinstance(res, jsonable) else {}
323
324
325class like(jsonable):
326 count = 0
327
328
329class photoAttachment(jsonable):
330 url = ""
331 picture = ""
332
333
334class videoAttachment(jsonable):
335 url = ""
336 name = ""
337 source = ""
338 picture = ""
339
340
341class linkAttachment(jsonable):
342 name = ""
343 url = ""
344 description = ""
345 picture = ""
346
347
348class comment(jsonable):
349 text = ""
350 time = ""
351 sender = lazyUserLoader
352
353
354class message(jsonable):
355 mid = ""
356 service = ""
357 account = ""
358 time = 0
359 sender = user
360 to_me = False
361 url = ""
362 text = ""
363 html = ""
364 content = ""
365 likes = like
366 photo = photoAttachment
367 video = videoAttachment
368 link = linkAttachment
369 type = ("photo", "video", "link")
370 comments = [comment, ]
371
372
373###########
374# END declarative description of Gwibber message JSON structure
375###########
376
377class Client(object):
378
379 mention_re = re.compile(r"\[id(\d+)\|(.+)\]", re.U)
380 hash_re = re.compile("#([A-Za-z0-9_]+)")
381
382 def mentify(self, text):
383 return self.mention_re.sub(r'<a href="%sid\1">\2</a>' % URL_PREFIX,
384 text)
385
386 def hashify(self, text):
387 return self.hash_re.sub(
388 r'#<a class="hash" href="{0}feed?q=%23\1&section=search">\1</a>'.format(
389 URL_PREFIX), text)
390
391 def __init__(self, acct):
392 self.account = acct
393 self.access_token = acct.get("access_token")
394 self.api = VkApi(access_token=self.access_token)
395 self.attachment_processor = attachment_processor(self)
396
397 def __call__(self, opname, **args):
398 try:
399 ret = [item for item in getattr(self, opname)(**args)
400 if isinstance(item, jsonable)] # read all yield's
401 #for i in ret:
402 # print str(i)
403 return [item.to_jsonable() for item in ret]
404 except VkException, e:
405 return [self._format_error(e).to_jsonable()]
406
407 def _format_error(self, e, type=None, message=None):
408 logger.error('Vkontakte error #%d: "%s" in url %s',
409 e.code, e.msg, e.url)
410 if e.code in (3, 5, 7):
411 _type = "auth"
412 #elif e.code in (2, 4, 100):
413 # _type = "internal"
414 else:
415 _type = "unknown"
416 return error(error=errMessage(type=type or _type,
417 account=self.account,
418 message=message or e.msg))
419
420 def _user(self, acc_data):
421 """Extract single user data from API response"""
422 if "gid" in acc_data:
423 return user(name=acc_data["name"],
424 id=str(acc_data["gid"] * -1),
425 is_me=False,
426 image=acc_data["photo"],
427 url="{0}sclub{1}".format(URL_PREFIX, acc_data["gid"]))
428 else:
429 return user(name=u"{0} {1}".format(acc_data["first_name"],
430 acc_data["last_name"]),
431 id=str(acc_data["uid"]),
432 is_me=acc_data["uid"] == int(self.account["uid"]),
433 image=acc_data["photo"],
434 url="{0}id{1}".format(URL_PREFIX, acc_data["uid"]))
435
436 def _message(self, item, account, comment_author_factory):
437 """Extract single message data from API response"""
438 m = message()
439 m.mid = str(item["post_id"])
440 m.service = "vkontakte"
441 m.account = self.account["id"]
442 # XXX: WTF?!? why we don't move timezone correction to presentation
443 # logic??? We must store to DB absolute UTC timestamp!!!
444 m.time = item["date"] + time.timezone
445 m.sender = self._user(account)
446 m.url = "{0}wall{1}_{2}".format(URL_PREFIX, m.sender.id, item["post_id"])
447 # remove strange align-right chars + opening br tags
448 m.text = item["text"].replace(u"\ufeff", "").replace("<br>", "<br/>")
449 m.html = self.hashify(
450 self.mentify(
451 util.linkify(m.text, escape=False)))
452 m.content = m.html
453
454 # find all mentions in message text where id is mine...
455 m.to_me = len([id for id, _name in self.mention_re.findall(m.text)
456 if id == self.account["uid"]]) > 0
457
458 if item.get("likes", 0):
459 m.likes = like(count=item["likes"]["count"])
460
461 if "attachment" in item:
462 if item["attachment"]["type"] == "audio":
463 return # FIXME: Gwibber don't support audio
464 m = self.attachment_processor.process(item["attachment"], m)
465
466 if item["comments"]["count"] > 0:
467 try:
468 comments = self.api.wall.getComments(owner_id=m.sender.id,
469 post_id=item["post_id"],
470 sort="asc",
471 count=50)
472 except VkException, e:
473 return self._format_error(e) # XXX: may be return "m" ?
474 if "response" in comments:
475 for comm in comments["response"]:
476 if not isinstance(comm, dict):
477 # drop first(?) element, because it is
478 # integer count of comments
479 continue
480 mask = comment_author_factory.new_mask(comm["uid"])
481 m.comments.append(
482 comment(
483 text=self.hashify(
484 self.mentify(
485 util.linkify(comm["text"]))),
486 time=comm["date"],
487 sender=mask
488 )
489 )
490 return m
491
492 def receive(self, since=None):
493 '''Retrieve messages from friend's and own walls.'''
494 if not since:
495 since = int(time.time() - 60 * 60 * 24) # 24 hours
496 response = self.api.newsfeed.get(filters="post",
497 start_time=since)["response"]
498
499 comment_author_factory = lazy_profiles_loader(self)
500
501 profiles_by_id = {} # index user and group profiles by user id
502 for _acc in response["profiles"]:
503 profiles_by_id[_acc["uid"]] = _acc
504 if self.account["receive_groups"]:
505 for _acc in response["groups"]:
506 profiles_by_id[_acc["gid"] * -1] = _acc
507
508 for item in response["items"]:
509 #source_id > 0 for peoples and < 0 for groups
510 if item["source_id"] < 0 and not self.account["receive_groups"]:
511 continue # skip groups if "receive_groups" option disabled
512 account = profiles_by_id[item["source_id"]]
513 message = self._message(item, account, comment_author_factory)
514 yield message
515
516 for msg in self._own_wall_posts(10, comment_author_factory):
517 yield msg
518
519 def _own_wall_posts(self, count, comment_author_factory):
520 '''Download all posts user post in own wall'''
521 try:
522 response = self.api.wall.get(
523 filter="owner", count=count)["response"]
524 except VkException, e:
525 yield self._format_error(e)
526 return
527 me = None
528 for post in response:
529 if not isinstance(post, dict):
530 # skip first element - number of items
531 continue
532 post["post_id"] = post["id"]
533 post["source_id"] = post["from_id"]
534 if not me:
535 me = self.api.getProfiles(
536 uids=post["source_id"],
537 fields="uid,photo,first_name,last_name")["response"][0]
538 yield self._message(post, me, comment_author_factory)
539
540 def user_messages(self, id=None, count=util.COUNT, since=None):
541 """Messages posted by some user"""
542 raise NotImplementedError
543 #if count>100:#vk limit
544 # count = 100
545 #comment_author_factory = lazy_profiles_loader(self)
546 #return self._own_wall_posts(count, comment_author_factory)
547
548 def send(self, message):
549 """Send new post to own wall"""
550 self.api.wall.post(message=message)
551 return self._own_wall_posts(1, lazy_profiles_loader(self))
552
553 def send_thread(self, message, target):
554 """Send comment with text @message to post @target
555 @param message: message text
556 @param target: target post"""
557 self.api.wall.addComment(owner_id=target["sender"]["id"],
558 post_id=target["mid"],
559 text=message)
560 # Update post data
561 # XXX: this doesn't update Gwibber UI and this is a Gwibber bug...
562 return self._post_by_id(target["sender"]["id"], target["mid"])
563
564 def _post_by_id(self, owner_id, post_id):
565 response = self.api.wall.getById(
566 posts="{0}_{1}".format(owner_id, post_id))["response"]
567 comment_author_factory = lazy_profiles_loader(self)
568 me = None
569 for post in response:
570 if not isinstance(post, dict):
571 # skip first element - number of items
572 continue
573 post["post_id"] = post["id"]
574 post["source_id"] = post["from_id"]
575 if not me:
576 me = self.api.getProfiles(
577 uids=post["source_id"],
578 fields="uid,photo,first_name,last_name")["response"][0]
579 yield self._message(post, me, comment_author_factory)
580
581 def like(self, message):
582 """Like message
583 @param message: target post you like"""
584 self.api.wall.addLike(owner_id=message["sender"]["id"],
585 post_id=message["mid"])
586 return []
587
588 def delete(self, message):
589 """Remove message from you wall
590 @param message: post you want to delete"""
591 self.api.wall.delete(owner_id=message["sender"]["id"],
592 post_id=message["mid"])
593 return []
0594
=== added directory 'gwibber/microblog/plugins/vkontakte/gtk'
=== added file 'gwibber/microblog/plugins/vkontakte/gtk/__init__.py'
=== added directory 'gwibber/microblog/plugins/vkontakte/gtk/vkontakte'
=== added file 'gwibber/microblog/plugins/vkontakte/gtk/vkontakte/__init__.py'
--- gwibber/microblog/plugins/vkontakte/gtk/vkontakte/__init__.py 1970-01-01 00:00:00 +0000
+++ gwibber/microblog/plugins/vkontakte/gtk/vkontakte/__init__.py 2012-03-17 22:54:18 +0000
@@ -0,0 +1,185 @@
1# -*- coding: utf-8 -*-
2'''
3@author: Sergey Prokhorov <me@seriyps.ru>
4'''
5from gi.repository import Gdk, Gtk, WebKit, Pango
6from gi.repository.Gtk import Builder
7from gwibber.microblog.util import resources
8from gwibber.microblog.util.const import *
9from gwibber.microblog.util.keyring import get_from_keyring
10
11try:
12 from gwibber.microblog.plugins.vkontakte.vk_api_wrapper import VkApi
13except ImportError:
14 try:
15 from vkontakte.vk_api_wrapper import VkApi
16 except ImportError:
17 VkApi = None
18
19import urllib
20import urlparse
21import logging
22
23import gettext
24from gettext import gettext as _
25if hasattr(gettext, 'bind_textdomain_codeset'):
26 gettext.bind_textdomain_codeset('gwibber', 'UTF-8')
27gettext.textdomain('gwibber')
28
29logger = logging.getLogger('Vkontakte')
30logger.info('Initializing...')
31
32# XXX: remove me! see gwibber.microblog.util.const.VK_APP_ID
33VK_APP_ID = "2036925"
34
35
36class AccountWidget(Gtk.VBox):
37 """AccountWidget: A widget that provides a user interface for configuring
38 Vkontakte accounts in Gwibber"""
39
40 def __init__(self, account=None, dialog=None):
41 """Creates the account pane for configuring Vkontakte accounts"""
42 Gtk.VBox.__init__(self, False, 20)
43 self._state_init()
44 if account:
45 self.account = account
46 else:
47 self.account = {}
48 self.dialog = dialog
49 self.window = dialog.dialog
50 has_access_token = True
51 if "id" in self.account: # check is current account already authorized
52 has_access_token = get_from_keyring(self.account['id'],
53 'access_token') is not None
54 try: # if account authorized, don't show "authorize" button
55 if (self.account["access_token"]
56 and self.account["username"]
57 and has_access_token
58 and not self.dialog.condition):
59 self._state_authorized()
60 else: # else don't show "authorized" label
61 self._state_not_authorized()
62 except:
63 self._state_not_authorized()
64
65
66 def on_vk_auth_clicked(self, widget, data=None):
67 """Start embed browser and show authorization dialog"""
68 web = WebKit.WebView()
69 web.get_settings().set_property("enable-plugins", False)
70 web.load_html_string(_("<p>Please wait...</p>"), "file:///")
71
72 qstring = urllib.urlencode({ #call to vkontakte API authorization
73 "client_id": VK_APP_ID,
74 "redirect_uri": "http://vkontakte.ru/api/login_success.html", #FIXME: http://api.vkontakte.ru/blank.html don't fire "title-changed"
75 "response_type": "token",
76 "display": "popup",
77 "scope": ",".join(("video", "offline", "wall"))
78 })
79 url = "http://api.vkontakte.ru/oauth/authorize?" + qstring
80 web.set_size_request(550, 440)
81 logger.info("Load url %s", url)
82 web.load_uri(url)
83 #http://api.vkontakte.ru/oauth/authorize?client_id=2036925&response_type=token&scope=video,offline,wal&redirect_uri=http://api.vkontakte.ru/blank.html
84 web.connect("title-changed", self.on_vk_auth_title_change)
85 self._state_show_browser(web)
86
87 def on_vk_auth_title_change(self, web=None, title=None, data=None):
88 """When user confirm or revoke authorization in embed browser"""
89 saved = False
90 url = web.get_main_frame().get_uri()
91 logger.info("title changed...")
92 if "access_token=" in url:
93 """When user successfully authorize our application, extract
94 access_token secret...
95 http://api.vkontakte.ru/blank.html#access_token=55a69...c55&expires_in=0&user_id=535...7"""
96 query_string = url.split("#", 1)[1]
97 data = urlparse.parse_qs(query_string)
98 self.account["access_token"] = str(data["access_token"][0])
99 self.account["uid"] = data["user_id"][0]
100 if VkApi: # try retrieve user name and last name from API
101 profile = (VkApi(access_token=self.account["access_token"])
102 .getProfiles(uids=self.account["uid"],
103 fields="first_name,last_name"))
104 self.account["username"] = u"%(first_name)s %(last_name)s" % (profile['response'][0])
105 else:
106 self.account["username"] = "id%s" % self.account["uid"]
107 saved = self.dialog.on_edit_account_save() # store user account data
108 self._state_authorized()
109 self._state_login_success(saved)
110 self._state_hide_browser(web)
111 if "error=" in url:
112 query_string = url.split("?", 1)[1]
113 desc = urlparse.parse_qs(query_string)["error_description"][0]
114 self._state_not_authorized()
115 self._state_login_failed(_("Authentication error: %s") % desc)
116 self._state_hide_browser(web)
117
118 ### UI-manipulation methods ###
119
120 def _state_init(self):
121 """create UI from UI file"""
122 self._browser_scroll = None
123 self.ui = Gtk.Builder()
124 self.ui.set_translation_domain("gwibber")
125 self.ui.add_from_file(
126 resources.get_ui_asset("gwibber-accounts-vkontakte.ui"))
127 self.ui.connect_signals(self) # attach events (on_vk_auth_clicked)
128 self.vbox_settings = self.ui.get_object("vbox_settings")
129 self.pack_start(self.vbox_settings, False, False, 0)
130 self.show_all()
131
132 def _state_show_browser(self, web):
133 """Show webkit widget, hide settings etc.."""
134 (self.win_w, self.win_h) = self.window.get_size()
135 if not self._browser_scroll:
136 self._browser_scroll = Gtk.ScrolledWindow()
137 self.pack_start(self._browser_scroll, True, True, 0)
138 self._browser_scroll.child = None
139 self._browser_scroll.add(web)
140 self._browser_scroll.set_size_request(550, 440)
141 self.show_all()
142 self.dialog.infobar.hide()
143 self.ui.get_object("vbox1").hide()
144 self.ui.get_object("vbox_advanced").hide()
145
146 def _state_hide_browser(self, web):
147 """Hide webkit, show advanced settings, etc.."""
148 web.hide()
149 self.window.resize(self.win_w, self.win_h)
150 self.ui.get_object("vbox1").show()
151 self.ui.get_object("vbox_advanced").show()
152
153 def _state_login_success(self, saved):
154 """Show 'update' or 'create' buttons if not saved automatically"""
155 if self.dialog.ui and "id" in self.account and not saved:
156 self.dialog.ui.get_object("vbox_save").show()
157 elif self.dialog.ui and not saved:
158 self.dialog.ui.get_object("vbox_create").show()
159
160 def _state_login_failed(self, reason=None):
161 """When user fail or revoke authorization, show popup message"""
162 Gtk.gdk.threads_enter()
163 if not reason:
164 reason = _("Vkontakte authorization failed. Please try again.")
165 else:
166 reason = _(reason)
167 d = Gtk.MessageDialog(None, Gtk.DIALOG_MODAL, Gtk.MESSAGE_ERROR,
168 Gtk.BUTTONS_OK, reason)
169 if d.run():
170 d.destroy()
171 Gtk.gdk.threads_leave()
172
173 def _state_not_authorized(self):
174 """Show 'Authorize with vkontakte' button"""
175 self.ui.get_object("hbox_vk_auth_done").hide()
176 self.ui.get_object("hbox_vk_auth").show()
177 if self.dialog.ui:
178 self.dialog.ui.get_object('vbox_create').hide()
179
180 def _state_authorized(self):
181 """Hide 'Authorize with vkontakte' button, show 'Authorized'"""
182 self.ui.get_object("hbox_vk_auth").hide()
183 self.ui.get_object("vk_auth_done_label").set_label(
184 _("%s has been authorized by Vkontakte") % self.account["username"].encode('utf-8'))
185 self.ui.get_object("hbox_vk_auth_done").show()
0186
=== added directory 'gwibber/microblog/plugins/vkontakte/ui'
=== added file 'gwibber/microblog/plugins/vkontakte/ui/gwibber-accounts-vkontakte.ui'
--- gwibber/microblog/plugins/vkontakte/ui/gwibber-accounts-vkontakte.ui 1970-01-01 00:00:00 +0000
+++ gwibber/microblog/plugins/vkontakte/ui/gwibber-accounts-vkontakte.ui 2012-03-17 22:54:18 +0000
@@ -0,0 +1,227 @@
1<?xml version="1.0" encoding="UTF-8"?>
2<interface>
3 <requires lib="gtk+" version="2.24"/>
4 <object class="GtkVBox" id="vbox_settings">
5 <property name="visible">True</property>
6 <property name="can_focus">False</property>
7 <property name="spacing">6</property>
8 <child>
9 <object class="GtkVBox" id="vbox1">
10 <property name="visible">True</property>
11 <property name="can_focus">False</property>
12 <child>
13 <object class="GtkHBox" id="hbox_vk_auth">
14 <property name="visible">True</property>
15 <property name="can_focus">False</property>
16 <child>
17 <object class="GtkButton" id="vk_auth_button">
18 <property name="label" translatable="yes">_Authorize</property>
19 <property name="visible">True</property>
20 <property name="can_focus">True</property>
21 <property name="receives_default">True</property>
22 <property name="use_action_appearance">False</property>
23 <property name="use_underline">True</property>
24 <signal name="clicked" handler="on_vk_auth_clicked" swapped="no"/>
25 </object>
26 <packing>
27 <property name="expand">True</property>
28 <property name="fill">False</property>
29 <property name="position">0</property>
30 </packing>
31 </child>
32 <child>
33 <object class="GtkLabel" id="vk_auth_label">
34 <property name="visible">True</property>
35 <property name="can_focus">False</property>
36 <property name="label" translatable="yes">Authorize with vkontakte</property>
37 </object>
38 <packing>
39 <property name="expand">True</property>
40 <property name="fill">True</property>
41 <property name="position">1</property>
42 </packing>
43 </child>
44 </object>
45 <packing>
46 <property name="expand">True</property>
47 <property name="fill">True</property>
48 <property name="position">0</property>
49 </packing>
50 </child>
51 <child>
52 <object class="GtkHBox" id="hbox_vk_auth_done">
53 <property name="visible">True</property>
54 <property name="can_focus">False</property>
55 <child>
56 <object class="GtkLabel" id="vk_auth_done_label">
57 <property name="visible">True</property>
58 <property name="can_focus">False</property>
59 <property name="label" translatable="yes">Vkontakte authorized</property>
60 </object>
61 <packing>
62 <property name="expand">True</property>
63 <property name="fill">True</property>
64 <property name="position">0</property>
65 </packing>
66 </child>
67 </object>
68 <packing>
69 <property name="expand">True</property>
70 <property name="fill">True</property>
71 <property name="position">1</property>
72 </packing>
73 </child>
74 </object>
75 <packing>
76 <property name="expand">True</property>
77 <property name="fill">True</property>
78 <property name="position">0</property>
79 </packing>
80 </child>
81 <child>
82 <object class="GtkHSeparator" id="hseparator1">
83 <property name="visible">True</property>
84 <property name="can_focus">False</property>
85 </object>
86 <packing>
87 <property name="expand">False</property>
88 <property name="fill">True</property>
89 <property name="position">1</property>
90 </packing>
91 </child>
92 <child>
93 <object class="GtkVBox" id="vbox_advanced">
94 <property name="visible">True</property>
95 <property name="can_focus">False</property>
96 <property name="spacing">6</property>
97 <child>
98 <object class="GtkLabel" id="label3">
99 <property name="visible">True</property>
100 <property name="can_focus">False</property>
101 <property name="xalign">0</property>
102 <property name="label" translatable="yes">Account Settings:</property>
103 <attributes>
104 <attribute name="weight" value="bold"/>
105 </attributes>
106 </object>
107 <packing>
108 <property name="expand">True</property>
109 <property name="fill">True</property>
110 <property name="position">0</property>
111 </packing>
112 </child>
113 <child>
114 <object class="GtkVBox" id="vbox2">
115 <property name="visible">True</property>
116 <property name="can_focus">False</property>
117 <child>
118 <object class="GtkCheckButton" id="receive_enabled">
119 <property name="label" translatable="yes">_Receive Messages</property>
120 <property name="visible">True</property>
121 <property name="can_focus">True</property>
122 <property name="receives_default">False</property>
123 <property name="tooltip_text" translatable="yes">Include this account when downloading messages</property>
124 <property name="use_action_appearance">False</property>
125 <property name="use_underline">True</property>
126 <property name="active">True</property>
127 <property name="draw_indicator">True</property>
128 </object>
129 <packing>
130 <property name="expand">True</property>
131 <property name="fill">True</property>
132 <property name="position">0</property>
133 </packing>
134 </child>
135 <child>
136 <object class="GtkCheckButton" id="send_enabled">
137 <property name="label" translatable="yes">_Send Messages</property>
138 <property name="visible">True</property>
139 <property name="can_focus">True</property>
140 <property name="receives_default">False</property>
141 <property name="tooltip_text" translatable="yes">Allow sending posts to this account</property>
142 <property name="use_action_appearance">False</property>
143 <property name="use_underline">True</property>
144 <property name="active">True</property>
145 <property name="draw_indicator">True</property>
146 </object>
147 <packing>
148 <property name="expand">True</property>
149 <property name="fill">True</property>
150 <property name="position">1</property>
151 </packing>
152 </child>
153 <child>
154 <object class="GtkCheckButton" id="receive_groups">
155 <property name="label" translatable="yes" comments="Receive messages from the groups, in addition to messages from users?">_Receive groups</property>
156 <property name="visible">True</property>
157 <property name="can_focus">True</property>
158 <property name="receives_default">False</property>
159 <property name="tooltip_text" translatable="yes">Receive news from groups</property>
160 <property name="use_action_appearance">False</property>
161 <property name="use_underline">True</property>
162 <property name="active">True</property>
163 <property name="draw_indicator">True</property>
164 </object>
165 <packing>
166 <property name="expand">True</property>
167 <property name="fill">True</property>
168 <property name="position">2</property>
169 </packing>
170 </child>
171 </object>
172 <packing>
173 <property name="expand">True</property>
174 <property name="fill">True</property>
175 <property name="position">1</property>
176 </packing>
177 </child>
178 <child>
179 <object class="GtkHBox" id="hbox1">
180 <property name="visible">True</property>
181 <property name="can_focus">False</property>
182 <property name="homogeneous">True</property>
183 <child>
184 <object class="GtkLabel" id="label4">
185 <property name="visible">True</property>
186 <property name="can_focus">False</property>
187 <property name="tooltip_text" translatable="yes">Color used to help distinguish accounts</property>
188 <property name="xalign">0</property>
189 <property name="label" translatable="yes">Account Color:</property>
190 </object>
191 <packing>
192 <property name="expand">True</property>
193 <property name="fill">True</property>
194 <property name="position">0</property>
195 </packing>
196 </child>
197 <child>
198 <object class="GtkColorButton" id="color">
199 <property name="visible">True</property>
200 <property name="can_focus">True</property>
201 <property name="receives_default">True</property>
202 <property name="tooltip_text" translatable="yes">Color used to help distinguish accounts</property>
203 <property name="use_action_appearance">False</property>
204 <property name="color">#000000000000</property>
205 </object>
206 <packing>
207 <property name="expand">False</property>
208 <property name="fill">True</property>
209 <property name="position">1</property>
210 </packing>
211 </child>
212 </object>
213 <packing>
214 <property name="expand">True</property>
215 <property name="fill">True</property>
216 <property name="position">2</property>
217 </packing>
218 </child>
219 </object>
220 <packing>
221 <property name="expand">True</property>
222 <property name="fill">True</property>
223 <property name="position">2</property>
224 </packing>
225 </child>
226 </object>
227</interface>
0228
=== added directory 'gwibber/microblog/plugins/vkontakte/ui/icons'
=== added directory 'gwibber/microblog/plugins/vkontakte/ui/icons/16x16'
=== added file 'gwibber/microblog/plugins/vkontakte/ui/icons/16x16/vkontakte.png'
1Binary files gwibber/microblog/plugins/vkontakte/ui/icons/16x16/vkontakte.png 1970-01-01 00:00:00 +0000 and gwibber/microblog/plugins/vkontakte/ui/icons/16x16/vkontakte.png 2012-03-17 22:54:18 +0000 differ229Binary files gwibber/microblog/plugins/vkontakte/ui/icons/16x16/vkontakte.png 1970-01-01 00:00:00 +0000 and gwibber/microblog/plugins/vkontakte/ui/icons/16x16/vkontakte.png 2012-03-17 22:54:18 +0000 differ
=== added directory 'gwibber/microblog/plugins/vkontakte/ui/icons/22x22'
=== added file 'gwibber/microblog/plugins/vkontakte/ui/icons/22x22/vkontakte.png'
2Binary files gwibber/microblog/plugins/vkontakte/ui/icons/22x22/vkontakte.png 1970-01-01 00:00:00 +0000 and gwibber/microblog/plugins/vkontakte/ui/icons/22x22/vkontakte.png 2012-03-17 22:54:18 +0000 differ230Binary files gwibber/microblog/plugins/vkontakte/ui/icons/22x22/vkontakte.png 1970-01-01 00:00:00 +0000 and gwibber/microblog/plugins/vkontakte/ui/icons/22x22/vkontakte.png 2012-03-17 22:54:18 +0000 differ
=== added directory 'gwibber/microblog/plugins/vkontakte/ui/icons/32x32'
=== added file 'gwibber/microblog/plugins/vkontakte/ui/icons/32x32/vkontakte.png'
3Binary files gwibber/microblog/plugins/vkontakte/ui/icons/32x32/vkontakte.png 1970-01-01 00:00:00 +0000 and gwibber/microblog/plugins/vkontakte/ui/icons/32x32/vkontakte.png 2012-03-17 22:54:18 +0000 differ231Binary files gwibber/microblog/plugins/vkontakte/ui/icons/32x32/vkontakte.png 1970-01-01 00:00:00 +0000 and gwibber/microblog/plugins/vkontakte/ui/icons/32x32/vkontakte.png 2012-03-17 22:54:18 +0000 differ
=== added directory 'gwibber/microblog/plugins/vkontakte/ui/icons/scalable'
=== added file 'gwibber/microblog/plugins/vkontakte/ui/icons/scalable/vkontakte.png'
4Binary files gwibber/microblog/plugins/vkontakte/ui/icons/scalable/vkontakte.png 1970-01-01 00:00:00 +0000 and gwibber/microblog/plugins/vkontakte/ui/icons/scalable/vkontakte.png 2012-03-17 22:54:18 +0000 differ232Binary files gwibber/microblog/plugins/vkontakte/ui/icons/scalable/vkontakte.png 1970-01-01 00:00:00 +0000 and gwibber/microblog/plugins/vkontakte/ui/icons/scalable/vkontakte.png 2012-03-17 22:54:18 +0000 differ
=== added file 'gwibber/microblog/plugins/vkontakte/ui/icons/scalable/vkontakte.svg'
--- gwibber/microblog/plugins/vkontakte/ui/icons/scalable/vkontakte.svg 1970-01-01 00:00:00 +0000
+++ gwibber/microblog/plugins/vkontakte/ui/icons/scalable/vkontakte.svg 2012-03-17 22:54:18 +0000
@@ -0,0 +1,55 @@
1<?xml version="1.0" encoding="UTF-8" standalone="no"?>
2<!-- Created with Inkscape (http://www.inkscape.org/) -->
3<!-- SVG Created by Sergey A Prochorov <root@seriyps.ru> (http://seriyps.ru/) -->
4<svg
5 xmlns:dc="http://purl.org/dc/elements/1.1/"
6 xmlns:cc="http://creativecommons.org/ns#"
7 xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
8 xmlns:svg="http://www.w3.org/2000/svg"
9 xmlns="http://www.w3.org/2000/svg"
10 version="1.1"
11 width="285"
12 height="285"
13 id="svg3036">
14 <defs
15 id="defs8">
16 <filter
17 color-interpolation-filters="sRGB"
18 id="filter3773">
19 <feGaussianBlur
20 id="feGaussianBlur3775"
21 stdDeviation="3.931875" />
22 </filter>
23 </defs>
24 <metadata
25 id="metadata3042">
26 <rdf:RDF>
27 <cc:Work
28 rdf:about="">
29 <dc:format>image/svg+xml</dc:format>
30 <dc:type
31 rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
32 <dc:title></dc:title>
33 </cc:Work>
34 </rdf:RDF>
35 </metadata>
36 <g
37 transform="translate(-45.0635,-38.936486)"
38 id="g3796">
39 <rect
40 width="168"
41 height="197"
42 x="108"
43 y="87"
44 id="rect4024"
45 style="fill:#4c77a0;fill-opacity:1;stroke:none" />
46 <path
47 d="M 102,52.5 C 78.75,55 57.625,71.25 54.5,98 l 0,170.5 c 2.875,25.25 20,43.625 46.5,46 l 168.5,0 c 22.75,-0.625 43.625,-18.125 47.25,-46.5 l 0,-169.375 C 314.25,73.875 292.375,54.5 270,52.5 l -168,0 z m 34,55.75 64.25,0 c 66,0.5 51.15504,62.271 24,68.5 44.5,3.75 50.25,82.5 -23.75,83.25 L 136.25,260 136,108.25 z m 36,27 0,31.75 18,0 c 22.1875,0 23.0625,-31.75 0,-31.75 l -18,0 z m 0,58 0,37.5 24.25,0 c 26.75,0 28,-37.5 0,-37.5 l -24.25,0 z"
48 id="path3978"
49 style="opacity:0.62999998;fill:#000000;fill-opacity:1;stroke:none;filter:url(#filter3773)" />
50 <path
51 d="M 99,50 C 75.75,52.5 54.625,68.75 51.5,95.5 l 0,170.5 c 2.875,25.25 20,43.625 46.5,46 l 168.5,0 c 22.75,-0.625 43.625,-18.125 47.25,-46.5 l 0,-169.375 C 311.25,71.375 289.375,52 267,50 L 99,50 z m 34,55.75 64.25,0 c 66,0.5 51.15504,62.271 24,68.5 44.5,3.75 50.25,82.5 -23.75,83.25 l -64.25,0 L 133,105.75 z m 36,27 0,31.75 18,0 c 22.1875,0 23.0625,-31.75 0,-31.75 l -18,0 z m 0,58 0,37.5 24.25,0 c 26.75,0 28,-37.5 0,-37.5 l -24.25,0 z"
52 id="path3046"
53 style="fill:#ffffff;fill-opacity:1;stroke:none" />
54 </g>
55</svg>
056
=== added file 'gwibber/microblog/plugins/vkontakte/vk_api_wrapper.py'
--- gwibber/microblog/plugins/vkontakte/vk_api_wrapper.py 1970-01-01 00:00:00 +0000
+++ gwibber/microblog/plugins/vkontakte/vk_api_wrapper.py 2012-03-17 22:54:18 +0000
@@ -0,0 +1,87 @@
1# -*- coding: utf-8 -*-
2'''
3Created on 07.12.2010
4
5@author: Sergey Prokhorov <root@seriyps.ru>
6'''
7from gwibber.microblog import network
8import urllib
9import time
10import logging
11
12logger = logging.getLogger("Vkontakte.vk_api_wrapper")
13
14
15class VkException(Exception):
16
17 def __init__(self, code, msg, url, response):
18 self.code = code
19 self.msg = msg
20 self.response = response
21 self.url = url
22 Exception.__init__(self, code, msg, url, response)
23
24
25class VkApi(object):
26 """Vkontakte.ru (vk.com) API wrapper for Gwibber
27 == Usage ==
28 Initialization:
29 api=vk_api(access_token)
30 "access_token" can be retrieved from call to http://api.vkontakte.ru/oauth/authorize
31 (see http://vkontakte.ru/developers.php?o=-1&p=%C0%E2%F2%EE%F0%E8%E7%E0%F6%E8%FF%20%EA%EB%E8%E5%ED%F2%F1%EA%E8%F5%20%EF%F0%E8%EB%EE%E6%E5%ED%E8%E9)
32 Make calls to simple methods (has no dots in name, like "getProfiles", "isAppUser"):
33 res=api.method(arg_name1=arg1, arg_name2=arg2)
34 eg res=api.getProfiles(uids="535397,1", fields="uid,first_name,last_name")
35 Make calls to namespaced methods (has dots in name, like "wall.post", "newsfeed.get"):
36 res=api.namespace.method(arg_name1=arg1, arg_name2=arg2)
37 eg res=api.wall.post(message="New wall post from API")
38 Alternative call syntax (better performance):
39 res=api._load('namespace.method', arg_name1=arg1, arg_name2=arg2)
40 eg res=api._get('wall.post', message="New wall post from API")
41 """
42
43 api_url = 'https://api.vkontakte.ru/method/%s'
44
45 def __init__(self, access_token):
46 super(VkApi, self).__init__()
47 self._access_token = access_token
48 self._prefix = []
49
50 def _load(self, method, **params):
51 """Make call by method name and arguments"""
52 params["access_token"] = self._access_token
53 url = self.api_url % method
54 for _i in xrange(3):
55 # this cycle used for API error #6 "Too many requests"
56 logger.debug("Perform API request to method %s. URL: %s?%s",
57 method, url, urllib.urlencode(params))
58 res = network.Download(url, params, False).get_json()
59 if "error" not in res:
60 #log.logger.debug("%s"%res)
61 return res
62
63 if res["error"]["error_code"] != 6:
64 break
65 logging.warning('Error #6 "%s". Wait 0.5s and retry...',
66 res["error"]["error_msg"])
67 '''if we get error #6 "Too many requests per second" (vk allow
68 max 3rps http://vkontakte.ru/developers.php?o=-1&p=%D0%92%D0%B7%D0%B0%D0%B8%D0%BC%D0%BE%D0%B4%D0%B5%D0%B9%D1%81%D1%82%D0%B2%D0%B8%D0%B5+%D0%BF%D1%80%D0%B8%D0%BB%D0%BE%D0%B6%D0%B5%D0%BD%D0%B8%D1%8F+%D1%81+API )
69 try to sleep with small delay and retry request not more then 3x times'''
70 time.sleep(0.5)#TODO: play with sleep value
71 raise VkException(res["error"]["error_code"],
72 res["error"]["error_msg"],
73 "%s?%s" % (url, urllib.urlencode(params)),
74 res)
75
76 """Support for api.namespace.method(**kwargs) calls below"""
77 def __getattr__(self, name):
78 self._prefix.append(name)
79 return self
80
81 def __call__(self, **kwargs):
82 if self._prefix:
83 method = ".".join(self._prefix)
84 self._prefix = []
85 else:
86 method = kwargs.pop("method")
87 return self._load(method, **kwargs)
088
=== modified file 'gwibber/microblog/util/const.py.in'
--- gwibber/microblog/util/const.py.in 2012-02-23 18:02:08 +0000
+++ gwibber/microblog/util/const.py.in 2012-03-17 22:54:18 +0000
@@ -15,6 +15,8 @@
15TWITTER_OAUTH_KEY = "qMBra1U4bpNYvDz947M5Q"15TWITTER_OAUTH_KEY = "qMBra1U4bpNYvDz947M5Q"
16TWITTER_OAUTH_SECRET = "Lzdkhg0WvGYFzD9tnsuwC0zYmpJ4z7HrZl3yOxU1g"16TWITTER_OAUTH_SECRET = "Lzdkhg0WvGYFzD9tnsuwC0zYmpJ4z7HrZl3yOxU1g"
1717
18VK_APP_ID="2036925"
19
18# Gwibber20# Gwibber
19MAX_MESSAGE_LENGTH = 14021MAX_MESSAGE_LENGTH = 140
20MAX_MESSAGE_COUNT = 2000022MAX_MESSAGE_COUNT = 20000