[gwibber/f14] add support for Sina, Catch errors trying to get Oauth tokendata from Twitter (bz 702940, 700960), D
Tom Callaway
spot at fedoraproject.org
Thu May 12 19:04:04 UTC 2011
commit 9b0cb11bea09d051fd7fa167ba53fd8952e1ad05
Author: Tom "spot" Callaway <tcallawa at redhat.com>
Date: Thu May 12 15:03:52 2011 -0400
add support for Sina, Catch errors trying to get Oauth tokendata from Twitter (bz 702940, 700960), Do not try to process an empty message (bz 702880), Improve about-dialog handling (bz 700878), Catch and log sqlite errors (bz 702992 678015 699139 698074 700966)
gwibber-3.0.0.1-empty_msg.patch | 28 +
...ber-3.0.0.1-improve-about-dialog-handling.patch | 12 +
gwibber-3.0.0.1-sina-icons.patch | Bin 0 -> 26424 bytes
gwibber-3.0.0.1-sina.patch | 726 ++++++++++++++++++++
gwibber-3.0.0.1-sqlite-catch_error.patch | 90 +++
gwibber-3.0.0.1-twitter-catch_error.patch | 34 +
gwibber.spec | 54 ++-
7 files changed, 936 insertions(+), 8 deletions(-)
---
diff --git a/gwibber-3.0.0.1-empty_msg.patch b/gwibber-3.0.0.1-empty_msg.patch
new file mode 100644
index 0000000..c2abda4
--- /dev/null
+++ b/gwibber-3.0.0.1-empty_msg.patch
@@ -0,0 +1,28 @@
+diff -up gwibber-3.0.0.1/gwibber/client.py.empty_msg gwibber-3.0.0.1/gwibber/client.py
+--- gwibber-3.0.0.1/gwibber/client.py.empty_msg 2011-05-12 12:47:32.510671984 -0400
++++ gwibber-3.0.0.1/gwibber/client.py 2011-05-12 12:48:54.281708288 -0400
+@@ -525,14 +525,16 @@ class GwibberClient(gtk.Window):
+ if a.action.__self__.__name__ == "private" and msg["sender"].get("is_me", 0):
+ continue
+
+- if a.include(self, msg):
+- image = gtk.image_new_from_icon_name(a.icon, gtk.ICON_SIZE_MENU)
+- mi = gtk.ImageMenuItem()
+- mi.set_label(a.label)
+- mi.set_image(image)
+- mi.set_property("use_underline", True)
+- mi.connect("activate", perform_action, a.action, view, msg)
+- menu.append(mi)
++ # I have no idea how you get here with msg = None, but rhbz #702880 says you can.
++ if msg:
++ if a.include(self, msg):
++ image = gtk.image_new_from_icon_name(a.icon, gtk.ICON_SIZE_MENU)
++ mi = gtk.ImageMenuItem()
++ mi.set_label(a.label)
++ mi.set_image(image)
++ mi.set_property("use_underline", True)
++ mi.connect("activate", perform_action, a.action, view, msg)
++ menu.append(mi)
+
+ menu.show_all()
+ menu.popup(None, None, None, 3, 0)
diff --git a/gwibber-3.0.0.1-improve-about-dialog-handling.patch b/gwibber-3.0.0.1-improve-about-dialog-handling.patch
new file mode 100644
index 0000000..624503f
--- /dev/null
+++ b/gwibber-3.0.0.1-improve-about-dialog-handling.patch
@@ -0,0 +1,12 @@
+diff -up gwibber-3.0.0.1/gwibber/client.py.improve-about-dialog-handling gwibber-3.0.0.1/gwibber/client.py
+--- gwibber-3.0.0.1/gwibber/client.py.improve-about-dialog-handling 2011-05-12 12:57:18.409763528 -0400
++++ gwibber-3.0.0.1/gwibber/client.py 2011-05-12 12:57:42.509479199 -0400
+@@ -504,7 +504,7 @@ class GwibberClient(gtk.Window):
+ about_dialog = self.ui.get_object("about_dialog")
+ about_dialog.set_version(str(VERSION_NUMBER))
+ about_dialog.set_transient_for(self)
+- about_dialog.connect("response", lambda *a: about_dialog.hide())
++ about_dialog.connect("response", lambda d, *a: d.hide())
+ about_dialog.show_all()
+
+ def on_close_stream(self, *args):
diff --git a/gwibber-3.0.0.1-sina-icons.patch b/gwibber-3.0.0.1-sina-icons.patch
new file mode 100644
index 0000000..fb5f27f
Binary files /dev/null and b/gwibber-3.0.0.1-sina-icons.patch differ
diff --git a/gwibber-3.0.0.1-sina.patch b/gwibber-3.0.0.1-sina.patch
new file mode 100644
index 0000000..ca5804d
--- /dev/null
+++ b/gwibber-3.0.0.1-sina.patch
@@ -0,0 +1,726 @@
+diff -up gwibber-3.0.0.1/gwibber/microblog/plugins/sina/gtk/__init__.py.sina gwibber-3.0.0.1/gwibber/microblog/plugins/sina/gtk/__init__.py
+--- gwibber-3.0.0.1/gwibber/microblog/plugins/sina/gtk/__init__.py.sina 2011-05-12 11:59:10.214024023 -0400
++++ gwibber-3.0.0.1/gwibber/microblog/plugins/sina/gtk/__init__.py 2011-05-12 11:59:10.214024023 -0400
+@@ -0,0 +1 @@
++
+diff -up gwibber-3.0.0.1/gwibber/microblog/plugins/sina/gtk/sina/__init__.py.sina gwibber-3.0.0.1/gwibber/microblog/plugins/sina/gtk/sina/__init__.py
+--- gwibber-3.0.0.1/gwibber/microblog/plugins/sina/gtk/sina/__init__.py.sina 2011-05-12 11:59:10.227023883 -0400
++++ gwibber-3.0.0.1/gwibber/microblog/plugins/sina/gtk/sina/__init__.py 2011-05-12 12:04:16.120705165 -0400
+@@ -0,0 +1,174 @@
++import gtk, pango, webkit, gnomekeyring
++import urllib, urllib2, json, urlparse, uuid
++from oauth import oauth
++
++from gtk import Builder
++from gwibber.microblog.util import resources
++import gettext
++from gettext import gettext as _
++if hasattr(gettext, 'bind_textdomain_codeset'):
++ gettext.bind_textdomain_codeset('gwibber','UTF-8')
++gettext.textdomain('gwibber')
++
++gtk.gdk.threads_init()
++
++sigmeth = oauth.OAuthSignatureMethod_HMAC_SHA1()
++
++class AccountWidget(gtk.VBox):
++ """AccountWidget: A widget that provides a user interface for configuring sina accounts in Gwibber
++ """
++
++ def __init__(self, account=None, dialog=None):
++ """Creates the account pane for configuring Sina accounts"""
++ gtk.VBox.__init__( self, False, 20 )
++ self.ui = gtk.Builder()
++ self.ui.set_translation_domain ("gwibber")
++ self.ui.add_from_file (resources.get_ui_asset("gwibber-accounts-sina.ui"))
++ self.ui.connect_signals(self)
++ self.vbox_settings = self.ui.get_object("vbox_settings")
++ self.pack_start(self.vbox_settings, False, False)
++ self.show_all()
++
++ self.account = account or {}
++ self.dialog = dialog
++ has_secret_key = True
++ if self.account.has_key("id"):
++ try:
++ value = gnomekeyring.find_items_sync(gnomekeyring.ITEM_GENERIC_SECRET, {"id": str("%s/%s" % (self.account["id"], "secret_token"))})[0].secret
++ except gnomekeyring.NoMatchError:
++ has_secret_key = False
++
++ try:
++ if self.account.has_key("access_token") and self.account.has_key("secret_token") and self.account.has_key("username") and has_secret_key and not self.dialog.condition:
++ self.ui.get_object("hbox_sina_auth").hide()
++ self.ui.get_object("sina_auth_done_label").set_label(_("%s has been authorized by Sina") % self.account["username"])
++ self.ui.get_object("hbox_sina_auth_done").show()
++ else:
++ self.ui.get_object("hbox_sina_auth_done").hide()
++ if self.dialog.ui:
++ self.dialog.ui.get_object('vbox_create').hide()
++ except:
++ self.ui.get_object("hbox_sina_auth_done").hide()
++ if self.dialog.ui:
++ self.dialog.ui.get_object("vbox_create").hide()
++
++
++ def on_sina_auth_clicked(self, widget, data=None):
++ self.winsize = self.window.get_size()
++
++ web = webkit.WebView()
++ web.get_settings().set_property("enable-plugins", False)
++ web.load_html_string(_("<p>Please wait...</p>"), "file:///")
++
++ self.consumer = oauth.OAuthConsumer(*resources.get_sina_keys())
++
++ request = oauth.OAuthRequest.from_consumer_and_token(self.consumer, http_method="POST",
++ http_url="http://api.t.sina.com.cn/oauth/request_token")
++
++ request.sign_request(sigmeth, self.consumer, token=None)
++
++ tokendata = urllib2.urlopen(request.http_url, request.to_postdata()).read()
++ self.token = oauth.OAuthToken.from_string(tokendata)
++
++ url = "http://api.t.sina.com.cn/oauth/authorize?oauth_token=%s&oauth_callback=%s&display=popup" % ( self.token.key, "http://gwibber.com/0/auth.html" )
++
++ web.load_uri(url)
++ web.set_size_request(550, 400)
++ web.connect("title-changed", self.on_sina_auth_title_change)
++
++ self.scroll = gtk.ScrolledWindow()
++ self.scroll.add(web)
++
++ self.pack_start(self.scroll, True, True, 0)
++ self.show_all()
++
++ self.ui.get_object("vbox1").hide()
++ self.ui.get_object("vbox_advanced").hide()
++ self.dialog.infobar.set_message_type(gtk.MESSAGE_INFO)
++
++ def on_sina_auth_title_change(self, web=None, title=None, data=None):
++ saved = False
++ if title.get_title() == "Success":
++
++ if hasattr(self.dialog, "infobar_content_area"):
++ for child in self.dialog.infobar_content_area.get_children(): child.destroy()
++ self.dialog.infobar_content_area = self.dialog.infobar.get_content_area()
++ self.dialog.infobar_content_area.show()
++ self.dialog.infobar.show()
++
++ message_label = gtk.Label(_("Verifying"))
++ message_label.set_use_markup(True)
++ message_label.set_ellipsize(pango.ELLIPSIZE_END)
++ self.dialog.infobar_content_area.add(message_label)
++ self.dialog.infobar.show_all()
++ self.scroll.hide()
++ url = web.get_main_frame().get_uri()
++ data = urlparse.parse_qs(url.split("?", 1)[1])
++
++ self.ui.get_object("vbox1").show()
++ self.ui.get_object("vbox_advanced").show()
++
++ token = data["oauth_token"][0]
++ verifier = data["oauth_verifier"][0]
++
++ request = oauth.OAuthRequest.from_consumer_and_token(
++ self.consumer, self.token,
++ http_url="http://api.t.sina.com.cn/oauth/access_token",
++ parameters={"oauth_verifier": str(verifier)})
++ request.sign_request(sigmeth, self.consumer, self.token)
++
++ tokendata = urllib2.urlopen(request.http_url, request.to_postdata()).read()
++ data = urlparse.parse_qs(tokendata)
++
++ atok = oauth.OAuthToken.from_string(tokendata)
++
++ self.account["access_token"] = data["oauth_token"][0]
++ self.account["secret_token"] = data["oauth_token_secret"][0]
++ self.account["username"] = data["screen_name"][0]
++ self.account["user_id"] = data["user_id"][0]
++
++ apireq = oauth.OAuthRequest.from_consumer_and_token(
++ self.consumer, atok,
++ http_method="GET",
++ http_url="http://api.t.sina.com.cn/account/verify_credentials.json", parameters=None)
++
++ apireq.sign_request(sigmeth, self.consumer, atok)
++
++ account_data = json.loads(urllib2.urlopen(apireq.to_url()).read())
++
++ if isinstance(account_data, dict):
++ if account_data.has_key("id"):
++ saved = self.dialog.on_edit_account_save()
++ else:
++ print "Failed"
++ self.dialog.infobar.set_message_type(gtk.MESSAGE_ERROR)
++ message_label.set_text(_("Authorization failed. Please try again."))
++ else:
++ print "Failed"
++ self.dialog.infobar.set_message_type(gtk.MESSAGE_ERROR)
++ message_label.set_text(_("Authorization failed. Please try again."))
++
++ if saved:
++ message_label.set_text(_("Successful"))
++ self.dialog.infobar.set_message_type(gtk.MESSAGE_INFO)
++ #self.dialog.infobar.hide()
++
++ self.ui.get_object("hbox_sina_auth").hide()
++ self.ui.get_object("sina_auth_done_label").set_label(_("%s has been authorized by Sina") % str(self.account["username"]))
++ self.ui.get_object("hbox_sina_auth_done").show()
++ if self.dialog.ui and self.account.has_key("id") and not saved:
++ self.dialog.ui.get_object("vbox_save").show()
++ elif self.dialog.ui and not saved:
++ self.dialog.ui.get_object("vbox_create").show()
++
++ self.window.resize(*self.winsize)
++
++ if title.get_title() == "Failure":
++ web.hide()
++ self.dialog.infobar.set_message_type(gtk.MESSAGE_ERROR)
++ message_label.set_text(_("Authorization failed. Please try again."))
++ self.dialog.infobar.show_all()
++
++ self.ui.get_object("vbox1").show()
++ self.ui.get_object("vbox_advanced").show()
++ self.window.resize(*self.winsize)
+diff -up gwibber-3.0.0.1/gwibber/microblog/plugins/sina/__init__.py.sina gwibber-3.0.0.1/gwibber/microblog/plugins/sina/__init__.py
+--- gwibber-3.0.0.1/gwibber/microblog/plugins/sina/__init__.py.sina 2011-05-12 11:59:10.233023817 -0400
++++ gwibber-3.0.0.1/gwibber/microblog/plugins/sina/__init__.py 2011-05-12 11:59:10.233023817 -0400
+@@ -0,0 +1,294 @@
++from gwibber.microblog import network, util
++from htmlentitydefs import name2codepoint
++import re
++import gnomekeyring
++from oauth import oauth
++from gwibber.microblog.util import log, resources
++from gettext import lgettext as _
++from kitchen.text.converters import to_unicode
++log.logger.name = "Sina"
++
++PROTOCOL_INFO = {
++ "name": "Sina",
++ "version": "1.0",
++
++ "config": [
++ "private:secret_token",
++ "access_token",
++ "username",
++ "color",
++ "receive_enabled",
++ "send_enabled",
++ ],
++
++ "authtype": "oauth1a",
++ "color": "#E61217",
++
++ "features": [
++ "send",
++ "receive",
++ "search",
++ "tag",
++ "reply",
++ "responses",
++ "private",
++ "public",
++ "delete",
++ "retweet",
++ "like",
++ "send_thread",
++ "send_private",
++ "user_messages",
++ "sinceid",
++ "lists",
++ "list",
++ ],
++
++ "default_streams": [
++ "receive",
++ "images",
++ "responses",
++ "private",
++ "lists",
++ ],
++}
++
++URL_PREFIX = "http://t.sina.com.cn"
++API_PREFIX = "http://api.t.sina.com.cn"
++
++def unescape(s):
++ return re.sub('&(%s);' % '|'.join(name2codepoint),
++ lambda m: unichr(name2codepoint[m.group(1)]), s)
++
++class Client:
++ def __init__(self, acct):
++ self.service = util.getbus("Service")
++ if acct.has_key("secret_token") and acct.has_key("password"): acct.pop("password")
++ self.account = acct
++
++ if not acct.has_key("access_token") and not acct.has_key("secret_token"):
++ return [{"error": {"type": "auth", "account": self.account, "message": _("Failed to find credentials")}}]
++
++ self.sigmethod = oauth.OAuthSignatureMethod_HMAC_SHA1()
++ self.consumer = oauth.OAuthConsumer(*util.resources.get_sina_keys())
++ self.token = oauth.OAuthToken(acct["access_token"], acct["secret_token"])
++
++ def _common(self, data):
++ m = {};
++ try:
++ m["mid"] = str(data["id"])
++ m["service"] = u"sina"
++ m["account"] = self.account["id"]
++ m["time"] = util.parsetime(data["created_at"])
++ m["text"] = to_unicode(unescape(data["text"]))
++ m["to_me"] = ("@%s" % self.account["username"]) in data["text"]
++
++ m["html"] = to_unicode(util.linkify(data["text"],
++ ((util.PARSE_HASH, '#<a class="hash" href="%s#search?q=\\1">\\1</a>' % URL_PREFIX),
++ (util.PARSE_NICK, '@<a class="nick" href="%s/\\1">\\1</a>' % URL_PREFIX)), escape=False))
++
++ m["content"] = to_unicode(util.linkify(data["text"],
++ ((util.PARSE_HASH, '#<a class="hash" href="gwibber:/tag?acct=%s&query=\\1">\\1</a>' % m["account"]),
++ (util.PARSE_NICK, '@<a class="nick" href="gwibber:/user?acct=%s&name=\\1">\\1</a>' % m["account"])), escape=False))
++
++ if data.has_key("retweeted_status"):
++ m["retweeted_status"] = data["retweeted_status"]
++ else:
++ m["retweeted_status"] = None
++
++ images = util.imgpreview(m["text"])
++ if images:
++ m["images"] = images
++ m["type"] = "photo"
++ except:
++ log.logger.error("%s failure - %s", PROTOCOL_INFO["name"], data)
++ return {}
++
++ return m
++
++ def _user(self, user):
++ return {
++ "name": to_unicode(user["name"]),
++ "nick": to_unicode(user["screen_name"]),
++ "id": user["id"],
++ "location": to_unicode(user["location"]),
++ "followers": user.get("followers", None),
++ "image": to_unicode(user["profile_image_url"]),
++ "url": to_unicode("/".join((URL_PREFIX, user["screen_name"]))),
++ "is_me": user["screen_name"] == self.account["username"],
++ }
++
++ def _message(self, data):
++ if type(data) == type(None):
++ return []
++
++ m = self._common(data)
++ m["source"] = data.get("source", False)
++
++ if data.has_key("in_reply_to_status_id"):
++ if data["in_reply_to_status_id"]:
++ m["reply"] = {}
++ m["reply"]["id"] = data["in_reply_to_status_id"]
++ m["reply"]["nick"] = to_unicode(data["in_reply_to_screen_name"])
++ if m["reply"]["id"] and m["reply"]["nick"]:
++ m["reply"]["url"] = to_unicode("/".join((URL_PREFIX, m["reply"]["nick"], "statuses", str(m["reply"]["id"]))))
++ else:
++ m["reply"]["url"] = None
++
++ m["sender"] = self._user(data["user"] if "user" in data else data["sender"])
++ m["url"] = to_unicode("/".join((m["sender"]["url"], "statuses", str(m["mid"]))))
++
++ return m
++
++ def _private(self, data):
++ m = self._message(data)
++ m["private"] = True
++
++ m["recipient"] = {}
++ m["recipient"]["name"] = to_unicode(data["recipient"]["name"])
++ m["recipient"]["nick"] = to_unicode(data["recipient"]["screen_name"])
++ m["recipient"]["id"] = data["recipient"]["id"]
++ m["recipient"]["image"] = to_unicode(data["recipient"]["profile_image_url"])
++ m["recipient"]["location"] = to_unicode(data["recipient"]["location"])
++ m["recipient"]["url"] = to_unicode("/".join((URL_PREFIX, m["recipient"]["nick"])))
++ m["recipient"]["is_me"] = m["recipient"]["nick"] == self.account["username"]
++ m["to_me"] = m["recipient"]["is_me"]
++
++ return m
++
++ def _result(self, data):
++ m = self._common(data)
++
++ if data["to_user_id"]:
++ m["reply"] = {}
++ m["reply"]["id"] = data["to_user_id"]
++ m["reply"]["nick"] = to_unicode(data["to_user"])
++
++ m["sender"] = {}
++ m["sender"]["nick"] = to_unicode(data["from_user"])
++ m["sender"]["id"] = data["from_user_id"]
++ m["sender"]["image"] = to_unicode(data["profile_image_url"])
++ m["sender"]["url"] = to_unicode("/".join((URL_PREFIX, m["sender"]["nick"])))
++ m["sender"]["is_me"] = m["sender"]["nick"] == self.account["username"]
++ m["url"] = to_unicode("/".join((m["sender"]["url"], "statuses", str(m["mid"]))))
++ return m
++
++ def _list(self, data):
++ return {
++ "mid": data["id"],
++ "service": u"sina",
++ "account": self.account["id"],
++ "time": 0,
++ "text": to_unicode(data["description"]),
++ "html": to_unicode(data["description"]),
++ "content": to_unicode(data["description"]),
++ "url": to_unicode("/".join((URL_PREFIX, data["uri"]))),
++ "sender": to_unicode(self._user(data["user"])),
++ "name": to_unicode(data["name"]),
++ "nick": to_unicode(data["slug"]),
++ "key": data["slug"],
++ "full": to_unicode(data["full_name"]),
++ "uri": to_unicode(data["uri"]),
++ "mode": data["mode"],
++ "members": data["member_count"],
++ "followers": data["subscriber_count"],
++ "kind": u"list",
++ }
++
++ def _get(self, path, parse="message", post=False, single=False, **args):
++ url = "/".join((API_PREFIX, path))
++
++ request = oauth.OAuthRequest.from_consumer_and_token(self.consumer, self.token,
++ http_method="POST" if post else "GET", http_url=url, parameters=util.compact(args))
++ request.sign_request(self.sigmethod, self.consumer, self.token)
++
++ if post:
++ data = network.Download(request.http_url, None, post, body=request.to_postdata()).get_json()
++ #data = network.Download(request.to_url(), util.compact(args), post).get_json()
++ else:
++ data = network.Download(request.to_url(), None, post).get_json()
++
++ resources.dump(self.account["service"], self.account["id"], data)
++
++ if isinstance(data, dict) and data.get("errors", 0):
++ if "authenticate" in data["errors"][0]["message"]:
++ logstr = """%s: %s - %s""" % (PROTOCOL_INFO["name"], _("Authentication failed"), error["message"])
++ log.logger.error("%s", logstr)
++ return [{"error": {"type": "auth", "account": self.account, "message": data["errors"][0]["message"]}}]
++ else:
++ for error in data["errors"]:
++ logstr = """%s: %s - %s""" % (PROTOCOL_INFO["name"], _("Unknown failure"), error["message"])
++ return [{"error": {"type": "unknown", "account": self.account, "message": error["message"]}}]
++ elif isinstance(data, dict) and data.get("error", 0):
++ if "Incorrect signature" in data["error"]:
++ logstr = """%s: %s - %s""" % (PROTOCOL_INFO["name"], _("Request failed"), data["error"])
++ log.logger.error("%s", logstr)
++ return [{"error": {"type": "auth", "account": self.account, "message": data["error"]}}]
++ elif isinstance(data, str):
++ logstr = """%s: %s - %s""" % (PROTOCOL_INFO["name"], _("Request failed"), data)
++ log.logger.error("%s", logstr)
++ return [{"error": {"type": "request", "account": self.account, "message": data}}]
++
++ if parse == "list":
++ return [self._list(l) for l in data["lists"]]
++ if single: return [getattr(self, "_%s" % parse)(data)]
++ if parse: return [getattr(self, "_%s" % parse)(m) for m in data]
++ else: return []
++
++ def _search(self, **args):
++ data = network.Download("http://api.t.sina.com.cn/search.json", util.compact(args))
++ data = data.get_json()["results"]
++
++ return [self._result(m) for m in data]
++
++ def __call__(self, opname, **args):
++ return getattr(self, opname)(**args)
++
++ def receive(self, count=util.COUNT, since=None):
++ return self._get("statuses/home_timeline.json", count=count, since_id=since)
++
++ def user_messages(self, id=None, count=util.COUNT, since=None):
++ return self._get("statuses/user_timeline.json", id=id, count=count, since_id=since)
++
++ def responses(self, count=util.COUNT, since=None):
++ return self._get("statuses/mentions.json", count=count, since_id=since)
++
++ def private(self, count=util.COUNT, since=None):
++ private = self._get("direct_messages.json", "private", count=count, since_id=since) or []
++ private_sent = self._get("direct_messages/sent.json", "private", count=count, since_id=since) or []
++ return private + private_sent
++
++ def public(self):
++ return self._get("statuses/public_timeline.json")
++
++ def lists(self, **args):
++ following = self._get("%s/lists/subscriptions.json" % self.account["username"], "list") or []
++ lists = self._get("%s/lists.json" % self.account["username"], "list") or []
++ return following + lists
++
++ def list(self, user, id, count=util.COUNT, since=None):
++ return self._get("%s/lists/%s/statuses.json" % (user, id), per_page=count, since_id=since)
++
++ def search(self, query, count=util.COUNT, since=None):
++ return self._search(q=query, rpp=count, since_id=since)
++
++ def tag(self, query, count=util.COUNT, since=None):
++ return self._search(q="#%s" % query, count=count, since_id=since)
++
++ def delete(self, message):
++ return self._get("statuses/destroy/%s.json" % message["mid"], None, post=True, do=1)
++
++ def like(self, message):
++ return self._get("favorites/create/%s.json" % message["mid"], None, post=True, do=1)
++
++ def send(self, message):
++ return self._get("statuses/update.json", post=True, single=True,
++ status=message)
++
++ def send_private(self, message, private):
++ return self._get("direct_messages/new.json", "private", post=True, single=True,
++ text=message, screen_name=private["sender"]["nick"])
++
++ def send_thread(self, message, target):
++ return self._get("statuses/update.json", post=True, single=True,
++ status=message, in_reply_to_status_id=target["mid"])
+diff -up gwibber-3.0.0.1/gwibber/microblog/plugins/sina/ui/gwibber-accounts-sina.ui.sina gwibber-3.0.0.1/gwibber/microblog/plugins/sina/ui/gwibber-accounts-sina.ui
+--- gwibber-3.0.0.1/gwibber/microblog/plugins/sina/ui/gwibber-accounts-sina.ui.sina 2011-05-12 11:59:10.234023806 -0400
++++ gwibber-3.0.0.1/gwibber/microblog/plugins/sina/ui/gwibber-accounts-sina.ui 2011-05-12 11:59:10.233023817 -0400
+@@ -0,0 +1,177 @@
++<?xml version="1.0" encoding="UTF-8"?>
++<interface>
++ <requires lib="gtk+" version="2.16"/>
++ <!-- interface-naming-policy toplevel-contextual -->
++ <object class="GtkVBox" id="vbox_settings">
++ <property name="visible">True</property>
++ <property name="spacing">6</property>
++ <child>
++ <object class="GtkVBox" id="vbox1">
++ <property name="visible">True</property>
++ <child>
++ <object class="GtkHBox" id="hbox_sina_auth">
++ <property name="visible">True</property>
++ <child>
++ <object class="GtkButton" id="sina_auth_button">
++ <property name="label" translatable="yes">_Authorize</property>
++ <property name="visible">True</property>
++ <property name="can_focus">True</property>
++ <property name="receives_default">True</property>
++ <property name="use_underline">True</property>
++ <signal name="clicked" handler="on_sina_auth_clicked"/>
++ </object>
++ <packing>
++ <property name="fill">False</property>
++ <property name="position">0</property>
++ </packing>
++ </child>
++ <child>
++ <object class="GtkLabel" id="sina_auth_label">
++ <property name="visible">True</property>
++ <property name="label" translatable="yes">Authorize with sina</property>
++ </object>
++ <packing>
++ <property name="position">1</property>
++ </packing>
++ </child>
++ </object>
++ <packing>
++ <property name="position">0</property>
++ </packing>
++ </child>
++ <child>
++ <object class="GtkHBox" id="hbox_sina_auth_done">
++ <property name="visible">True</property>
++ <child>
++ <object class="GtkLabel" id="sina_auth_done_label">
++ <property name="visible">True</property>
++ <property name="label" translatable="yes">Sina authorized</property>
++ </object>
++ <packing>
++ <property name="position">0</property>
++ </packing>
++ </child>
++ </object>
++ <packing>
++ <property name="position">1</property>
++ </packing>
++ </child>
++ </object>
++ <packing>
++ <property name="position">0</property>
++ </packing>
++ </child>
++ <child>
++ <object class="GtkHSeparator" id="hseparator1">
++ <property name="visible">True</property>
++ </object>
++ <packing>
++ <property name="expand">False</property>
++ <property name="position">1</property>
++ </packing>
++ </child>
++ <child>
++ <object class="GtkVBox" id="vbox_advanced">
++ <property name="visible">True</property>
++ <property name="spacing">6</property>
++ <child>
++ <object class="GtkLabel" id="label3">
++ <property name="visible">True</property>
++ <property name="xalign">0</property>
++ <property name="label" translatable="yes">Account Settings:</property>
++ <attributes>
++ <attribute name="weight" value="bold"/>
++ </attributes>
++ </object>
++ <packing>
++ <property name="position">0</property>
++ </packing>
++ </child>
++ <child>
++ <object class="GtkTable" id="table_advanced_settings">
++ <property name="visible">True</property>
++ <property name="n_rows">2</property>
++ <property name="n_columns">3</property>
++ <property name="column_spacing">12</property>
++ <property name="row_spacing">6</property>
++ <child>
++ <object class="GtkCheckButton" id="send_enabled">
++ <property name="label" translatable="yes">_Send Messages</property>
++ <property name="visible">True</property>
++ <property name="can_focus">True</property>
++ <property name="receives_default">False</property>
++ <property name="tooltip_text" translatable="yes">Allow sending posts to this account</property>
++ <property name="use_underline">True</property>
++ <property name="active">True</property>
++ <property name="draw_indicator">True</property>
++ </object>
++ <packing>
++ <property name="right_attach">3</property>
++ <property name="top_attach">1</property>
++ <property name="bottom_attach">2</property>
++ <property name="x_options">GTK_FILL</property>
++ <property name="y_options"></property>
++ </packing>
++ </child>
++ <child>
++ <object class="GtkCheckButton" id="receive_enabled">
++ <property name="label" translatable="yes">_Receive Messages</property>
++ <property name="visible">True</property>
++ <property name="can_focus">True</property>
++ <property name="receives_default">False</property>
++ <property name="tooltip_text" translatable="yes">Include this account when downloading messages</property>
++ <property name="use_underline">True</property>
++ <property name="active">True</property>
++ <property name="draw_indicator">True</property>
++ </object>
++ <packing>
++ <property name="right_attach">3</property>
++ <property name="x_options">GTK_FILL</property>
++ <property name="y_options"></property>
++ </packing>
++ </child>
++ </object>
++ <packing>
++ <property name="position">1</property>
++ </packing>
++ </child>
++ <child>
++ <object class="GtkHBox" id="hbox1">
++ <property name="visible">True</property>
++ <property name="homogeneous">True</property>
++ <child>
++ <object class="GtkLabel" id="label4">
++ <property name="visible">True</property>
++ <property name="tooltip_text" translatable="yes">Color used to help distinguish accounts</property>
++ <property name="xalign">0</property>
++ <property name="label" translatable="yes">Account Color:</property>
++ </object>
++ <packing>
++ <property name="position">0</property>
++ </packing>
++ </child>
++ <child>
++ <object class="GtkColorButton" id="color">
++ <property name="visible">True</property>
++ <property name="can_focus">True</property>
++ <property name="receives_default">True</property>
++ <property name="tooltip_text" translatable="yes">Color used to help distinguish accounts</property>
++ <property name="color">#000000000000</property>
++ </object>
++ <packing>
++ <property name="expand">False</property>
++ <property name="position">1</property>
++ </packing>
++ </child>
++ </object>
++ <packing>
++ <property name="position">2</property>
++ </packing>
++ </child>
++ </object>
++ <packing>
++ <property name="position">2</property>
++ </packing>
++ </child>
++ </object>
++</interface>
+diff -up gwibber-3.0.0.1/gwibber/microblog/util/const.py.sina gwibber-3.0.0.1/gwibber/microblog/util/const.py
+--- gwibber-3.0.0.1/gwibber/microblog/util/const.py.sina 2011-05-12 11:59:10.145024769 -0400
++++ gwibber-3.0.0.1/gwibber/microblog/util/const.py 2011-05-12 11:59:10.234023806 -0400
+@@ -15,6 +15,9 @@ else:
+ TWITTER_OAUTH_KEY = "VDOuA5qCJ1XhjaSa4pl76g"
+ TWITTER_OAUTH_SECRET = "BqHlB8sMz5FhZmmFimwgiIdB0RiBr72Y0bio49IVJM"
+
++SINA_OAUTH_KEY = "4014350411"
++SINA_OAUTH_SECRET = "92e7877ad12d59a8410d850c19787701"
++
+ # Gwibber
+ MAX_MESSAGE_LENGTH = 140
+ MAX_MESSAGE_COUNT = 20000
+diff -up gwibber-3.0.0.1/gwibber/microblog/util/resources.py.sina gwibber-3.0.0.1/gwibber/microblog/util/resources.py
+--- gwibber-3.0.0.1/gwibber/microblog/util/resources.py.sina 2011-04-05 17:06:50.000000000 -0400
++++ gwibber-3.0.0.1/gwibber/microblog/util/resources.py 2011-05-12 11:59:10.234023806 -0400
+@@ -79,6 +79,10 @@ def get_twitter_keys():
+ # Distros should register their own keys and not rely on the defaults
+ return TWITTER_OAUTH_KEY, TWITTER_OAUTH_SECRET
+
++def get_sina_keys():
++ # Distros should register their own keys and not rely on the defaults
++ return SINA_OAUTH_KEY, SINA_OAUTH_SECRET
++
+ def get_avatar_path(url):
+ avatar_cache_dir = realpath(join(CACHE_BASE_DIR, "gwibber", "avatars"))
+ if not isdir(avatar_cache_dir):
+diff -up gwibber-3.0.0.1/po/POTFILES.in.sina gwibber-3.0.0.1/po/POTFILES.in
+--- gwibber-3.0.0.1/po/POTFILES.in.sina 2011-05-12 11:59:10.200024174 -0400
++++ gwibber-3.0.0.1/po/POTFILES.in 2011-05-12 11:59:10.235023795 -0400
+@@ -18,6 +18,7 @@ gwibber/microblog/plugins/buzz/gtk/buzz/
+ gwibber/microblog/plugins/digg/gtk/digg/__init__.py
+ gwibber/microblog/plugins/pingfm/gtk/pingfm/__init__.py
+ gwibber/microblog/plugins/qaiku/gtk/qaiku/__init__.py
++gwibber/microblog/plugins/sina/gtk/sina/__init__.py
+ gwibber/preferences.py
+ gwibber/util.py
+ [type: gettext/glade] ui/gwibber-about-dialog.ui
+@@ -35,6 +36,7 @@ gwibber/util.py
+ [type: gettext/glade] gwibber/microblog/plugins/qaiku/ui/gwibber-accounts-qaiku.ui
+ [type: gettext/glade] gwibber/microblog/plugins/foursquare/ui/gwibber-accounts-foursquare.ui
+ [type: gettext/glade] gwibber/microblog/plugins/gowalla/ui/gwibber-accounts-gowalla.ui
++[type: gettext/glade] gwibber/microblog/plugins/sina/ui/gwibber-accounts-sina.ui
+ ui/templates/base.mako
+ ui/templates/targetbar.mako
+ bin/gwibber-poster
+diff -up gwibber-3.0.0.1/setup.py.sina gwibber-3.0.0.1/setup.py
+--- gwibber-3.0.0.1/setup.py.sina 2011-05-12 11:59:10.201024163 -0400
++++ gwibber-3.0.0.1/setup.py 2011-05-12 11:59:10.235023795 -0400
+@@ -69,6 +69,14 @@ setup(name="gwibber",
+ ('share/gwibber/plugins/gowalla/ui/icons/22x22', glob("gwibber/microblog/plugins/gowalla/ui/icons/22x22/*.*")),
+ ('share/gwibber/plugins/gowalla/ui/icons/32x32', glob("gwibber/microblog/plugins/gowalla/ui/icons/32x32/*.*")),
+ ('share/gwibber/plugins/gowalla/ui/icons/scalable', glob("gwibber/microblog/plugins/gowalla/ui/icons/scalable/*.*")),
++ ('share/gwibber/plugins/sina', glob("gwibber/microblog/plugins/sina/*.*")),
++ ('share/gwibber/plugins/sina/gtk', glob("gwibber/microblog/plugins/sina/gtk/*.*")),
++ ('share/gwibber/plugins/sina/gtk/sina', glob("gwibber/microblog/plugins/sina/gtk/sina/*.*")),
++ ('share/gwibber/plugins/sina/ui', glob("gwibber/microblog/plugins/sina/ui/*.*")),
++ ('share/gwibber/plugins/sina/ui/icons/16x16', glob("gwibber/microblog/plugins/sina/ui/icons/16x16/*.*")),
++ ('share/gwibber/plugins/sina/ui/icons/22x22', glob("gwibber/microblog/plugins/sina/ui/icons/22x22/*.*")),
++ ('share/gwibber/plugins/sina/ui/icons/32x32', glob("gwibber/microblog/plugins/sina/ui/icons/32x32/*.*")),
++ ('share/gwibber/plugins/sina/ui/icons/scalable', glob("gwibber/microblog/plugins/sina/ui/icons/scalable/*.*")),
+ ('share/gwibber/plugins/buzz', glob("gwibber/microblog/plugins/buzz/*.*")),
+ ('share/gwibber/plugins/buzz/gtk', glob("gwibber/microblog/plugins/buzz/gtk/*.*")),
+ ('share/gwibber/plugins/buzz/gtk/buzz', glob("gwibber/microblog/plugins/buzz/gtk/buzz/*.*")),
diff --git a/gwibber-3.0.0.1-sqlite-catch_error.patch b/gwibber-3.0.0.1-sqlite-catch_error.patch
new file mode 100644
index 0000000..0a461b1
--- /dev/null
+++ b/gwibber-3.0.0.1-sqlite-catch_error.patch
@@ -0,0 +1,90 @@
+diff -up gwibber-3.0.0.1/gwibber/microblog/storage.py.catch_sqlite_error gwibber-3.0.0.1/gwibber/microblog/storage.py
+--- gwibber-3.0.0.1/gwibber/microblog/storage.py.catch_sqlite_error 2011-05-12 13:43:10.977009364 -0400
++++ gwibber-3.0.0.1/gwibber/microblog/storage.py 2011-05-12 13:59:46.707973761 -0400
+@@ -41,7 +41,11 @@ class MessageManager(dbus.service.Object
+ def setup_table(self):
+ with self.db:
+ schema = "rowid integer primary key autoincrement," + self.schema
+- self.db.execute("CREATE TABLE messages (%s)" % schema)
++ try:
++ self.db.execute("CREATE TABLE messages (%s)" % schema)
++ except sqlite3.OperationalError, msg:
++ log.logger.info("SQLite threw an error trying to setup the messages table: %s", msg)
++
+ self.db.execute("create unique index idx1 on messages (mid, account, operation, transient)")
+
+ def maintenance(self):
+@@ -59,10 +63,16 @@ class MessageManager(dbus.service.Object
+ log.logger.info("Found %d records in the messages stream for account %s", count, acct[0])
+ if count > 2000:
+ log.logger.info("Purging old data for %s", acct[0])
+- self.db.execute("DELETE FROM messages WHERE account = ? AND operation = 'receive' AND stream = 'messages' AND time IN (SELECT CAST (time AS int) FROM (SELECT time FROM messages WHERE account = ? AND operation = 'receive' AND stream = 'messages' AND time != 0 ORDER BY time ASC LIMIT (SELECT COUNT(time) FROM messages WHERE operation = 'receive' AND stream = 'messages' AND account = ? AND time != 0) - 2000) ORDER BY time ASC)", (acct[0],acct[0],acct[0]))
++ try:
++ self.db.execute("DELETE FROM messages WHERE account = ? AND operation = 'receive' AND stream = 'messages' AND time IN (SELECT CAST (time AS int) FROM (SELECT time FROM messages WHERE account = ? AND operation = 'receive' AND stream = 'messages' AND time != 0 ORDER BY time ASC LIMIT (SELECT COUNT(time) FROM messages WHERE operation = 'receive' AND stream = 'messages' AND account = ? AND time != 0) - 2000) ORDER BY time ASC)", (acct[0],acct[0],acct[0]))
++ except sqlite3.OperationalError, msg:
++ log.logger.info("DB Maintenance: SQLite threw an error: %s", msg)
+ except:
+ pass
+- self.db.execute("VACUUM")
++ try:
++ self.db.execute("VACUUM")
++ except sqlite3.OperationalError, msg:
++ log.logger.info("DB Maintenance: SQLite threw an error: %s", msg)
+ return
+
+
+@@ -154,14 +164,17 @@ class StreamManager(dbus.service.Object)
+
+ def setup_table(self):
+ with self.db:
+- self.db.execute("""
+- CREATE TABLE streams (
+- id text,
+- name text,
+- account text,
+- operation text,
+- data text)
+- """)
++ try:
++ self.db.execute("""
++ CREATE TABLE streams (
++ id text,
++ name text,
++ account text,
++ operation text,
++ data text)
++ """)
++ except sqlite3.OperationalError, msg:
++ log.logger.info("SQLite threw an error trying to setup the streams table: %s", msg)
+
+ @dbus.service.signal("com.Gwibber.Streams", signature="s")
+ def Updated(self, data):
+@@ -265,15 +278,19 @@ class AccountManager(dbus.service.Object
+
+ def setup_table(self):
+ with self.db:
+- self.db.execute("""
+- CREATE TABLE accounts (
+- id text,
+- service text,
+- username text,
+- color text,
+- send integer,
+- receive integer,
+- data text)""")
++ try:
++ self.db.execute("""
++ CREATE TABLE accounts (
++ id text,
++ service text,
++ username text,
++ color text,
++ send integer,
++ receive integer,
++ data text)""")
++ except sqlite3.OperationalError, msg:
++ log.logger.info("SQLite threw an error trying to setup the accounts table: %s", msg)
++
+
+ def refresh_password_cache(self):
+ for acct in json.loads(self.List()):
diff --git a/gwibber-3.0.0.1-twitter-catch_error.patch b/gwibber-3.0.0.1-twitter-catch_error.patch
new file mode 100644
index 0000000..8a5bb76
--- /dev/null
+++ b/gwibber-3.0.0.1-twitter-catch_error.patch
@@ -0,0 +1,34 @@
+diff -up gwibber-3.0.0.1/gwibber/microblog/plugins/twitter/gtk/twitter/__init__.py.catch_error gwibber-3.0.0.1/gwibber/microblog/plugins/twitter/gtk/twitter/__init__.py
+--- gwibber-3.0.0.1/gwibber/microblog/plugins/twitter/gtk/twitter/__init__.py.catch_error 2011-04-05 17:06:50.000000000 -0400
++++ gwibber-3.0.0.1/gwibber/microblog/plugins/twitter/gtk/twitter/__init__.py 2011-05-12 13:31:09.380655567 -0400
+@@ -4,6 +4,7 @@ from oauth import oauth
+
+ from gtk import Builder
+ from gwibber.microblog.util import resources
++from gwibber import error
+ import gettext
+ from gettext import gettext as _
+ if hasattr(gettext, 'bind_textdomain_codeset'):
+@@ -68,7 +69,21 @@ class AccountWidget(gtk.VBox):
+
+ request.sign_request(sigmeth, self.consumer, token=None)
+
+- tokendata = urllib2.urlopen(request.http_url, request.to_postdata()).read()
++ try:
++ tokendata = urllib2.urlopen(request.http_url, request.to_postdata()).read()
++ except urllib2.HTTPError, e:
++ http_error = error.GwibberErrorService()
++ http_error.ShowDialog(message='HTTP error trying to get twitter OAuth tokendata (%s)' % (e.code), title='Uh oh. Twitter FAIL WHALE!', type='network')
++ return
++ except urllib2.URLError, e:
++ url_error = error.GwibberErrorService()
++ url_error.ShowDialog(message='URL error trying to get twitter OAuth tokendata (%s)' % (e.reason), title='Uh oh. Twitter FAIL WHALE!', type='network')
++ return
++ except Exception:
++ unknown_error = error.GwibberErrorService()
++ unknown_error.ShowDialog(message='An unknown error occurred trying to get twitter OAuth tokendata.', title='Uh oh. Twitter FAIL WHALE!', type='network')
++ return
++
+ self.token = oauth.OAuthToken.from_string(tokendata)
+
+ url = "http://api.twitter.com/oauth/authorize?oauth_token=" + self.token.key
diff --git a/gwibber.spec b/gwibber.spec
index 35e3d85..4120de8 100644
--- a/gwibber.spec
+++ b/gwibber.spec
@@ -4,7 +4,7 @@
Name: gwibber
Version: 3.0.0.1
-Release: 1%{?dist}
+Release: 2%{?dist}
Epoch: 1
Summary: An open source microblogging client for GNOME developed with Python and GTK
Group: Applications/Internet
@@ -16,7 +16,6 @@ URL: https://launchpad.net/gwibber
# bzr export -r %{bzr_rev} gwibber-%{bzr_rev}bzr.tar.gz lp:gwibber
# Source0: %{name}-%{bzr_rev}bzr.tar.gz
Source0: http://launchpad.net/gwibber/trunk/3.0/+download/%{name}-%{version}.tar.gz
-BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n)
# Fix situation where pango_overlay can be undefined
# https://bugs.launchpad.net/gwibber/+bug/577050
@@ -61,6 +60,22 @@ Patch40: gwibber-3.0.0-gowalla.patch
Patch41: gwibber-2.91.92-gowalla-icons.patch
Patch42: gwibber-2.91.92-gowalla-kitchen.patch
+# Sina support
+Patch43: gwibber-3.0.0.1-sina.patch
+Patch44: gwibber-3.0.0.1-sina-icons.patch
+
+# Catch errors trying to get Twitter Oauth token
+Patch45: gwibber-3.0.0.1-twitter-catch_error.patch
+
+# Don't try to process empty message (bz 702880)
+Patch46: gwibber-3.0.0.1-empty_msg.patch
+
+# Improve about-dialog handling (bz 700878)
+Patch47: gwibber-3.0.0.1-improve-about-dialog-handling.patch
+
+# Catch sqlite errors (bz 702992 678015 699139 698074 700966)
+Patch48: gwibber-3.0.0.1-sqlite-catch_error.patch
+
Requires: libsoup, python-pycurl
Requires: python
Requires: dbus-python >= 0.80.2
@@ -115,6 +130,22 @@ and GTK. It supports Twitter, Jaiku, Identi.ca, Facebook, and Digg.
%patch41 -p1 -b .gowalla-icons
%patch42 -p1 -b .gowalla-kitchen
+# Sina
+%patch43 -p1 -b .sina
+%patch44 -p1 -b .sina-icons
+
+# Catch errors trying to get Oauth tokendata from Twitter
+%patch45 -p1 -b .catch_error
+
+# Do not try to process an empty message (bz 702880)
+%patch46 -p1 -b .empty_msg
+
+# Improve about-dialog handling (bz700878)
+%patch47 -p1 -b .improve-about-dialog-handling
+
+# Catch sqlite errors (bz 702992 678015 699139 698074 700966)
+%patch48 -p1 -b .catch_sqlite_error
+
# sed -i -e '/^#! \?\//, 1d' $(find %{name} | grep "\.py$")
%build
@@ -122,13 +153,18 @@ and GTK. It supports Twitter, Jaiku, Identi.ca, Facebook, and Digg.
%install
-rm -rf %{buildroot}
%{__python} setup.py install --prefix %{_prefix} -O1 --skip-build --root %{buildroot}
# Clean up patchfiles
for i in `find %{buildroot} |grep "\.gowalla"`; do
rm -f $i
done
+for i in `find %{buildroot} |grep "\.sina"`; do
+ rm -f $i
+done
+for i in `find %{buildroot} |grep "\.catch_error"`; do
+ rm -f $i
+done
## Reinstall .desktop file
rm -rf %{buildroot}%{_datadir}/applications
@@ -139,12 +175,7 @@ cp -a build/mo %{buildroot}%{_datadir}/locale
%find_lang %{name}
-%clean
-rm -rf %{buildroot}
-
-
%files -f %{name}.lang
-%defattr(-,root,root,-)
%doc AUTHORS COPYING README
%{python_sitelib}/%{name}
%{python_sitelib}/%{name}-*.egg-info
@@ -166,6 +197,13 @@ rm -rf %{buildroot}
%{_datadir}/indicators/messages/applications/gwibber
%changelog
+* Thu May 12 2011 Tom Callaway <spot at fedoraproject.org> - 1:3.0.0.1-2
+- add support for Sina
+- Catch errors trying to get Oauth tokendata from Twitter (bz 702940, 700960)
+- Do not try to process an empty message (bz 702880)
+- Improve about-dialog handling (bz 700878)
+- Catch and log sqlite errors (bz 702992 678015 699139 698074 700966)
+
* Tue Apr 26 2011 Tom Callaway <spot at fedoraproject.org> - 1:3.0.0.1-1
- update to 3.0.0.1
More information about the scm-commits
mailing list