1
from gwibber.microblog import network, util
2
from gwibber.microblog.util import resources
3
from gwibber.microblog.util.auth import Authentication
6
from gettext import lgettext as _
9
logger = logging.getLogger("FourSquare")
10
logger.debug("Initializing.")
19
"private:secret_token",
38
URL_PREFIX = "https://api.foursquare.com/v2"
41
"""Query Foursquare and converts data.
43
The Client class is responsible for querying Foursquare and turning the data obtained
44
into data that Gwibber can understand. Foursquare uses a version of OAuth for security.
46
Tokens have already been obtained when the account was set up in Gwibber and are used to
47
authenticate when getting data.
50
def __init__(self, acct):
51
self.service = util.getbus("Service")
55
def _retrieve_user_id(self):
56
#Make a request with our new token for the user's own data
57
url = "https://api.foursquare.com/v2/users/self?oauth_token=" + self.account["access_token"]
58
data = json.load(urllib2.urlopen(url))
60
if isinstance(data, dict):
61
if data["response"]["user"].has_key("firstName"):
62
fullname += data["response"]["user"]["firstName"] + " "
63
if data["response"]["user"].has_key("lastName"):
64
fullname += data["response"]["user"]["lastName"]
65
self.account["username"] = fullname.encode("utf-8")
66
self.account["user_id"] = data["response"]["user"]["id"]
67
self.account["uid"] = self.account["user_id"]
69
logger.error("Couldn't get user ID")
72
old_token = self.account.get("access_token", None)
73
with self.account.login_lock:
74
# Perform the login only if it wasn't already performed by another thread
75
# while we were waiting to the lock
76
if self.account.get("access_token", None) == old_token:
77
self._locked_login(old_token)
79
return "access_token" in self.account and \
80
self.account["access_token"] != old_token
82
def _locked_login(self, old_token):
83
logger.debug("Re-authenticating" if old_token else "Logging in")
85
auth = Authentication(self.account, logger)
87
if reply and reply.has_key("AccessToken"):
88
self.account["access_token"] = reply["AccessToken"]
89
self._retrieve_user_id()
90
logger.debug("User id is: %s" % self.account["uid"])
92
logger.error("Didn't find token in session: %s", (reply,))
94
def _message(self, data):
95
"""Parses messages into Gwibber compatible forms.
98
data -- A data object obtained from Foursquare containing a complete checkin
101
m -- A data object compatible with inserting into the Gwibber database for that checkin
105
m["mid"] = str(data["id"])
106
m["service"] = "foursquare"
107
m["account"] = self.account["id"]
108
m["time"] = data["createdAt"]
113
if data.has_key("shout"):
114
shout = data["shout"]
115
shout = shout.replace('& ', '& ')
117
if data.has_key("venue"):
118
venuename = data["venue"]["name"]
119
venuename = venuename.replace('& ', '& ')
121
if data.has_key("shout"):
122
shouttext += shout + "\n\n"
124
if data["venue"].has_key("id"):
125
m["url"] = "https://foursquare.com/venue/%s" % data["venue"]["id"]
127
m["url"] = "https://foursquare.com"
128
shouttext += "Checked in at <a href='" + m["url"] + "'>" + venuename + "</a>"
129
text += "Checked in at " + venuename
130
if data["venue"]["location"].has_key("address"):
131
shouttext += ", " + data["venue"]["location"]["address"]
132
text += ", " + data["venue"]["location"]["address"]
133
if data["venue"]["location"].has_key("crossstreet"):
134
shouttext += " and " + data["venue"]["location"]["crossstreet"]
135
text += " and " + data["venue"]["location"]["crossstreet"]
136
if data["venue"]["location"].has_key("city"):
137
shouttext += ", " + data["venue"]["location"]["city"]
138
text += ", " + data["venue"]["location"]["city"]
139
if data["venue"]["location"].has_key("state"):
140
shouttext += ", " + data["venue"]["location"]["state"]
141
text += ", " + data["venue"]["location"]["state"]
142
if data.has_key("event"):
143
if data["event"].has_key("name"):
144
shouttext += " for " + data["event"]["name"]
146
if data.has_key("shout"):
147
shouttext += shout + "\n\n"
150
text= "Checked in off the grid"
151
shouttext= "Checked in off the grid"
154
m["content"] = shouttext
155
m["html"] = shouttext
158
m["sender"]["id"] = data["user"]["id"]
159
m["sender"]["image"] = data["user"]["photo"]
160
m["sender"]["url"] = "https://www.foursquare.com/user/" + data["user"]["id"]
161
if data["user"]["relationship"] == "self":
162
m["sender"]["is_me"] = True
164
m["sender"]["is_me"] = False
166
if data["user"].has_key("firstName"):
167
fullname += data["user"]["firstName"] + " "
168
if data["user"].has_key("lastName"):
169
fullname += data["user"]["lastName"]
171
if data.has_key("photos"):
172
if data["photos"]["count"] > 0:
174
m["photo"]["url"] = ""
175
m["photo"]["picture"] = data["photos"]["items"][0]["url"]
176
m["photo"]["name"] = ""
179
if data.has_key("likes"):
180
if data["likes"]["count"] > 0:
181
m["likes"]["count"] = data["likes"]["count"]
183
m["sender"]["name"] = fullname
184
m["sender"]["nick"] = fullname
186
if data.has_key("source"):
187
m["source"] = "<a href='" + data["source"]["url"] + "'>" + data["source"]["name"] + "</a>"
189
m["source"] = "<a href='https://foursquare.com/'>Foursquare</a>"
191
if data.has_key("comments"):
192
if data["comments"]["count"] > 0:
195
comments = self._get_comments(data["id"])
196
for comment in comments:
197
# Get the commenter's name
199
if comment["user"].has_key("firstName"):
200
fullname += comment["user"]["firstName"] + " "
201
if comment["user"].has_key("lastName"):
202
fullname += comment["user"]["lastName"]
207
"id" : comment["user"]["id"],
209
"image": comment["user"]["photo"],
210
"url" : "https://www.foursquare.com/user/" + comment["user"]["id"]
213
m["comments"].append({
214
"text" : comment["text"],
215
"time" : comment["createdAt"],
221
def _check_error(self, data):
222
"""Checks to ensure the data obtained by Foursquare is in the correct form.
224
If it's not in the correct form, an error is logged in gwibber.log
227
data -- A data structure obtained from Foursquare
230
True if data is valid (is a dictionary and contains a 'recent' parameter).
231
If the data is not valid, then return False.
234
if isinstance(data, dict) and "recent" in data:
237
logger.error("Foursquare error %s", data)
240
def _get_comments(self, checkin_id):
241
"""Gets comments on a particular check in ID.
244
checkin_id -- The checkin id of the checkin
250
url = "/".join((URL_PREFIX, "checkins", checkin_id))
251
url = url + "?oauth_token=" + self.token
252
data = network.Download(url, None, False).get_json()["response"]
253
return data["checkin"]["comments"]["items"]
255
def _get(self, path, parse="message", post=False, single=False, **args):
256
"""Establishes a connection with Foursquare and gets the data requested.
259
path -- The end of the URL to look up on Foursquare
260
parse -- The function to use to parse the data returned (message by default)
261
post -- True if using POST, for example the send operation. False if using GET, most operations other than send. (False by default)
262
single -- True if a single checkin is requested, False if multiple (False by default)
263
**args -- Arguments to be added to the URL when accessed
266
A list of Gwibber compatible objects which have been parsed by the parse function.
269
if "access_token" not in self.account and not self._login():
270
logstr = """%s: %s - %s""" % (PROTOCOL_INFO["name"], _("Authentication failed"), "Auth needs updating")
271
logger.error("%s", logstr)
272
return [{"error": {"type": "auth", "account": self.account, "message": _("Authentication failed, please re-authorize")}}]
274
url = "/".join((URL_PREFIX, path))
276
url = url + "?oauth_token=" + self.account["access_token"]
278
data = network.Download(url, None, post).get_json()["response"]
280
resources.dump(self.account["service"], self.account["id"], data)
282
if isinstance(data, dict) and data.get("errors", 0):
283
if "authenticate" in data["errors"][0]["message"]:
284
# Try again, if we get a new token
286
logger.debug("Authentication error, logging in again")
287
return self._get(path, parse, post, single, args)
289
logstr = """%s: %s - %s""" % (PROTOCOL_INFO["name"], _("Authentication failed"), data["errors"][0]["message"])
290
logger.error("%s", logstr)
291
return [{"error": {"type": "auth", "account": self.account, "message": data["errors"][0]["message"]}}]
293
for error in data["errors"]:
294
logstr = """%s: %s - %s""" % (PROTOCOL_INFO["name"], _("Unknown failure"), error["message"])
295
return [{"error": {"type": "unknown", "account": self.account, "message": error["message"]}}]
296
elif isinstance(data, dict) and data.get("error", 0):
297
if "Incorrect signature" in data["error"]:
298
# Try again, if we get a new token
300
logger.debug("Authentication error, logging in again")
301
return self._get(path, parse, post, single, args)
303
logstr = """%s: %s - %s""" % (PROTOCOL_INFO["name"], _("Request failed"), data["error"])
304
logger.error("%s", logstr)
305
return [{"error": {"type": "auth", "account": self.account, "message": data["error"]}}]
306
elif isinstance(data, str):
307
logstr = """%s: %s - %s""" % (PROTOCOL_INFO["name"], _("Request failed"), data)
308
logger.error("%s", logstr)
309
return [{"error": {"type": "request", "account": self.account, "message": data}}]
310
if not self._check_error(data):
313
checkins = data["recent"]
314
if single: return [getattr(self, "_%s" % parse)(checkins)]
315
if parse: return [getattr(self, "_%s" % parse)(m) for m in checkins]
318
def __call__(self, opname, **args):
319
return getattr(self, opname)(**args)
322
"""Gets a list of each friend's most recent check-ins.
324
The list of each friend's recent check-ins is then
325
saved to the database.
331
A list of Gwibber compatible objects which have been parsed by the parse function.
334
return self._get("checkins/recent", v=20120612)