1
from gwibber.microblog import network, util
2
from htmlentitydefs import name2codepoint
5
from oauth import oauth
6
from gwibber.microblog.util import log, resources
7
from gettext import lgettext as _
9
log.logger.name = "Sina"
16
"private:secret_token",
24
"authtype": "oauth1a",
56
URL_PREFIX = "http://t.sina.com.cn"
57
API_PREFIX = "http://api.t.sina.com.cn"
61
return re.sub('&(%s);' % '|'.join(name2codepoint),
62
lambda m: unichr(name2codepoint[m.group(1)]), s)
65
def __init__(self, acct):
66
self.service = util.getbus("Service")
67
if acct.has_key("secret_token") and acct.has_key("password"): acct.pop("password")
70
if not acct.has_key("access_token") and not acct.has_key("secret_token"):
71
return [{"error": {"type": "auth", "account": self.account, "message": _("Failed to find credentials")}}]
73
self.sigmethod = oauth.OAuthSignatureMethod_HMAC_SHA1()
74
self.consumer = oauth.OAuthConsumer(*sina.utils.get_sina_keys())
75
self.token = oauth.OAuthToken(acct["access_token"], acct["secret_token"])
77
def _common(self, data):
80
m["mid"] = str(data["id"])
82
m["account"] = self.account["id"]
83
m["time"] = util.parsetime(data["created_at"])
84
m["text"] = unescape(data["text"])
85
m["to_me"] = ("@%s" % self.account["username"]) in data["text"]
87
m["html"] = util.linkify(data["text"],
88
((util.PARSE_HASH, '#<a class="hash" href="%s#search?q=\\1">\\1</a>' % URL_PREFIX),
89
(util.PARSE_NICK, '@<a class="nick" href="%s/\\1">\\1</a>' % URL_PREFIX)), escape=False)
91
m["content"] = util.linkify(data["text"],
92
((util.PARSE_HASH, '#<a class="hash" href="gwibber:/tag?acct=%s&query=\\1">\\1</a>' % m["account"]),
93
(util.PARSE_NICK, '@<a class="nick" href="gwibber:/user?acct=%s&name=\\1">\\1</a>' % m["account"])), escape=False)
95
if data.has_key("retweeted_status"):
96
m["retweeted_status"] = data["retweeted_status"]
98
m["retweeted_status"] = None
101
if data.get("original_pic", 0):
102
images.append({"src": data["thumbnail_pic"], "url": data["original_pic"]})
103
if data.get("retweeted_status", 0):
104
if data["retweeted_status"].get("original_pic"):
105
images.append({"src": data["retweeted_status"]["thumbnail_pic"], "url": data["retweeted_status"]["original_pic"]})
110
log.logger.error("%s failure - %s", PROTOCOL_INFO["name"], data)
115
def _user(self, user):
117
"name": user["name"],
118
"nick": user["screen_name"],
120
"location": user["location"],
121
"followers": user.get("followers", None),
122
"image": user["profile_image_url"],
123
"url": "/".join((API_PREFIX, str(user["id"]))),
124
"is_me": user["screen_name"] == self.account["username"],
127
def _message(self, data):
128
if type(data) == type(None):
131
m = self._common(data)
132
m["source"] = data.get("source", False)
134
if data.has_key("in_reply_to_status_id"):
135
if data["in_reply_to_status_id"]:
137
m["reply"]["id"] = data["in_reply_to_status_id"]
138
m["reply"]["nick"] = data["in_reply_to_screen_name"]
139
if m["reply"]["id"] and m["reply"]["nick"]:
140
m["reply"]["url"] = "/".join((URL_PREFIX, m["reply"]["nick"], "statuses", str(m["reply"]["id"])))
142
m["reply"]["url"] = None
144
m["sender"] = self._user(data["user"] if "user" in data else data["sender"])
145
m["url"] = "/".join((m["sender"]["url"], "statuses", str(m["mid"])))
149
def _private(self, data):
150
m = self._message(data)
154
m["recipient"]["name"] = data["recipient"]["name"]
155
m["recipient"]["nick"] = data["recipient"]["screen_name"]
156
m["recipient"]["id"] = data["recipient"]["id"]
157
m["recipient"]["image"] = data["recipient"]["profile_image_url"]
158
m["recipient"]["location"] = data["recipient"]["location"]
159
m["recipient"]["url"] = "/".join((URL_PREFIX, m["recipient"]["nick"]))
160
m["recipient"]["is_me"] = m["recipient"]["nick"] == self.account["username"]
161
m["to_me"] = m["recipient"]["is_me"]
165
def _result(self, data):
166
m = self._common(data)
168
if data["to_user_id"]:
170
m["reply"]["id"] = data["to_user_id"]
171
m["reply"]["nick"] = data["to_user"]
174
m["sender"]["nick"] = data["from_user"]
175
m["sender"]["id"] = data["from_user_id"]
176
m["sender"]["image"] = data["profile_image_url"]
177
m["sender"]["url"] = "/".join((URL_PREFIX, m["sender"]["nick"]))
178
m["sender"]["is_me"] = m["sender"]["nick"] == self.account["username"]
179
m["url"] = "/".join((m["sender"]["url"], "statuses", str(m["mid"])))
182
def _list(self, data):
186
"account": self.account["id"],
188
"text": data["description"],
189
"html": data["description"],
190
"content": data["description"],
191
"url": "/".join((URL_PREFIX, data["uri"])),
192
"sender": self._user(data["user"]),
193
"name": data["name"],
194
"nick": data["slug"],
196
"full": data["full_name"],
198
"mode": data["mode"],
199
"members": data["member_count"],
200
"followers": data["subscriber_count"],
204
def _get(self, path, parse="message", post=False, single=False, **args):
205
url = "/".join((API_PREFIX, path))
207
request = oauth.OAuthRequest.from_consumer_and_token(self.consumer, self.token,
208
http_method="POST" if post else "GET", http_url=url, parameters=util.compact(args))
209
request.sign_request(self.sigmethod, self.consumer, self.token)
212
data = network.Download(request.http_url, None, post, body=request.to_postdata()).get_json()
213
#data = network.Download(request.to_url(), util.compact(args), post).get_json()
215
data = network.Download(request.to_url(), None, post).get_json()
217
resources.dump(self.account["service"], self.account["id"], data)
219
if isinstance(data, dict) and data.get("errors", 0):
220
if "authenticate" in data["errors"][0]["message"]:
221
logstr = """%s: %s - %s""" % (PROTOCOL_INFO["name"], _("Authentication failed"), error["message"])
222
log.logger.error("%s", logstr)
223
return [{"error": {"type": "auth", "account": self.account, "message": data["errors"][0]["message"]}}]
225
for error in data["errors"]:
226
logstr = """%s: %s - %s""" % (PROTOCOL_INFO["name"], _("Unknown failure"), error["message"])
227
return [{"error": {"type": "unknown", "account": self.account, "message": error["message"]}}]
228
elif isinstance(data, dict) and data.get("error", 0):
229
if "Incorrect signature" in data["error"]:
230
logstr = """%s: %s - %s""" % (PROTOCOL_INFO["name"], _("Request failed"), data["error"])
231
log.logger.error("%s", logstr)
232
return [{"error": {"type": "auth", "account": self.account, "message": data["error"]}}]
233
elif isinstance(data, str):
234
logstr = """%s: %s - %s""" % (PROTOCOL_INFO["name"], _("Request failed"), data)
235
log.logger.error("%s", logstr)
236
return [{"error": {"type": "request", "account": self.account, "message": data}}]
239
return [self._list(l) for l in data["lists"]]
240
if single: return [getattr(self, "_%s" % parse)(data)]
241
if parse: return [getattr(self, "_%s" % parse)(m) for m in data]
244
def _search(self, **args):
245
data = network.Download("http://api.t.sina.com.cn/search.json", util.compact(args))
246
data = data.get_json()["results"]
248
return [self._result(m) for m in data]
250
def __call__(self, opname, **args):
251
return getattr(self, opname)(**args)
253
def receive(self, count=util.COUNT, since=None):
254
return self._get("statuses/home_timeline.json", count=count, since_id=since)
256
def user_messages(self, id=None, count=util.COUNT, since=None):
257
return self._get("statuses/user_timeline.json", id=id, count=count, since_id=since)
259
def responses(self, count=util.COUNT, since=None):
260
return self._get("statuses/mentions.json", count=count, since_id=since)
262
def private(self, count=util.COUNT, since=None):
263
private = self._get("direct_messages.json", "private", count=count, since_id=since) or []
264
private_sent = self._get("direct_messages/sent.json", "private", count=count, since_id=since) or []
265
return private + private_sent
268
return self._get("statuses/public_timeline.json")
270
def lists(self, **args):
271
following = self._get("%s/lists/subscriptions.json" % self.account["username"], "list") or []
272
lists = self._get("%s/lists.json" % self.account["username"], "list") or []
273
return following + lists
275
def list(self, user, id, count=util.COUNT, since=None):
276
return self._get("%s/lists/%s/statuses.json" % (user, id), per_page=count, since_id=since)
278
def search(self, query, count=util.COUNT, since=None):
279
return self._search(q=query, rpp=count, since_id=since)
281
def tag(self, query, count=util.COUNT, since=None):
282
return self._search(q="#%s" % query, count=count, since_id=since)
284
def delete(self, message):
285
return self._get("statuses/destroy/%s.json" % message["mid"], None, post=True, do=1)
287
def like(self, message):
288
return self._get("favorites/create/%s.json" % message["mid"], None, post=True, do=1)
290
def send(self, message):
291
return self._get("statuses/update.json", post=True, single=True,
294
def send_private(self, message, private):
295
return self._get("direct_messages/new.json", "private", post=True, single=True,
296
text=message, screen_name=private["sender"]["nick"])
298
def send_thread(self, message, target):
299
return self._get("statuses/update.json", post=True, single=True,
300
status=message, in_reply_to_status_id=target["mid"])