[gwibber] 3.3.1.1

Tom Callaway spot at fedoraproject.org
Tue Dec 20 17:52:25 UTC 2011


commit f8a5046133e90686d2d5eceef4320c3868872f18
Author: Tom Callaway <spot at fedoraproject.org>
Date:   Tue Dec 20 12:52:17 2011 -0500

    3.3.1.1

 .gitignore                               |    1 +
 gwibber-2.91.92-gowalla-icons.patch      |  Bin 8093 -> 0 bytes
 gwibber-3.3.1.1-facebook-fedora.patch    |   12 +
 gwibber-3.3.1.1-fix-twitter-oauth.patch  |   14 +
 gwibber-3.3.1.1-kitchen-unicode.patch    |  692 +++++++++++++++++++++++++
 gwibber-3.3.1.1-no-gtkspell.patch        |   14 +
 gwibber-3.3.1.1-no-unity.patch           |   18 +
 gwibber-3.3.1.1-sina.patch               |  817 ++++++++++++++++++++++++++++++
 gwibber-3.3.1.1-sqlite-catch_error.patch |   89 ++++
 gwibber.spec                             |  168 ++++---
 sources                                  |    2 +-
 11 files changed, 1766 insertions(+), 61 deletions(-)
---
diff --git a/.gitignore b/.gitignore
index b35fae3..be54aec 100644
--- a/.gitignore
+++ b/.gitignore
@@ -10,3 +10,4 @@ gwibber-pre3-738bzr.tar.gz
 /gwibber-3.0.0.tar.gz
 /gwibber-3.0.0.1.tar.gz
 /gwibber-3.1.0.tar.gz
