3
from gwibber.microblog import network, util
4
from gwibber.microblog.util import exceptions, log
5
import hashlib, mx.DateTime, time
6
from os.path import join, getmtime, exists
7
from gettext import lgettext as _
8
from gwibber.microblog.util.const import *
9
# Try to import * from custom, install custom.py to include packaging
10
# customizations like distro API keys, etc
12
from gwibber.microblog.util.custom import *
16
log.logger.name = "Facebook"
31
"authtype": "facebook",
57
URL_PREFIX = "https://api.facebook.com/restserver.php"
58
POST_URL = "http://www.facebook.com/profile.php?id=%s&v=feed&story_fbid=%s&ref=mf"
61
def __init__(self, acct):
63
self.user_id = acct["session_key"].split("-")[1]
65
def _check_error(self, data):
66
if isinstance(data, dict) and "error_code" in data:
67
log.logger.info("Facebook error %s - %s", data["error_code"], data["error_msg"])
72
def _get(self, operation, post=False, single=False, **args):
76
"method": "facebook." + operation,
77
"api_key": FB_APP_KEY,
78
"session_key": self.account["session_key"],
79
"call_id": str(int(time.time()) * 1000),
82
sig = "".join("%s=%s" % (k, v) for k, v in sorted(args.items()))
83
args["sig"] = hashlib.md5(sig + self.account["secret_key"]).hexdigest()
84
data = network.Download(URL_PREFIX, args, post).get_json()
86
if isinstance(data, dict) and data.get("error_msg", 0):
87
if "permission" in data["error_msg"]:
88
logstr = """%s: %s - %s""" % (PROTOCOL_INFO["name"], _("Authentication failed"), data["error_msg"])
89
log.logger.error("%s", logstr)
90
return [{"error": {"type": "auth", "account": self.account, "message": data["error_msg"]}}]
92
logstr = """%s: %s - %s""" % (PROTOCOL_INFO["name"], _("Unknown failure"), data["error_msg"])
93
log.logger.error("%s", logstr)
94
return [{"error": {"type": "unknown", "account": self.account, "message": data["error_msg"]}}]
97
log.logger.error("%s unexpected result - %s", PROTOCOL_INFO["name"], _("Unknown failure"))
98
return [{"error": {"type": "unknown", "account": self.account, "message": _("Unknown failure")}}]
103
def _sender(self, user):
105
"nick": user.get("username", str(user.get("id", user.get("uid", "")))),
106
"name": user["name"],
107
"id": str(user.get("id", user.get("uid", ''))),
108
"is_me": str(user.get("id", user.get("uid", ''))) == self.user_id,
109
"url": user.get("url", user.get("profile_url", "")),
110
"image": user["pic_square"],
113
if user.has_key("username"):
114
sender["nick"] = user["username"]
115
elif sender["url"] and not "?" in sender["url"]:
116
sender["nick"] = sender["url"].rsplit("/", 1)[-1]
117
elif user.has_key("id"):
118
sender["nick"] = str(user["id"])
120
sender["nick"] = str(user.get("uid", ""))
123
def _message(self, data, profiles):
125
m["mid"] = str(data["post_id"])
126
m["service"] = "facebook"
127
m["account"] = self.account["id"]
128
m["time"] = int(mx.DateTime.DateTimeFrom(int(data.get("updated_time", data["created_time"]))).gmtime())
129
m["url"] = data["permalink"]
130
m["to_me"] = ("@%s" % self.account["username"]) in data["message"]
132
if data.get("attribution", 0):
133
m["source"] = util.strip_urls(data["attribution"]).replace("via ", "")
135
if data.get("message", "").strip():
136
m["text"] = data["message"]
137
m["html"] = util.linkify(data["message"])
138
m["content"] = m["html"]
144
if data.get("actor_id", 0) in profiles:
145
m["sender"] = self._sender(profiles[data["actor_id"]])
147
# Handle target for wall posts with a specific recipient
148
if data.get("target_id", 0) in profiles:
149
m["sender"]["name"] += u" \u25b8 %s"%(profiles[data["target_id"]]['name'])
151
if data.get("likes", {}).get("count", None):
153
"count": data["likes"]["count"],
154
"url": data["likes"]["href"],
157
if data.get("comments", 0):
159
for item in data["comments"]["comment_list"]:
160
if item["fromid"] in profiles:
161
m["comments"].append({
162
"text": item["text"],
163
"time": int(mx.DateTime.DateTimeFrom(int(item["time"])).gmtime()),
164
"sender": self._sender(profiles[item["fromid"]]),
167
if data.get("attachment", 0):
168
if data["attachment"].get("name", 0):
169
m["content"] += "<p><b>%s</b></p>" % data["attachment"]["name"]
171
if data["attachment"].get("description", 0):
172
m["content"] += "<p>%s</p>" % data["attachment"]["description"]
175
for a in data["attachment"].get("media", []):
176
if a["type"] in ["photo", "video", "link"]:
178
if a["src"].startswith("/"):
179
a["src"] = "http://facebook.com" + a["src"]
180
m["images"].append({"src": a["src"], "url": a["href"]})
184
def _comment(self, data, profiles):
185
if not data.has_key("fromid"):
187
user = profiles[int(data["fromid"])]
189
"mid": str(data["id"]),
190
"service": "facebook",
191
"account": self.account["id"],
192
"time": int(mx.DateTime.DateTimeFrom(int(data['time'])).gmtime()),
193
"text": "@%s: %s" % (self.account["username"], data["text"]),
194
"content": "@%s: %s" % (self.account["username"], data["text"]),
195
"html": "@%s: %s" % (self.account["username"], data["text"]),
198
"id": data["post_id"],
199
"nick": self.account["username"],
200
"url": POST_URL % (self.user_id, data["object_id"]),
202
"sender": self._sender(user),
207
def _image(self, data, profiles):
208
user = profiles[int(data["owner"])]
210
"mid": str(data["object_id"]),
211
"service": "facebook",
212
"account": self.account["id"],
213
"time": int(mx.DateTime.DateTimeFrom(int(data['created'])).gmtime()),
214
"content": data["caption"],
215
"text": data["caption"],
216
"html": data["caption"],
218
"full": data["src_big"],
219
"src": data["src_big"],
220
"thumb": data["src_small"],
224
"nick": user["username"] or str(user["uid"]),
225
"name": user["name"],
226
"id": str(user["uid"]),
227
"url": user["profile_url"],
228
"image": user["pic_square"],
232
def _list(self, data, user):
234
"mid": data["value"],
235
"service": "facebook",
236
"account": self.account["id"],
241
"url": "#", # http://www.facebook.com/friends/?filter=flp_%s" % data["flid"],
242
"name": data["name"],
243
"nick": data["name"],
244
"key": data["filter_key"],
246
"type": data["type"],
248
"fbstreamicon": data["icon_url"],
250
"nick": user["username"] or str(user["uid"]),
251
"name": user["name"],
252
"id": str(user["uid"]),
253
"url": user["profile_url"],
254
"image": user["pic_square"],
259
friends_cache_file = join(CACHE_DIR, ("%s_friends.cache" % self.account["id"]))
260
if not exists(friends_cache_file):
261
f = file(friends_cache_file, "w")
263
f = open(friends_cache_file, "r")
265
friends = eval(f.read())
268
if (int(getmtime(friends_cache_file)) < int(mx.DateTime.DateTimeFromTicks(mx.DateTime.localtime()) - mx.DateTime.TimeDelta(hours=4.0))) \
269
or not isinstance(friends, list):
270
log.logger.debug("facebook:friends is refreshing at %s", mx.DateTime.localtime())
273
f = open(friends_cache_file, "r+")
274
friends = self._get("fql.query", query="""
275
SELECT name, profile_url, pic_square, username, uid
276
FROM user WHERE uid in (SELECT uid2 FROM friend WHERE uid1=%s)
278
f.write(str(friends))
280
log.logger.debug("<STATS> facebook:friends account:%s size:%s", self.account["id"], str(friends).__len__())
282
if not self._check_error(friends):
284
return dict((p["uid"], p) for p in friends)
286
log.logger.error("<facebook:_friends> failed to parse friend data")
291
def __call__(self, opname, **args):
292
return getattr(self, opname)(**args)
294
def thread(self, id):
295
query = "SELECT name, profile_url, pic_square, username, uid FROM user WHERE uid in \
296
(SELECT fromid FROM comment WHERE post_id = '%s')" % id
298
profiles = dict((p["uid"], p) for p in self._get("fql.query", query=query))
299
comments = self._get("stream.getComments", post_id=id)
301
return [self._comment(comment, profiles) for comment in comments]
303
log.logger.error("<facebook:thread> failed to parse comments for post_id %s", id)
306
def receive(self, since=None):
308
since = int(mx.DateTime.DateTimeFromTicks(mx.DateTime.localtime()) - mx.DateTime.TimeDelta(hours=240.0))
310
since = int(mx.DateTime.DateTimeFromTicks(since).localtime())
312
data = self._get("stream.get", viewer_id=self.user_id, start_time=since, limit=80)
314
log.logger.debug("<STATS> facebook:receive account:%s since:%s size:%s",
315
self.account["id"], mx.DateTime.DateTimeFromTicks(since), len(str(data)))
317
if not self._check_error(data):
319
profiles = dict((p["id"], p) for p in data["profiles"])
320
return [self._message(post, profiles) for post in data["posts"]]
322
log.logger.error("<facebook:receive> failed to parse message data")
326
def responses(self, since=None, limit=100):
328
since = int(mx.DateTime.DateTimeFromTicks(mx.DateTime.localtime()) - mx.DateTime.TimeDelta(hours=240.0))
330
since = int(mx.DateTime.DateTimeFromTicks(since).localtime())
332
data = self._get("fql.query", query="""
333
SELECT id, post_id, time, fromid, text, object_id FROM comment WHERE post_id IN
334
(SELECT post_id FROM stream WHERE source_id = %s) AND
337
ORDER BY time DESC LIMIT %s
338
""" % (self.user_id, self.user_id, since, limit))
340
log.logger.debug("<STATS> facebook:responses account:%s since:%s size:%s",
341
self.account["id"], mx.DateTime.DateTimeFromTicks(since), len(str(data)))
343
if not self._check_error(data):
344
profiles = self._friends()
346
return [self._comment(comment, profiles) for comment in data]
348
log.logger.error("<facebook:responses> failed to parse response data")
349
log.logger.debug("<facebook:responses> %s", data)
353
def lists(self, **args):
354
data = self._get("fql.query", query="""
355
SELECT name, profile_url, pic_square, username, uid
356
FROM user WHERE uid=%s""" % self.user_id)
357
if not self._check_error(data):
361
return [self._list(l, user) for l in self._get("stream.getFilters")]
363
def list(self, user, id, count=util.COUNT, since=None):
365
since = int(mx.DateTime.DateTimeFromTicks(mx.DateTime.localtime()) - mx.DateTime.TimeDelta(hours=240.0))
367
since = int(mx.DateTime.DateTimeFromTicks(since).localtime())
369
data = self._get("stream.get", viewer_id=self.user_id, start_time=since, limit=80, filter_key=id)
370
profiles = dict((p["id"], p) for p in data["profiles"])
371
return [self._message(post, profiles) for post in data["posts"]]
373
def images(self, limit=100, since=None):
375
since = int(mx.DateTime.DateTimeFromTicks(mx.DateTime.localtime()) - mx.DateTime.TimeDelta(hours=240.0))
377
since = int(mx.DateTime.DateTimeFromTicks(since).localtime())
379
data = self._get("fql.query", query="""
380
SELECT owner, object_id, created, src_small, src_big, link, caption
381
FROM photo WHERE aid in
382
(SELECT aid FROM album WHERE owner IN
383
(SELECT uid2 FROM friend WHERE uid1=%s))
385
ORDER BY created DESC LIMIT %s
386
""" % (self.user_id, since, limit))
388
log.logger.debug("<STATS> facebook:images account:%s since:%s size:%s",
389
self.account["id"], mx.DateTime.DateTimeFromTicks(since), len(str(data)))
391
if not self._check_error(data):
392
profiles = self._friends()
394
return [self._image(post, profiles) for post in data if int(post.get("owner", 0)) in profiles]
396
log.logger.error("<facebook:images> failed to parse image data")
397
log.logger.debug("<facebook:images> %s", data)
401
def delete(self, message):
402
result = self._get("stream.remove", post_id=message["mid"])
404
log.logger.error("<facebook:delete> failed")
408
def like(self, message):
409
result = self._get("stream.addLike", post_id=message["mid"])
411
log.logger.error("<facebook:like> failed")
415
def send(self, message):
416
result = self._get("users.setStatus", status=message, status_includes_verb=False)
418
log.logger.error("<facebook:send> failed")
422
def send_thread(self, message, target):
423
result = self._get("stream.addComment", post_id=target["mid"], comment=message)
425
log.logger.error("<facebook:send_thread> failed")