Sophie

Sophie

distrib > Fedora > 13 > i386 > by-pkgid > 4fe0eac35e20eb2d09b83f78a19ab835 > files > 14

gwibber-3.0.0.1-2.fc13.src.rpm

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/*.*")),