+/gwibber-3.3.1.1.tar.gz
diff --git a/gwibber-3.3.1.1-facebook-fedora.patch b/gwibber-3.3.1.1-facebook-fedora.patch
new file mode 100644
index 0000000..e771dc8
--- /dev/null
+++ b/gwibber-3.3.1.1-facebook-fedora.patch
@@ -0,0 +1,12 @@
+diff -up gwibber-3.3.1.1/gwibber/microblog/util/const.py.in.fedora gwibber-3.3.1.1/gwibber/microblog/util/const.py.in
+--- gwibber-3.3.1.1/gwibber/microblog/util/const.py.in.fedora	2011-11-10 11:21:01.000000000 -0500
++++ gwibber-3.3.1.1/gwibber/microblog/util/const.py.in	2011-11-30 14:36:03.513287754 -0500
+@@ -10,7 +10,7 @@ from os import environ
+ if environ.has_key("FB_APP_KEY"):
+   FB_APP_KEY = environ["FB_APP_KEY"]
+ else:
+-  FB_APP_KEY = "71b85c6d8cb5bbb9f1a3f8bbdcdd4b05"
++  FB_APP_KEY = "1e51e9b88c054048dc201f81ea5543b7"
+ 
+ TWITTER_OAUTH_KEY = "qMBra1U4bpNYvDz947M5Q"
+ TWITTER_OAUTH_SECRET = "Lzdkhg0WvGYFzD9tnsuwC0zYmpJ4z7HrZl3yOxU1g"
diff --git a/gwibber-3.3.1.1-fix-twitter-oauth.patch b/gwibber-3.3.1.1-fix-twitter-oauth.patch
new file mode 100644
index 0000000..672d122
--- /dev/null
+++ b/gwibber-3.3.1.1-fix-twitter-oauth.patch
@@ -0,0 +1,14 @@
+diff -up gwibber-3.3.1.1/gwibber/microblog/util/const.py.in.orig gwibber-3.3.1.1/gwibber/microblog/util/const.py.in
+--- gwibber-3.3.1.1/gwibber/microblog/util/const.py.in.orig	2011-11-30 14:42:45.000000000 -0500
++++ gwibber-3.3.1.1/gwibber/microblog/util/const.py.in	2011-11-30 14:43:23.566762443 -0500
+@@ -12,8 +12,8 @@ if environ.has_key("FB_APP_KEY"):
+ else:
+   FB_APP_KEY = "1e51e9b88c054048dc201f81ea5543b7"
+ 
+-TWITTER_OAUTH_KEY = "qMBra1U4bpNYvDz947M5Q"
+-TWITTER_OAUTH_SECRET = "Lzdkhg0WvGYFzD9tnsuwC0zYmpJ4z7HrZl3yOxU1g"
++TWITTER_OAUTH_KEY = "GDbYbywyvbcPfe26MqrifQ"
++TWITTER_OAUTH_SECRET = "U4C34CzUW6YYNFinX8RjSTsWWn2bWX08WVdFsxyQ"
+ 
+ SINA_OAUTH_KEY = "4014350411"
+ SINA_OAUTH_SECRET = "92e7877ad12d59a8410d850c19787701"
diff --git a/gwibber-3.3.1.1-kitchen-unicode.patch b/gwibber-3.3.1.1-kitchen-unicode.patch
new file mode 100644
index 0000000..665c9fd
--- /dev/null
+++ b/gwibber-3.3.1.1-kitchen-unicode.patch
@@ -0,0 +1,692 @@
+diff -up gwibber-3.3.1.1/gwibber/microblog/dispatcher.py.kitchen gwibber-3.3.1.1/gwibber/microblog/dispatcher.py
+--- gwibber-3.3.1.1/gwibber/microblog/dispatcher.py.kitchen	2011-11-10 09:40:58.000000000 -0500
++++ gwibber-3.3.1.1/gwibber/microblog/dispatcher.py	2011-11-30 14:21:00.417769649 -0500
+@@ -5,6 +5,7 @@ import multiprocessing, threading, trace
+ import gobject, dbus, dbus.service
+ import sqlite3, mx.DateTime, re, uuid
+ import urlshorter, storage, network, util, uploader
++from kitchen.text.converters import to_unicode
+ from gettext import lgettext as _
+ import signal
+ 
+@@ -73,9 +74,9 @@ def perform_operation((account, opname,
+       for m in message_data:
+         try: 
+           if isinstance(m, dict) and m.has_key("mid"):
+-            m["id"] = uuid.uuid1().hex
+-            m["operation"] = opname
+-            m["stream"] = m.get("stream", stream)
++            m["id"] = to_unicode(uuid.uuid1().hex)
++            m["operation"] = to_unicode(opname)
++            m["stream"] = to_unicode(m.get("stream", stream))
+             m["transient"] = transient
+             m["time"] = m.get("time", 0)
+             if not m["text"]: m["text"] = ""
+diff -up gwibber-3.3.1.1/gwibber/microblog/plugins/buzz/__init__.py.kitchen gwibber-3.3.1.1/gwibber/microblog/plugins/buzz/__init__.py
+--- gwibber-3.3.1.1/gwibber/microblog/plugins/buzz/__init__.py.kitchen	2011-06-02 09:25:01.000000000 -0400
++++ gwibber-3.3.1.1/gwibber/microblog/plugins/buzz/__init__.py	2011-11-30 14:19:55.706601610 -0500
+@@ -2,6 +2,7 @@ from gwibber.microblog import network, u
+ from gwibber.microblog.util import resources
+ import json
+ from oauth import oauth
++from kitchen.text.converters import to_unicode
+ 
+ PROTOCOL_INFO = {
+   "name": "Buzz",
+@@ -43,32 +44,32 @@ class Client:
+ 
+   def _actor(self, user):
+     return {
+-        "name": user["name"],
+-        "nick": user["id"],
+-        "id": user["id"],
+-        "image": user.get("thumbnailUrl", "https://mail.google.com/mail/images/blue_ghost.jpg?sz=45"),
+-        "url": user.get("profileUrl", None),
++        "name": to_unicode(user["name"]),
++        "nick": to_unicode(user["id"]),
++        "id": to_unicode(user["id"]),
++        "image": to_unicode(user.get("thumbnailUrl", "https://mail.google.com/mail/images/blue_ghost.jpg?sz=45")),
++        "url": to_unicode(user.get("profileUrl", None)),
+         "is_me": user["id"] == self.account["user_id"],
+     }
+ 
+   def _message(self, data):
+     m = {
+         "mid": data["id"],
+-        "service": "buzz",
+-        "account": self.account["id"],
++        "service": u"buzz",
++        "account": to_unicode(self.account["id"]),
+         "time": util.parsetime(data["published"]),
+-        "url": data.get("links", {})["alternate"][0].get("href", ""),
+-        "source": data.get("source", {}).get("title", None),
++        "url": to_unicode(data.get("links", {})["alternate"][0].get("href", "")),
++        "source": to_unicode(data.get("source", {}).get("title", None)),
+         "sender": self._actor(data["actor"]),
+     }
+ 
+-    m["text"] = data["object"]["content"]
++    m["text"] = to_unicode(data["object"]["content"])
+ 
+     if data.get("source", {}).get("title", 0) == "Twitter":
+       m["text"] = m["text"].split(">:", 1)[1].strip()
+     
+-    m["html"] = m["text"]
+-    m["content"] = m["text"]
++    m["html"] = to_unicode(m["text"])
++    m["content"] = to_unicode(m["text"])
+ 
+     if data.get("geocode", 0):
+       m["location"] = {
+@@ -77,24 +78,24 @@ class Client:
+       }
+ 
+       if data.get("address", 0):
+-        m["location"]["address"] = data["address"]
++        m["location"]["address"] = to_unicode(data["address"])
+ 
+     m["images"] = []
+     for a in data["object"].get("attachments", []):
+       if a["type"] == "photo":
+         m["images"].append({
+-          "src": a["links"]["preview"][0]["href"],
+-          "url": a["links"]["enclosure"][0]["href"]
++          "src": to_unicode(a["links"]["preview"][0]["href"]),
++          "url": to_unicode(a["links"]["enclosure"][0]["href"])
+         })
+ 
+       if a["type"] == "video":
+         m["images"].append({
+-          "src": a["links"]["preview"][0]["href"],
+-          "url": a["links"]["alternate"][0]["href"],
++          "src": to_unicode(a["links"]["preview"][0]["href"]),
++          "url": to_unicode(a["links"]["alternate"][0]["href"]),
+         })
+ 
+       if a["type"] == "article":
+-        m["content"] += "<p><b><a href=\"%s\">%s</a></b></p>" % (a["links"]["alternate"][0]["href"], a["title"])
++        m["content"] += to_unicode("<p><b><a href=\"%s\">%s</a></b></p>" % (a["links"]["alternate"][0]["href"], a["title"]))
+ 
+     return m
+ 
+diff -up gwibber-3.3.1.1/gwibber/microblog/plugins/digg/__init__.py.kitchen gwibber-3.3.1.1/gwibber/microblog/plugins/digg/__init__.py
+--- gwibber-3.3.1.1/gwibber/microblog/plugins/digg/__init__.py.kitchen	2011-06-02 09:25:01.000000000 -0400
++++ gwibber-3.3.1.1/gwibber/microblog/plugins/digg/__init__.py	2011-11-30 14:19:55.707601597 -0500
+@@ -1,6 +1,7 @@
+ from gwibber.microblog import network, util
+ from gwibber.microblog.util import resources
+ from gettext import lgettext as _
++from kitchen.text.converters import to_unicode
+ 
+ PROTOCOL_INFO = {
+   "name": "Digg",
+@@ -33,33 +34,33 @@ class Client:
+   def _story(self, data):
+     m = {}; 
+     m["mid"] = str(data["id"])
+-    m["service"] = "digg"
+-    m["account"] = self.account["id"]
++    m["service"] = u"digg"
++    m["account"] = to_unicode(self.account["id"])
+     m["time"] = data["submit_date"]
+ 
+-    m["text"] = data["title"] + "\n" + data["description"]
+-    m["content"] = "<b>%(title)s</b><br />%(description)s" % data
+-    m["html"] = "<b>%(title)s</b><br />%(description)s" % data
++    m["text"] = to_unicode(data["title"]) + u"\n" + to_unicode(data["description"])
++    m["content"] = to_unicode("<b>%(title)s</b><br />%(description)s" % data)
++    m["html"] = to_unicode("<b>%(title)s</b><br />%(description)s" % data)
+     user = data["friends"]["users"][0]
+ 
+     m["sender"] = {}
+-    m["sender"]["nick"] = user["name"]
+-    m["sender"]["id"] = user["name"]
+-    m["sender"]["image"] = user["icon"]
+-    m["sender"]["url"] = "http://digg.com/users/%s" % user["name"]
++    m["sender"]["nick"] = to_unicode(user["name"])
++    m["sender"]["id"] = to_unicode(user["name"])
++    m["sender"]["image"] = to_unicode(user["icon"])
++    m["sender"]["url"] = to_unicode("http://digg.com/users/%s" % user["name"])
+     m["sender"]["is_me"] = user["name"] == self.account["username"]
+-    if user.get("fullname", 0): m["sender"]["name"] = user["fullname"]
++    if user.get("fullname", 0): m["sender"]["name"] = to_unicode(user["fullname"])
+     
+-    m["url"] = data["link"]
++    m["url"] = to_unicode(data["link"])
+     m["likes"] = {"count": data["diggs"]}
+ 
+-    m["html"] = util.linkify(m["text"],
++    m["html"] = to_unicode(util.linkify(m["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)))
++      (util.PARSE_NICK, '@<a class="nick" href="%s/\\1">\\1</a>' % URL_PREFIX))))
+ 
+-    m["content"] = util.linkify(m["text"],
++    m["content"] = to_unicode(util.linkify(m["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"])))
++      (util.PARSE_NICK, '@<a class="nick" href="gwibber:/user?acct=%s&name=\\1">\\1</a>' % m["account"]))))
+ 
+     return m
+ 
+diff -up gwibber-3.3.1.1/gwibber/microblog/plugins/facebook/__init__.py.kitchen gwibber-3.3.1.1/gwibber/microblog/plugins/facebook/__init__.py
+--- gwibber-3.3.1.1/gwibber/microblog/plugins/facebook/__init__.py.kitchen	2011-08-22 11:13:29.000000000 -0400
++++ gwibber-3.3.1.1/gwibber/microblog/plugins/facebook/__init__.py	2011-11-30 14:23:54.571537504 -0500
+@@ -6,6 +6,7 @@ import hashlib, mx.DateTime, time
+ from os.path import join, getmtime, exists
+ from gettext import lgettext as _
+ from gwibber.microblog.util.const import *
++from kitchen.text.converters import to_unicode
+ # Try to import * from custom, install custom.py to include packaging 
+ # customizations like distro API keys, etc
+ try:
+@@ -101,11 +102,11 @@ class Client:
+ 
+   def _sender(self, data):
+     sender = {
+-      "name": data["name"],
++      "name": to_unicode(data["name"]),
+       "id": str(data.get("id", '')),
+       "is_me": str(data.get("id", '')) == self.user_id,
+-      "image": URL_PREFIX + data["id"] + "/picture",
+-      "url": "https://www.facebook.com/profile.php?id=" + str(data.get("id", ''))
++      "image": to_unicode(URL_PREFIX) + to_unicode(data["id"]) + u"/picture",
++      "url": u"https://www.facebook.com/profile.php?id=" + to_unicode(str(data.get("id", '')))
+     }
+     return sender
+   
+@@ -115,27 +116,27 @@ class Client:
+       return {}
+ 
+     m = {}
+-    m["mid"] = str(data["id"])
+-    m["service"] = "facebook"
++    m["mid"] = to_unicode(str(data["id"]))
++    m["service"] = u"facebook"
+     m["account"] = self.account["id"]
+ 
+     m["time"] = int(mx.DateTime.DateTimeFrom(str(data.get("updated_time", data["created_time"]))))
+-    m["url"] = "https://facebook.com/" + data["id"].split("_")[0] + "/posts/" + data["id"].split("_")[1]
++    m["url"] = u"https://facebook.com/" + to_unicode(data["id"].split("_")[0]) + u"/posts/" + to_unicode(data["id"].split("_")[1])
+     
+ 
+     if data.get("attribution", 0):
+-      m["source"] = util.strip_urls(data["attribution"]).replace("via ", "")
++      m["source"] = to_unicode(util.strip_urls(data["attribution"]).replace("via ", ""))
+     
+     if data.has_key("message"):
+       m["to_me"] = ("@%s" % self.account["username"]) in data["message"]
+     if data.get("message", "").strip():
+-      m["text"] = data["message"]
+-      m["html"] = util.linkify(data["message"])
+-      m["content"] = m["html"]
++      m["text"] = to_unicode(data["message"])
++      m["html"] = to_unicode(util.linkify(data["message"]))
++      m["content"] = to_unicode(m["html"])
+     else:
+-      m["text"] = ""
+-      m["html"] = ""
+-      m["content"] = ""
++      m["text"] = u""
++      m["html"] = u""
++      m["content"] = u""
+ 
+     m["sender"] = self._sender(data["from"])
+ 
+@@ -186,17 +187,17 @@ class Client:
+       if data["comments"].has_key("data"):
+         for item in data["comments"]["data"]:
+           m["comments"].append({
+-              "text": item["message"],
++              "text": to_unicode(item["message"]),
+               "time": int(mx.DateTime.DateTimeFrom(str(data.get("updated_time", data["created_time"])))),
+               "sender": self._sender(item["from"]),
+             })
+ 
+     if data.get("attachment", 0):
+       if data["attachment"].get("name", 0):
+-        m["content"] += "<p><b>%s</b></p>" % data["attachment"]["name"]
++        m["content"] += to_unicode("<p><b>%s</b></p>" % data["attachment"]["name"])
+ 
+       if data["attachment"].get("description", 0):
+-        m["content"] += "<p>%s</p>" % data["attachment"]["description"]
++        m["content"] += to_unicode("<p>%s</p>" % data["attachment"]["description"])
+ 
+     return m
+ 
+diff -up gwibber-3.3.1.1/gwibber/microblog/plugins/identica/__init__.py.kitchen gwibber-3.3.1.1/gwibber/microblog/plugins/identica/__init__.py
+--- gwibber-3.3.1.1/gwibber/microblog/plugins/identica/__init__.py.kitchen	2011-11-10 09:40:58.000000000 -0500
++++ gwibber-3.3.1.1/gwibber/microblog/plugins/identica/__init__.py	2011-11-30 14:27:18.931930075 -0500
+@@ -4,6 +4,7 @@ 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 = "Identi.ca"
+ 
+ PROTOCOL_INFO = {
+@@ -62,21 +63,21 @@ class Client:
+     m = {}
+     try:
+       m["mid"] = str(data["id"])
+-      m["service"] = "identica"
++      m["service"] = u"identica"
+       m["account"] = self.account["id"]
+       if data.has_key("created_at"):
+         m["time"] = util.parsetime(data["created_at"])
+-      m["source"] = data.get("source", False)
+-      m["text"] = util.unescape(data["text"])
++      m["source"] = to_unicode(data.get("source", False))
++      m["text"] = to_unicode(util.unescape(data["text"]))
+       m["to_me"] = ("@%s" % self.account["username"]) in data["text"]
+ 
+-      m["html"] = util.linkify(m["text"],
++      m["html"] = to_unicode(util.linkify(m["text"],
+         ((util.PARSE_HASH, '#<a class="hash" href="%s#search?q=\\1">\\1</a>' % self.url_prefix),
+-        (util.PARSE_NICK, '@<a class="nick" href="%s/\\1">\\1</a>' % self.url_prefix)), escape=False)
++        (util.PARSE_NICK, '@<a class="nick" href="%s/\\1">\\1</a>' % self.url_prefix)), escape=False))
+ 
+-      m["content"] = util.linkify(m["text"],
++      m["content"] = to_unicode(util.linkify(m["text"],
+         ((util.PARSE_HASH, '#<a href="gwibber:/tag?acct=%s&query=\\1">\\1</a>' % m["account"]),
+-        (util.PARSE_NICK, '@<a href="gwibber:/user?acct=%s&name=\\1">\\1</a>' % m["account"])), escape=True)
++        (util.PARSE_NICK, '@<a href="gwibber:/user?acct=%s&name=\\1">\\1</a>' % m["account"])), escape=True))
+ 
+       m["favorited"] = data.get("favorited", False)
+ 
+@@ -85,7 +86,7 @@ class Client:
+         for a in data["attachments"]:
+           mime = a.get("mimetype", "")
+           if mime and mime.startswith("image") and a.get("url", 0):
+-            images.append({"src": a["url"], "url": a["url"]})
++            images.append({"src": to_unicode(a["url"]), "url": to_unicode(a["url"])})
+ 
+       images.extend(util.imgpreview(m["text"]))
+   
+@@ -99,19 +100,19 @@ class Client:
+ 
+   def _user(self, user):
+     return {
+-        "name": user.get("name", None),
+-        "nick": user.get("screen_name", None),
++        "name": to_unicode(user.get("name", None)),
++        "nick": to_unicode(user.get("screen_name", None)),
+         "id": user.get("id", None),
+-        "location": user.get("location", None),
++        "location": to_unicode(user.get("location", None)),
+         "followers": user.get("followers_count", None),
+         "friends": user.get("friends_count", None),
+-        "description": user.get("description", None),
++        "description": to_unicode(user.get("description", None)),
+         "following": user.get("following", None),
+         "protected": user.get("protected", None),
+         "statuses": user.get("statuses_count", None),
+-        "image": user.get("profile_image_url", None),
+-        "website": user.get("url", None),
+-        "url": "/".join((self.url_prefix, user["screen_name"])) or None,
++        "image": to_unicode(user.get("profile_image_url", None)),
++        "website": to_unicode(user.get("url", None)),
++        "url": to_unicode("/".join((self.url_prefix, user["screen_name"]))) or None,
+         "is_me": user.get("screen_name", None) == self.account["username"],
+     }
+ 
+@@ -140,9 +141,9 @@ class Client:
+       if data["in_reply_to_status_id"]:
+         m["reply"] = {}
+         m["reply"]["id"] = data["in_reply_to_status_id"]
+-        m["reply"]["nick"] = data["in_reply_to_screen_name"]
++        m["reply"]["nick"] = to_unicode(data["in_reply_to_screen_name"])
+         if m["reply"]["id"] and m["reply"]["nick"]:
+-          m["reply"]["url"] = "/".join((self.url_prefix, "notice", str(m["reply"]["id"])))
++          m["reply"]["url"] = to_unicode("/".join((self.url_prefix, "notice", str(m["reply"]["id"]))))
+         else:
+           m["reply"]["url"] = None
+ 
+@@ -155,12 +156,12 @@ class Client:
+     m = self._message(data)
+     m["private"] = True
+     m["recipient"] = {}
+-    m["recipient"]["name"] = data["recipient"]["name"]
+-    m["recipient"]["nick"] = data["recipient"]["screen_name"]
++    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"] = data["recipient"]["profile_image_url"]
+-    m["recipient"]["location"] = data["recipient"]["location"]
+-    m["recipient"]["url"] = "/".join((self.url_prefix, m["recipient"]["nick"]))
++    m["recipient"]["image"] = to_unicode(data["recipient"]["profile_image_url"])
++    m["recipient"]["location"] = to_unicode(data["recipient"]["location"])
++    m["recipient"]["url"] = to_unicode("/".join((self.url_prefix, m["recipient"]["nick"])))
+     m["recipient"]["is_me"] = m["recipient"]["nick"].lower() == self.account["username"].lower()
+     m["to_me"] = m["recipient"]["is_me"]
+     return m
+@@ -171,14 +172,14 @@ class Client:
+     if data["to_user_id"]:
+       m["reply"] = {}
+       m["reply"]["id"] = data["to_user_id"]
+-      m["reply"]["nick"] = data["to_user"]
++      m["reply"]["nick"] = to_unicode(data["to_user"])
+ 
+     m["sender"] = {}
+-    m["sender"]["nick"] = data["from_user"]
++    m["sender"]["nick"] = to_unicode(data["from_user"])
+     m["sender"]["id"] = data["from_user_id"]
+-    m["sender"]["image"] = data["profile_image_url"]
+-    m["sender"]["url"] = "/".join((self.url_prefix, m["sender"]["nick"]))
+-    m["url"] = "/".join((self.url_prefix, "notice", str(m["mid"])))
++    m["sender"]["image"] = to_unicode(data["profile_image_url"])
++    m["sender"]["url"] = to_unicode("/".join((self.url_prefix, m["sender"]["nick"])))
++    m["url"] = to_unicode("/".join((self.url_prefix, "notice", str(m["mid"]))))
+     return m
+ 
+   def _profile(self, data):
+diff -up gwibber-3.3.1.1/gwibber/microblog/plugins/statusnet/__init__.py.kitchen gwibber-3.3.1.1/gwibber/microblog/plugins/statusnet/__init__.py
+--- gwibber-3.3.1.1/gwibber/microblog/plugins/statusnet/__init__.py.kitchen	2011-11-10 09:40:58.000000000 -0500
++++ gwibber-3.3.1.1/gwibber/microblog/plugins/statusnet/__init__.py	2011-11-30 14:30:02.877846782 -0500
+@@ -4,6 +4,7 @@ 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 = "StatusNet"
+ 
+ PROTOCOL_INFO = {
+@@ -65,21 +66,21 @@ class Client:
+     m = {}
+     try:
+       m["mid"] = str(data["id"])
+-      m["service"] = "statusnet"
++      m["service"] = u"statusnet"
+       m["account"] = self.account["id"]
+       if data.has_key("created_at"):
+         m["time"] = util.parsetime(data["created_at"])
+-      m["source"] = data.get("source", False)
+-      m["text"] = util.unescape(data["text"])
++      m["source"] = to_unicode(data.get("source", False))
++      m["text"] = to_unicode(util.unescape(data["text"]))
+       m["to_me"] = ("@%s" % self.account["username"]) in data["text"]
+ 
+-      m["html"] = util.linkify(m["text"],
++      m["html"] = to_unicode(util.linkify(m["text"],
+         ((util.PARSE_HASH, '#<a class="hash" href="%s#search?q=\\1">\\1</a>' % self.account["url_prefix"]),
+-        (util.PARSE_NICK, '@<a class="nick" href="%s/\\1">\\1</a>' % self.account["url_prefix"])), escape=False)
++        (util.PARSE_NICK, '@<a class="nick" href="%s/\\1">\\1</a>' % self.account["url_prefix"])), escape=False))
+ 
+-      m["content"] = util.linkify(m["text"],
++      m["content"] = to_unicode(util.linkify(m["text"],
+         ((util.PARSE_HASH, '#<a href="gwibber:/tag?acct=%s&query=\\1">\\1</a>' % m["account"]),
+-        (util.PARSE_NICK, '@<a href="gwibber:/user?acct=%s&name=\\1">\\1</a>' % m["account"])), escape=True)
++        (util.PARSE_NICK, '@<a href="gwibber:/user?acct=%s&name=\\1">\\1</a>' % m["account"])), escape=True))
+ 
+       m["favorited"] = data.get("favorited", False)
+ 
+@@ -88,7 +89,7 @@ class Client:
+         for a in data["attachments"]:
+           mime = a.get("mimetype", "")
+           if mime and mime.startswith("image") and a.get("url", 0):
+-            images.append({"src": a["url"], "url": a["url"]})
++            images.append({"src": to_unicode(a["url"]), "url": to_unicode(a["url"])})
+ 
+       images.extend(util.imgpreview(m["text"]))
+   
+@@ -102,19 +103,19 @@ class Client:
+ 
+   def _user(self, user):
+     return {
+-        "name": user.get("name", None),
+-        "nick": user.get("screen_name", None),
++        "name": to_unicode(user.get("name", None)),
++        "nick": to_unicode(user.get("screen_name", None)),
+         "id": user.get("id", None),
+         "location": user.get("location", None),
+         "followers": user.get("followers_count", None),
+         "friends": user.get("friends_count", None),
+-        "description": user.get("description", None),
++        "description": to_unicode(user.get("description", None)),
+         "following": user.get("following", None),
+         "protected": user.get("protected", None),
+         "statuses": user.get("statuses_count", None),
+-        "image": user.get("profile_image_url", None),
+-        "website": user.get("url", None),
+-        "url": "/".join((self.url_prefix, user["screen_name"])) or None,
++        "image": to_unicode(user.get("profile_image_url", None)),
++        "website": to_unicode(user.get("url", None)),
++        "url": to_unicode("/".join((self.url_prefix, user["screen_name"]))) or None,
+         "is_me": user.get("screen_name", None) == self.account["username"],
+     }
+ 
+@@ -142,14 +143,14 @@ class Client:
+       if data["in_reply_to_status_id"]:
+         m["reply"] = {}
+         m["reply"]["id"] = data["in_reply_to_status_id"]
+-        m["reply"]["nick"] = data["in_reply_to_screen_name"]
++        m["reply"]["nick"] = to_unicode(data["in_reply_to_screen_name"])
+         if m["reply"]["id"] and m["reply"]["nick"]:
+-          m["reply"]["url"] = "/".join((self.account["url_prefix"], "notice", str(m["reply"]["id"])))
++          m["reply"]["url"] = to_unicode("/".join((self.account["url_prefix"], "notice", str(m["reply"]["id"]))))
+         else:
+           m["reply"]["url"] = None
+ 
+     m["sender"] = self._user(data["user"] if "user" in data else data["sender"])
+-    m["url"] = "/".join((self.account["url_prefix"], "notice", str(m["mid"])))
++    m["url"] = to_unicode("/".join((self.account["url_prefix"], "notice", str(m["mid"]))))
+ 
+     return m
+ 
+@@ -157,12 +158,12 @@ class Client:
+     m = self._message(data)
+     m["private"] = True
+     m["recipient"] = {}
+-    m["recipient"]["name"] = data["recipient"]["name"]
+-    m["recipient"]["nick"] = data["recipient"]["screen_name"]
++    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"] = data["recipient"]["profile_image_url"]
+-    m["recipient"]["location"] = data["recipient"]["location"]
+-    m["recipient"]["url"] = "/".join((self.account["url_prefix"], m["recipient"]["nick"]))
++    m["recipient"]["image"] = to_unicode(data["recipient"]["profile_image_url"])
++    m["recipient"]["location"] = to_unicode(data["recipient"]["location"])
++    m["recipient"]["url"] = to_unicode("/".join((self.account["url_prefix"], m["recipient"]["nick"])))
+     m["recipient"]["is_me"] = m["recipient"]["nick"].lower() == self.account["username"].lower()
+     m["to_me"] = m["recipient"]["is_me"]
+     return m
+@@ -176,11 +177,11 @@ class Client:
+       m["reply"]["nick"] = data["to_user"]
+ 
+     m["sender"] = {}
+-    m["sender"]["nick"] = data["from_user"]
++    m["sender"]["nick"] = to_unicode(data["from_user"])
+     m["sender"]["id"] = data["from_user_id"]
+-    m["sender"]["image"] = data["profile_image_url"]
+-    m["sender"]["url"] = "/".join((self.account["url_prefix"], m["sender"]["nick"]))
+-    m["url"] = "/".join((self.account["url_prefix"], "notice", str(m["mid"])))
++    m["sender"]["image"] = to_unicode(data["profile_image_url"])
++    m["sender"]["url"] = to_unicode("/".join((self.account["url_prefix"], m["sender"]["nick"])))
++    m["url"] = to_unicode("/".join((self.account["url_prefix"], "notice", str(m["mid"]))))
+     return m
+ 
+   def _profile(self, data):
+diff -up gwibber-3.3.1.1/gwibber/microblog/plugins/twitter/__init__.py.kitchen gwibber-3.3.1.1/gwibber/microblog/plugins/twitter/__init__.py
+--- gwibber-3.3.1.1/gwibber/microblog/plugins/twitter/__init__.py.kitchen	2011-11-10 11:20:13.000000000 -0500
++++ gwibber-3.3.1.1/gwibber/microblog/plugins/twitter/__init__.py	2011-11-30 14:34:34.582409176 -0500
+@@ -4,6 +4,7 @@ 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 = "Twitter"
+ 
+ PROTOCOL_INFO = {
+@@ -75,20 +76,20 @@ class Client:
+     try:
+ 
+       m["mid"] = str(data["id"])
+-      m["service"] = "twitter"
++      m["service"] = u"twitter"
+       m["account"] = self.account["id"]
+       if data.has_key("created_at"):
+         m["time"] = util.parsetime(data["created_at"])
+-      m["text"] = util.unescape(data["text"])
++      m["text"] = to_unicode(util.unescape(data["text"]))
+       m["to_me"] = ("@%s" % self.account["username"]) in data["text"]
+ 
+-      m["html"] = util.linkify(m["text"],
++      m["html"] = to_unicode(util.linkify(m["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)
++        (util.PARSE_NICK, '@<a class="nick" href="%s/\\1">\\1</a>' % URL_PREFIX)), escape=False))
+ 
+-      m["content"] = util.linkify(m["text"],
++      m["content"] = to_unicode(util.linkify(m["text"],
+         ((util.PARSE_HASH, '#<a href="gwibber:/tag?acct=%s&query=\\1">\\1</a>' % m["account"]),
+-        (util.PARSE_NICK, '@<a href="gwibber:/user?acct=%s&name=\\1">\\1</a>' % m["account"])), escape=True)
++        (util.PARSE_NICK, '@<a href="gwibber:/user?acct=%s&name=\\1">\\1</a>' % m["account"])), escape=True))
+ 
+       m["favorited"] = data.get("favorited", False)
+ 
+@@ -104,19 +105,19 @@ class Client:
+ 
+   def _user(self, user):
+     return {
+-        "name": user.get("name", None),
+-        "nick": user.get("screen_name", None),
++        "name": to_unicode(user.get("name", None)),
++        "nick": to_unicode(user.get("screen_name", None)),
+         "id": user.get("id", None),
+-        "location": user.get("location", None),
++        "location": to_unicode(user.get("location", None)),
+         "followers": user.get("followers_count", None),
+         "friends": user.get("friends_count", None),
+-        "description": user.get("description", None),
++        "description": to_unicode(user.get("description", None)),
+         "following": user.get("following", None),
+         "protected": user.get("protected", None),
+         "statuses": user.get("statuses_count", None),
+-        "image": user.get("profile_image_url", None),
+-        "website": user.get("url", None),
+-        "url": "/".join((URL_PREFIX, user.get("screen_name", ""))) or None,
++        "image": to_unicode(user.get("profile_image_url", None)),
++        "website": to_unicode(user.get("url", None)),
++        "url": to_unicode("/".join((URL_PREFIX, user.get("screen_name", "")))) or None,
+         "is_me": user.get("screen_name", None) == self.account["username"],
+     }
+     
+@@ -146,14 +147,14 @@ class Client:
+       if data["in_reply_to_status_id"]:
+         m["reply"] = {}
+         m["reply"]["id"] = data["in_reply_to_status_id"]
+-        m["reply"]["nick"] = data["in_reply_to_screen_name"]
++        m["reply"]["nick"] = to_unicode(data["in_reply_to_screen_name"])
+         if m["reply"]["id"] and m["reply"]["nick"]:
+-          m["reply"]["url"] = "/".join((URL_PREFIX, m["reply"]["nick"], "statuses", str(m["reply"]["id"])))
++          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"] = "/".join((m["sender"]["url"], "statuses", str(m["mid"])))
++    m["url"] = to_unicode("/".join((m["sender"]["url"], "statuses", str(m["mid"]))))
+ 
+     return m
+ 
+@@ -162,12 +163,12 @@ class Client:
+     m["private"] = True
+ 
+     m["recipient"] = {}
+-    m["recipient"]["name"] = data["recipient"]["name"]
+-    m["recipient"]["nick"] = data["recipient"]["screen_name"]
++    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"] = data["recipient"]["profile_image_url"]
+-    m["recipient"]["location"] = data["recipient"]["location"]
+-    m["recipient"]["url"] = "/".join((URL_PREFIX, m["recipient"]["nick"]))
++    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"]
+ 
+@@ -179,15 +180,15 @@ class Client:
+     if data["to_user_id"]:
+       m["reply"] = {}
+       m["reply"]["id"] = data["to_user_id"]
+-      m["reply"]["nick"] = data["to_user"]
++      m["reply"]["nick"] = to_unicode(data["to_user"])
+ 
+     m["sender"] = {}
+-    m["sender"]["nick"] = data["from_user"]
++    m["sender"]["nick"] = to_unicode(data["from_user"])
+     m["sender"]["id"] = data["from_user_id"]
+-    m["sender"]["image"] = data["profile_image_url"]
+-    m["sender"]["url"] = "/".join((URL_PREFIX, m["sender"]["nick"]))
++    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"] = "/".join((m["sender"]["url"], "statuses", str(m["mid"])))
++    m["url"] = to_unicode("/".join((m["sender"]["url"], "statuses", str(m["mid"]))))
+     return m
+ 
+   def _profile(self, data):
+@@ -197,20 +198,20 @@ class Client:
+         }
+     return {
+         "name": data.get("name", data["screen_name"]),
+-        "service": "twitter",
++        "service": u"twitter",
+         "stream": "profile",
+         "account": self.account["id"],
+         "mid": data["id"],
+-        "text": data.get("description", ""),
+-        "nick": data["screen_name"],
+-        "url": data.get("url", ""),
++        "text": to_unicode(data.get("description", "")),
++        "nick": to_unicode(data["screen_name"]),
++        "url": to_unicode(data.get("url", "")),
+         "protected": data.get("protected", False),
+         "statuses": data.get("statuses_count", 0),
+         "followers": data.get("followers_count", 0),
+         "friends": data.get("friends_count", 0),
+         "following": data.get("following", 0),
+         "favourites": data.get("favourites_count", 0),
+-        "image": data["profile_image_url"],
++        "image": to_unicode(data["profile_image_url"]),
+         "utc_offset": data.get("utc_offset", 0),
+         "id": data["id"],
+         "lang": data.get("lang", "en"),
+@@ -222,23 +223,23 @@ class Client:
+   def _list(self, data):
+     return {
+         "mid": data["id"],
+-        "service": "twitter",
++        "service": u"twitter",
+         "account": self.account["id"],
+         "time": 0,
+-        "text": data["description"],
+-        "html": data["description"],
+-        "content": data["description"],
+-        "url": "/".join((URL_PREFIX, data["uri"])),
+-        "sender": self._user(data["user"]),
+-        "name": data["name"],
+-        "nick": data["slug"],
++        "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": data["full_name"],
+-        "uri": data["uri"],
++        "full": to_unicode(data["full_name"]),
++        "uri": to_unicode(data["uri"]),
+         "mode": data["mode"],
+         "members": data["member_count"],
+         "followers": data["subscriber_count"],
+-        "kind": "list",
++        "kind": u"list",
+     }
+ 
+   def _get(self, path, parse="message", post=False, single=False, **args):
diff --git a/gwibber-3.3.1.1-no-gtkspell.patch b/gwibber-3.3.1.1-no-gtkspell.patch
new file mode 100644
index 0000000..1464c77
--- /dev/null
+++ b/gwibber-3.3.1.1-no-gtkspell.patch
@@ -0,0 +1,14 @@
+diff -up gwibber-3.3.1.1/vapi/Makefile.am.no-gtkspell gwibber-3.3.1.1/vapi/Makefile.am
+--- gwibber-3.3.1.1/vapi/Makefile.am.no-gtkspell	2011-10-25 12:25:06.000000000 -0400
++++ gwibber-3.3.1.1/vapi/Makefile.am	2011-11-30 15:02:18.115657117 -0500
+@@ -1,5 +1,8 @@
+ EXTRA_DIST = \
+ 	config.vapi \
+ 	libnotify.deps \
+-	libnotify.vapi \
+-	gtkspell-2.0.vapi
++	libnotify.vapi
++
++if HAVE_GTKSPELL
++EXTRA_DIST += gtkspell-2.0.vapi
++endif
diff --git a/gwibber-3.3.1.1-no-unity.patch b/gwibber-3.3.1.1-no-unity.patch
new file mode 100644
index 0000000..22e0ffd
--- /dev/null
+++ b/gwibber-3.3.1.1-no-unity.patch
@@ -0,0 +1,18 @@
+diff -up gwibber-3.3.1.1/client/Makefile.am.no-unity gwibber-3.3.1.1/client/Makefile.am
+--- gwibber-3.3.1.1/client/Makefile.am.no-unity	2011-08-30 15:54:40.000000000 -0400
++++ gwibber-3.3.1.1/client/Makefile.am	2011-11-30 14:46:30.054431541 -0500
+@@ -69,10 +69,13 @@ gwibber_preferences_VALAFLAGS = \
+         --pkg atk \
+         --pkg glib-2.0 \
+         --pkg gmodule-2.0 \
+-        --pkg Dbusmenu-0.4 \
+ 	$(MAINTAINER_VALAFLAGS) \
+ 	$(NULL)
+ 
++if HAVE_UNITY
++gwibber_preferences_VALAFLAGS += --pkg Dbusmenu-0.4
++endif
++
+ gwibber_preferences_LDADD = \
+         $(BASE_LIBS) 
+ 
diff --git a/gwibber-3.3.1.1-sina.patch b/gwibber-3.3.1.1-sina.patch
new file mode 100644
index 0000000..bc9e716
--- /dev/null
+++ b/gwibber-3.3.1.1-sina.patch
@@ -0,0 +1,817 @@
+diff -up gwibber-3.3.1.1/configure.ac.sina gwibber-3.3.1.1/configure.ac
+--- gwibber-3.3.1.1/configure.ac.sina	2011-11-21 16:35:35.000000000 -0500
++++ gwibber-3.3.1.1/configure.ac	2011-11-30 14:37:56.447866098 -0500
+@@ -281,6 +281,10 @@ AC_CONFIG_FILES([
+   gwibber/microblog/plugins/qaiku/gtk/Makefile
+   gwibber/microblog/plugins/qaiku/gtk/qaiku/Makefile
+   gwibber/microblog/plugins/qaiku/ui/Makefile
++  gwibber/microblog/plugins/sina/Makefile
++  gwibber/microblog/plugins/sina/gtk/Makefile
++  gwibber/microblog/plugins/sina/gtk/sina/Makefile
++  gwibber/microblog/plugins/sina/ui/Makefile
+   gwibber/microblog/plugins/statusnet/Makefile
+   gwibber/microblog/plugins/statusnet/gtk/Makefile
+   gwibber/microblog/plugins/statusnet/gtk/statusnet/Makefile
+diff -up gwibber-3.3.1.1/data/icons/breakdance/16x16/Makefile.am.sina gwibber-3.3.1.1/data/icons/breakdance/16x16/Makefile.am
+--- gwibber-3.3.1.1/data/icons/breakdance/16x16/Makefile.am.sina	2011-06-23 16:39:09.000000000 -0400
++++ gwibber-3.3.1.1/data/icons/breakdance/16x16/Makefile.am	2011-11-30 14:37:56.447866098 -0500
+@@ -9,6 +9,7 @@ icon_DATA = 	brightkite.png	\
+ 		openid.png	\
+ 		pingfm.png	\
+ 		qaiku.png	\
++		sina.png	\
+ 		statusnet.png	\
+ 		stumbleupon.png	\
+ 		twitter.png
+diff -up gwibber-3.3.1.1/data/icons/breakdance/22x22/Makefile.am.sina gwibber-3.3.1.1/data/icons/breakdance/22x22/Makefile.am
+--- gwibber-3.3.1.1/data/icons/breakdance/22x22/Makefile.am.sina	2011-06-23 16:39:09.000000000 -0400
++++ gwibber-3.3.1.1/data/icons/breakdance/22x22/Makefile.am	2011-11-30 14:37:56.447866098 -0500
+@@ -9,6 +9,7 @@ icon_DATA = 	brightkite.png	\
+ 		openid.png	\
+ 		pingfm.png	\
+ 		qaiku.png	\
++		sina.png	\
+ 		statusnet.png	\
+ 		stumbleupon.png	\
+ 		twitter.png
+diff -up gwibber-3.3.1.1/data/icons/breakdance/32x32/Makefile.am.sina gwibber-3.3.1.1/data/icons/breakdance/32x32/Makefile.am
+--- gwibber-3.3.1.1/data/icons/breakdance/32x32/Makefile.am.sina	2011-06-23 16:39:09.000000000 -0400
++++ gwibber-3.3.1.1/data/icons/breakdance/32x32/Makefile.am	2011-11-30 14:37:56.447866098 -0500
+@@ -9,6 +9,7 @@ icon_DATA = 	brightkite.png	\
+ 		openid.png	\
+ 		pingfm.png	\
+ 		qaiku.png	\
++		sina.png	\
+ 		statusnet.png	\
+ 		stumbleupon.png	\
+ 		twitter.png
+diff -up gwibber-3.3.1.1/data/icons/breakdance/scalable/Makefile.am.sina gwibber-3.3.1.1/data/icons/breakdance/scalable/Makefile.am
+--- gwibber-3.3.1.1/data/icons/breakdance/scalable/Makefile.am.sina	2011-06-23 16:39:09.000000000 -0400
++++ gwibber-3.3.1.1/data/icons/breakdance/scalable/Makefile.am	2011-11-30 14:37:56.448866086 -0500
+@@ -9,6 +9,7 @@ icon_DATA = 	brightkite.svg	\
+ 		openid.svg	\
+ 		pingfm.svg	\
+ 		qaiku.svg	\
++		sina.svg	\
+ 		statusnet.svg	\
+ 		stumbleupon.svg	\
+ 		twitter.svg
+diff -up gwibber-3.3.1.1/gwibber/microblog/plugins/Makefile.am.sina gwibber-3.3.1.1/gwibber/microblog/plugins/Makefile.am
+--- gwibber-3.3.1.1/gwibber/microblog/plugins/Makefile.am.sina	2011-06-23 16:39:09.000000000 -0400
++++ gwibber-3.3.1.1/gwibber/microblog/plugins/Makefile.am	2011-11-30 14:38:25.091505943 -0500
+@@ -1,4 +1,4 @@
+-SUBDIRS = buzz digg facebook flickr foursquare friendfeed identica pingfm qaiku statusnet twitter
++SUBDIRS = buzz digg facebook flickr foursquare friendfeed identica pingfm qaiku sina statusnet twitter
+ 
+ plugindir = $(datadir)/gwibber/plugins/
+ plugin_PYTHON = \
+diff -up gwibber-3.3.1.1/gwibber/microblog/plugins/sina/gtk/__init__.py.sina gwibber-3.3.1.1/gwibber/microblog/plugins/sina/gtk/__init__.py
+--- gwibber-3.3.1.1/gwibber/microblog/plugins/sina/gtk/__init__.py.sina	2011-11-30 14:37:56.448866086 -0500
++++ gwibber-3.3.1.1/gwibber/microblog/plugins/sina/gtk/__init__.py	2011-11-30 14:37:56.448866086 -0500
+@@ -0,0 +1 @@
++ 
+diff -up gwibber-3.3.1.1/gwibber/microblog/plugins/sina/gtk/Makefile.am.sina gwibber-3.3.1.1/gwibber/microblog/plugins/sina/gtk/Makefile.am
+--- gwibber-3.3.1.1/gwibber/microblog/plugins/sina/gtk/Makefile.am.sina	2011-11-30 14:37:56.449866074 -0500
++++ gwibber-3.3.1.1/gwibber/microblog/plugins/sina/gtk/Makefile.am	2011-11-30 14:37:56.449866074 -0500
+@@ -0,0 +1,7 @@
++SUBDIRS = sina
++plugindir = $(datadir)/gwibber/plugins/sina/gtk
++plugin_PYTHON = \
++        __init__.py
++
++uninstall-hook:
++	rm -fr $(plugindir)
+diff -up gwibber-3.3.1.1/gwibber/microblog/plugins/sina/gtk/sina/__init__.py.sina gwibber-3.3.1.1/gwibber/microblog/plugins/sina/gtk/sina/__init__.py
+--- gwibber-3.3.1.1/gwibber/microblog/plugins/sina/gtk/sina/__init__.py.sina	2011-11-30 14:37:56.449866074 -0500
++++ gwibber-3.3.1.1/gwibber/microblog/plugins/sina/gtk/sina/__init__.py	2011-11-30 14:37:56.449866074 -0500
+@@ -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.3.1.1/gwibber/microblog/plugins/sina/gtk/sina/Makefile.am.sina gwibber-3.3.1.1/gwibber/microblog/plugins/sina/gtk/sina/Makefile.am
+--- gwibber-3.3.1.1/gwibber/microblog/plugins/sina/gtk/sina/Makefile.am.sina	2011-11-30 14:37:56.449866074 -0500
++++ gwibber-3.3.1.1/gwibber/microblog/plugins/sina/gtk/sina/Makefile.am	2011-11-30 14:37:56.449866074 -0500
+@@ -0,0 +1,6 @@
++plugindir = $(datadir)/gwibber/plugins/sina/gtk/sina
++plugin_PYTHON = \
++        __init__.py
++
++uninstall-hook:
++	rm -fr $(plugindir)
+diff -up gwibber-3.3.1.1/gwibber/microblog/plugins/sina/__init__.py.sina gwibber-3.3.1.1/gwibber/microblog/plugins/sina/__init__.py
+--- gwibber-3.3.1.1/gwibber/microblog/plugins/sina/__init__.py.sina	2011-11-30 14:37:56.449866074 -0500
++++ gwibber-3.3.1.1/gwibber/microblog/plugins/sina/__init__.py	2011-11-30 14:37:56.449866074 -0500
+@@ -0,0 +1,293 @@
++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 _
++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"] = "sina"
++      m["account"] = self.account["id"]
++      m["time"] = util.parsetime(data["created_at"])
++      m["text"] = unescape(data["text"])
++      m["to_me"] = ("@%s" % self.account["username"]) in data["text"]
++
++      m["html"] = 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"] = 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": user["name"],
++        "nick": user["screen_name"],
++        "id": user["id"],
++        "location": user["location"],
++        "followers": user.get("followers", None),
++        "image": user["profile_image_url"],
++        "url": "/".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"] = data["in_reply_to_screen_name"]
++        if m["reply"]["id"] and m["reply"]["nick"]:
++          m["reply"]["url"] = "/".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"] = "/".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"] = data["recipient"]["name"]
++    m["recipient"]["nick"] = data["recipient"]["screen_name"]
++    m["recipient"]["id"] = data["recipient"]["id"]
++    m["recipient"]["image"] = data["recipient"]["profile_image_url"]
++    m["recipient"]["location"] = data["recipient"]["location"]
++    m["recipient"]["url"] = "/".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"] = data["to_user"]
++
++    m["sender"] = {}
++    m["sender"]["nick"] = data["from_user"]
++    m["sender"]["id"] = data["from_user_id"]
++    m["sender"]["image"] = data["profile_image_url"]
++    m["sender"]["url"] = "/".join((URL_PREFIX, m["sender"]["nick"]))
++    m["sender"]["is_me"] = m["sender"]["nick"] == self.account["username"]
++    m["url"] = "/".join((m["sender"]["url"], "statuses", str(m["mid"])))
++    return m
++
++  def _list(self, data):
++    return {
++        "mid": data["id"],
++        "service": "sina",
++        "account": self.account["id"],
++        "time": 0,
++        "text": data["description"],
++        "html": data["description"],
++        "content": data["description"],
++        "url": "/".join((URL_PREFIX, data["uri"])),
++        "sender": self._user(data["user"]),
++        "name": data["name"],
++        "nick": data["slug"],
++        "key": data["slug"],
++        "full": data["full_name"],
++        "uri": data["uri"],
++        "mode": data["mode"],
++        "members": data["member_count"],
++        "followers": data["subscriber_count"],
++        "kind": "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.3.1.1/gwibber/microblog/plugins/sina/Makefile.am.sina gwibber-3.3.1.1/gwibber/microblog/plugins/sina/Makefile.am
+--- gwibber-3.3.1.1/gwibber/microblog/plugins/sina/Makefile.am.sina	2011-11-30 14:37:56.449866074 -0500
++++ gwibber-3.3.1.1/gwibber/microblog/plugins/sina/Makefile.am	2011-11-30 14:37:56.449866074 -0500
+@@ -0,0 +1,7 @@
++SUBDIRS = gtk ui
++plugindir = $(datadir)/gwibber/plugins/sina
++plugin_PYTHON = \
++        __init__.py
++
++uninstall-hook:
++	rm -fr $(plugindir)
+diff -up gwibber-3.3.1.1/gwibber/microblog/plugins/sina/ui/gwibber-accounts-sina.ui.sina gwibber-3.3.1.1/gwibber/microblog/plugins/sina/ui/gwibber-accounts-sina.ui
+--- gwibber-3.3.1.1/gwibber/microblog/plugins/sina/ui/gwibber-accounts-sina.ui.sina	2011-11-30 14:37:56.450866062 -0500
++++ gwibber-3.3.1.1/gwibber/microblog/plugins/sina/ui/gwibber-accounts-sina.ui	2011-11-30 14:37:56.450866062 -0500
+@@ -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.3.1.1/gwibber/microblog/plugins/sina/ui/Makefile.am.sina gwibber-3.3.1.1/gwibber/microblog/plugins/sina/ui/Makefile.am
+--- gwibber-3.3.1.1/gwibber/microblog/plugins/sina/ui/Makefile.am.sina	2011-11-30 14:37:56.450866062 -0500
++++ gwibber-3.3.1.1/gwibber/microblog/plugins/sina/ui/Makefile.am	2011-11-30 14:37:56.450866062 -0500
+@@ -0,0 +1,8 @@
++plugindir = $(datadir)/gwibber/plugins/sina/ui
++plugin_DATA = \
++        gwibber-accounts-sina.ui
++
++uninstall-hook:
++	rm -fr $(plugindir)
++
++EXTRA_DIST = $(plugin_DATA)
+diff -up gwibber-3.3.1.1/gwibber/microblog/util/const.py.in.sina gwibber-3.3.1.1/gwibber/microblog/util/const.py.in
+--- gwibber-3.3.1.1/gwibber/microblog/util/const.py.in.sina	2011-11-30 14:37:56.000000000 -0500
++++ gwibber-3.3.1.1/gwibber/microblog/util/const.py.in	2011-11-30 14:39:05.793994449 -0500
+@@ -15,6 +15,9 @@ else:
+ TWITTER_OAUTH_KEY = "qMBra1U4bpNYvDz947M5Q"
+ TWITTER_OAUTH_SECRET = "Lzdkhg0WvGYFzD9tnsuwC0zYmpJ4z7HrZl3yOxU1g"
+ 
++SINA_OAUTH_KEY = "4014350411"
++SINA_OAUTH_SECRET = "92e7877ad12d59a8410d850c19787701"
++
+ # Gwibber
+ MAX_MESSAGE_LENGTH = 140
+ MAX_MESSAGE_COUNT = 20000
+diff -up gwibber-3.3.1.1/gwibber/microblog/util/resources.py.sina gwibber-3.3.1.1/gwibber/microblog/util/resources.py
+--- gwibber-3.3.1.1/gwibber/microblog/util/resources.py.sina	2011-11-10 09:40:58.000000000 -0500
++++ gwibber-3.3.1.1/gwibber/microblog/util/resources.py	2011-11-30 14:37:56.450866062 -0500
+@@ -83,6 +83,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.3.1.1/po/POTFILES.in.sina gwibber-3.3.1.1/po/POTFILES.in
+--- gwibber-3.3.1.1/po/POTFILES.in.sina	2011-11-21 09:24:35.000000000 -0500
++++ gwibber-3.3.1.1/po/POTFILES.in	2011-11-30 14:39:54.159387082 -0500
+@@ -50,6 +50,8 @@ gwibber/microblog/plugins/pingfm/gtk/pin
+ gwibber/microblog/plugins/qaiku/gtk/qaiku/__init__.py
+ gwibber/microblog/plugins/foursquare/__init__.py
+ gwibber/microblog/plugins/foursquare/gtk/foursquare/__init__.py
++gwibber/microblog/plugins/sina/__init__.py
++gwibber/microblog/plugins/sina/gtk/sina/__init__.py
+ gwibber/util.py
+ [type: gettext/glade] data/gwibber-accounts-dialog.ui
+ [type: gettext/glade] data/gwibber-preferences-dialog.ui
+@@ -64,4 +66,5 @@ gwibber/util.py
+ [type: gettext/glade] gwibber/microblog/plugins/twitter/ui/gwibber-accounts-twitter.ui
+ [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/sina/ui/gwibber-accounts-sina.ui
+ [type: gettext/ini] lens/data/gwibber.lens.in.in
diff --git a/gwibber-3.3.1.1-sqlite-catch_error.patch b/gwibber-3.3.1.1-sqlite-catch_error.patch
new file mode 100644
index 0000000..7287cb5
--- /dev/null
+++ b/gwibber-3.3.1.1-sqlite-catch_error.patch
@@ -0,0 +1,89 @@
+diff -up gwibber-3.3.1.1/gwibber/microblog/storage.py.catch_sqlite_error gwibber-3.3.1.1/gwibber/microblog/storage.py
+--- gwibber-3.3.1.1/gwibber/microblog/storage.py.catch_sqlite_error	2011-10-24 11:32:05.000000000 -0400
++++ gwibber-3.3.1.1/gwibber/microblog/storage.py	2011-11-30 14:41:50.687925563 -0500
+@@ -44,7 +44,10 @@ 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)")
+       self.db.execute("create index IF NOT EXISTS idx2 on messages (stream, time, transient)")
+ 
+@@ -63,10 +66,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
+   
+ 
+@@ -158,14 +167,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):
+@@ -269,15 +281,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.spec b/gwibber.spec
index aa65506..dc69aa1 100644
--- a/gwibber.spec
+++ b/gwibber.spec
@@ -1,10 +1,8 @@
- %{!?python_sitelib: %define python_sitelib %(%{__python} -c "from distutils.sysconfig import get_python_lib; print get_python_lib()")}
-
-%global bzr_rev 894
+%{!?python_sitelib: %define python_sitelib %(%{__python} -c "from distutils.sysconfig import get_python_lib; print get_python_lib()")}
 
 Name:           gwibber
-Version:        3.1.0
-Release:        2%{?dist}
+Version:        3.3.1.1
+Release:        1%{?dist}
 Epoch:          1
 Summary:        An open source microblogging client for GNOME developed with Python and GTK
 Group:          Applications/Internet
@@ -15,7 +13,7 @@ URL:            https://launchpad.net/gwibber
 # following command to generate the tarball:
 #   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/3.2/3.1.0/+download/%{name}-%{version}.tar.gz
+Source0:	http://launchpad.net/gwibber/3.4/3.3.1.1/+download/%{name}-%{version}.tar.gz
 
 # Fix situation where pango_overlay can be undefined
 # https://bugs.launchpad.net/gwibber/+bug/577050
@@ -33,33 +31,21 @@ Patch3:		gwibber-2.91.92-handle-x00-in-content.patch
 # Oauth bits
 Patch6:		gwibber-832bzr-twitter-oauth.patch
 
-# Minimize characters with unicode equivs
-Patch10:	gwibber-3.1.0-minimize-chars.patch
-
 # Fix iter call to non-existent "mark", use "mark_start"
 # bz635345 bz623754
 Patch11:	gwibber-3.1.0-undefined-mark.patch
 
 # Use kitchen to force all strings generated by microblog inputs to unicode
-Patch13:	gwibber-3.1.0-kitchen-unicode.patch
-
-# Right click on a stream in the navigation bar to close it.
-Patch14:	gwibber-875bzr-right-click-to-close-stream.patch
+Patch13:	gwibber-3.3.1.1-kitchen-unicode.patch
 
 # Facebook Fedora key
-Patch15:	gwibber-886bzr-facebook-fedora.patch
-
-# Fix hash tag parsing for unicode
-Patch19:	gwibber-2.91.92-kitchen-unicode-hash.patch
-
-# Gowalla support
-Patch40:	gwibber-3.0.0-gowalla.patch
-Patch41:	gwibber-2.91.92-gowalla-icons.patch
-Patch42:	gwibber-2.91.92-gowalla-kitchen.patch
+Patch15:	gwibber-3.3.1.1-facebook-fedora.patch
 
 # Sina support
-Patch43:	gwibber-3.0.0.1-sina.patch
-Patch44:	gwibber-3.0.0.1-sina-icons.patch
+Patch43:	gwibber-3.3.1.1-sina.patch
+Patch44:	gwibber-3.1.2-sina-icons.patch
+# TODO
+# Kitchen patch
 
 # Catch errors trying to get Twitter Oauth token
 Patch45:	gwibber-3.0.0.1-twitter-catch_error.patch
@@ -71,10 +57,16 @@ Patch46:	gwibber-3.0.0.1-empty_msg.patch
 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
+Patch48:	gwibber-3.3.1.1-sqlite-catch_error.patch
 
 # Overwrite default oauth key
-Patch49:	gwibber-3.1.0-fix-twitter-oauth.patch
+Patch49:	gwibber-3.3.1.1-fix-twitter-oauth.patch
+
+# No unity here, sorry.
+Patch51:	gwibber-3.3.1.1-no-unity.patch
+
+# No gtkspell right now, still only gtk2 in Fedora
+Patch52:	gwibber-3.3.1.1-no-gtkspell.patch
 
 Requires:	libsoup, python-pycurl
 Requires:	python 
@@ -96,12 +88,25 @@ Requires:	gnome-python2-libwnck
 Requires:	gnome-python2-gtkspell
 Requires:	python-kitchen
 BuildRequires:  python-devel, desktop-file-utils, python-distutils-extra, intltool, gettext
-BuildArch:      noarch
+BuildRequires:	gsettings-desktop-schemas-devel, json-glib-devel, libnotify-devel, dee-devel >= 0.5.19
+BuildRequires:	libgee-devel, pango-devel, glib2-devel, gtk3-devel, libsoup-devel
+BuildRequires:	libtool, autoconf, automake
+# This needs to be a gtk3 version.
+# BuildRequires:	gtkspell-devel
 
 %description
 Gwibber is an open source microblogging client for GNOME developed with Python
-and GTK. It supports Twitter, Jaiku, Identi.ca, Facebook, and Digg.
+and GTK. It supports Twitter, Jaiku, Identi.ca, Facebook, Sina, Buzz 
+and Digg.
+
+%package devel
+Summary:	Development files for gwibber's libraries
+Requires:	%{name}%{?_isa} = %{epoch}:%{version}-%{release}
+Requires:	pkgconfig
+Group:		Development/Libraries
 
+%description devel
+Development files for gwibber's libraries.
 
 %prep
 %setup -q
@@ -112,22 +117,12 @@ and GTK. It supports Twitter, Jaiku, Identi.ca, Facebook, and Digg.
 
 %patch6 -p1 -b .oauth
 
-%patch10 -p1 -b .minimize
 %patch11 -p1 -b .missing-mark
 
 %patch13 -p1 -b .kitchen
 
-%patch14 -p1 -b .right-click
-
 %patch15 -p1 -b .fedora
 
-%patch19 -p1 -b .kitchen-hash
-
-# Gowalla
-%patch40 -p1 -b .gowalla
-%patch41 -p1 -b .gowalla-icons
-%patch42 -p1 -b .gowalla-kitchen
-
 # Sina
 %patch43 -p1 -b .sina
 %patch44 -p1 -b .sina-icons
@@ -145,48 +140,65 @@ and GTK. It supports Twitter, Jaiku, Identi.ca, Facebook, and Digg.
 %patch48 -p1 -b .catch_sqlite_error
 
 # Overwrite default oauth key for twitter
-%patch49 -p1
+%patch49 -p1 -b .fedora-twitter
 
-# sed -i -e '/^#! \?\//, 1d' $(find %{name} | grep "\.py$")
+# No unity here, sorry.
+%patch51 -p1 -b .no-unity
 
-%build
-%{__python} setup.py build
+# No gtkspell at the moment
+%patch52 -p1 -b .no-gtkspell
 
+%build
+NOCONFIGURE=true ./autogen.sh
+%configure --disable-unity --disable-static --disable-spell
+pushd libgwibber-gtk
+make clean
+popd
+make
 
 %install
-%{__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
+make DESTDIR=%{buildroot} install
 
 ## Reinstall .desktop file
 rm -rf %{buildroot}%{_datadir}/applications
-desktop-file-install --vendor="fedora" --dir=%{buildroot}%{_datadir}/applications build/share/applications/%{name}.desktop
+desktop-file-install --vendor="fedora" --dir=%{buildroot}%{_datadir}/applications data/%{name}.desktop
+
+rm -rf %{buildroot}%{_libdir}/*.la
 
 ## Install i18n data  (THIS MUST COME LAST)
-cp -a build/mo %{buildroot}%{_datadir}/locale
+# cp -a build/mo %{buildroot}%{_datadir}/locale
 %find_lang %{name}
 
+%post
+/sbin/ldconfig
+/bin/touch --no-create %{_datadir}/icons/hicolor &>/dev/null || :
+
+%postun
+if [ $1 -eq 0 ] ; then
+    /usr/bin/glib-compile-schemas %{_datadir}/glib-2.0/schemas &> /dev/null || :
+    /bin/touch --no-create %{_datadir}/icons/hicolor &>/dev/null
+    /usr/bin/gtk-update-icon-cache %{_datadir}/icons/hicolor &>/dev/null || :
+fi
+
+%posttrans
+/usr/bin/glib-compile-schemas %{_datadir}/glib-2.0/schemas &> /dev/null || :
+/usr/bin/gtk-update-icon-cache %{_datadir}/icons/hicolor &>/dev/null || :
  
 %files -f %{name}.lang
 %doc AUTHORS COPYING README
 %{python_sitelib}/%{name}
-%{python_sitelib}/%{name}-*.egg-info
 %{_bindir}/%{name}
 %{_bindir}/%{name}-accounts
 %{_bindir}/%{name}-poster
 %{_bindir}/%{name}-preferences
 %{_bindir}/%{name}-service
 %{_datadir}/%{name}
-%{_datadir}/pixmaps/%{name}.svg
+%{_datadir}/icons/hicolor/16x16/apps/%{name}.png
+%{_datadir}/icons/hicolor/24x24/apps/%{name}.png
+%{_datadir}/icons/hicolor/32x32/apps/%{name}.png
+%{_datadir}/icons/hicolor/48x48/apps/%{name}.png
+%{_datadir}/icons/hicolor/256x256/apps/%{name}.png
+%{_datadir}/icons/hicolor/scalable/apps/%{name}.svg
 %{_datadir}/applications/fedora-%{name}.desktop
 %{_datadir}/dbus-1/services/com.Gwibber.Accounts.service
 %{_datadir}/dbus-1/services/com.Gwibber.Connection.service
@@ -195,10 +207,46 @@ cp -a build/mo %{buildroot}%{_datadir}/locale
 %{_datadir}/dbus-1/services/com.Gwibber.Service.service
 %{_datadir}/dbus-1/services/com.Gwibber.Streams.service
 %{_datadir}/dbus-1/services/com.Gwibber.URLShorten.service
-%{_datadir}/dbus-1/services/com.GwibberClient.service
-%{_datadir}/indicators/messages/applications/gwibber
+%{_datadir}/GConf/gsettings/gwibber.convert
+%{_datadir}/glib-2.0/schemas/org.gwibber.gschema.xml
+%{_datadir}/indicators/messages/applications/gwibber.indicator
+%{_libdir}/libgwibber*.so.*
+%{_libdir}/girepository-1.0/Gwibber*
+%{_mandir}/man1/gwibber*
+
+%files devel
+%{_includedir}/libgwibber-*/
+%{_libdir}/libgwibber*.so
+%{_libdir}/pkgconfig/gwibber*.pc
+%{_datadir}/gir-1.0/Gwibber*
+%{_datadir}/vala/vapi/gwibber*
 
 %changelog
+* Wed Nov 30 2011 Tom Callaway <spot at fedoraproject.org> - 1:3.3.1.1-1
+- 3.3.1.1
+
+* Tue Aug  2 2011 Tom Callaway <spot at fedoraproject.org> - 1:3.1.5-0.1.20110802bzr1086
+- bzr1086
+
+* Tue Jul 26 2011 Tom Callaway <spot at fedoraproject.org> - 1:3.1.5-0.1.20110726bzr1079
+- bzr1079
+
+* Wed Jul 20 2011 Tom Callaway <spot at fedoraproject.org> - 1:3.1.2-1.20110720bzr1057
+- bzr1057
+
+* Mon Jul 18 2011 Tom Callaway <spot at fedoraproject.org> - 1:3.1.2-1.20110718bzr1053
+- update versioning (no epoch bump because previous builds with 3.2 tag were never released)
+- update to bzr1053
+
+* Fri Jul 15 2011 Tom Callaway <spot at fedoraproject.org> - 1:3.2-0.1.20110715bzr1042
+- 1042
+
+* Thu Jul 14 2011 Tom Callaway <spot at fedoraproject.org> - 1:3.2-0.1.20110713bzr1039
+- 1039bzr
+
+* Thu Jul 14 2011 Tom Callaway <spot at fedoraproject.org> - 1:3.2-0.1.20110713bzr1037
+- rebase to trunk code
+
 * Thu Jul  7 2011 Tom Callaway <spot at fedoraproject.org> - 1:3.1.0-2
 - really use our oauth key
 
diff --git a/sources b/sources
index 5afb9d0..f4db20b 100644
--- a/sources
+++ b/sources
@@ -1 +1 @@
-e022ad230bd7ef0c735069ba6407df0d  gwibber-3.1.0.tar.gz
+484bf327257698a267ffc6684bed253b  gwibber-3.3.1.1.tar.gz


More information about the scm-commits mailing list