forked from platypush/platypush
Added Foursquare integration - closes #94
This commit is contained in:
parent
ddbe76646b
commit
b0a5f1e272
3 changed files with 365 additions and 0 deletions
5
docs/source/platypush/plugins/foursquare.rst
Normal file
5
docs/source/platypush/plugins/foursquare.rst
Normal file
|
@ -0,0 +1,5 @@
|
|||
``platypush.plugins.foursquare``
|
||||
================================
|
||||
|
||||
.. automodule:: platypush.plugins.foursquare
|
||||
:members:
|
|
@ -26,6 +26,7 @@ Plugins
|
|||
platypush/plugins/db.rst
|
||||
platypush/plugins/dropbox.rst
|
||||
platypush/plugins/file.rst
|
||||
platypush/plugins/foursquare.rst
|
||||
platypush/plugins/google.rst
|
||||
platypush/plugins/google.calendar.rst
|
||||
platypush/plugins/google.drive.rst
|
||||
|
|
359
platypush/plugins/foursquare.py
Normal file
359
platypush/plugins/foursquare.py
Normal file
|
@ -0,0 +1,359 @@
|
|||
import datetime
|
||||
import requests
|
||||
from typing import List, Dict, Any, Optional, Union, Tuple
|
||||
|
||||
from platypush.plugins import Plugin, action
|
||||
|
||||
|
||||
class FoursquarePlugin(Plugin):
|
||||
"""
|
||||
Plugin to interact with the `Foursquare Places API <https://developer.foursquare.com/docs/api>`_.
|
||||
|
||||
In order to enable the Foursquare API on your account you need to:
|
||||
|
||||
- Create a new app on the `Foursquare developers website <https://foursquare.com/developers/signup>`_.
|
||||
- Copy the ``client_id`` and ``client_secret``.
|
||||
- Add a redirect URL. It must point to a valid IP/hostname with a web server running, even if it runs
|
||||
locally. You can also use the local URL of the platypush web server - e.g. http://192.168.1.2:8008/.
|
||||
- Open the following URL: ``https://foursquare.com/oauth2/authenticate?client_id=CLIENT_ID&response_type=token&redirect_uri=REDIRECT_URI``.
|
||||
Replace ``CLIENT_ID`` and ``REDIRECT_URI`` with the parameters from your app.
|
||||
- Allow the application. You will be redirected to the URL you provided. Copy the ``access_token`` provided in
|
||||
the URL.
|
||||
|
||||
"""
|
||||
|
||||
api_base_url = 'https://api.foursquare.com/v2'
|
||||
|
||||
def __init__(self, access_token: str, **kwargs):
|
||||
"""
|
||||
:param access_token:
|
||||
"""
|
||||
super().__init__(**kwargs)
|
||||
self.access_token = access_token
|
||||
|
||||
def _get_url(self, endpoint):
|
||||
return '{url}/{endpoint}?oauth_token={token}&v={version}'.format(
|
||||
url=self.api_base_url, endpoint=endpoint, token=self.access_token,
|
||||
version=datetime.date.today().strftime('%Y%m%d'),
|
||||
)
|
||||
|
||||
@action
|
||||
def get_checkins(self) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
Get the list of check-ins of the current user.
|
||||
:return: A list of checkins, as returned by the Foursquare API.
|
||||
"""
|
||||
url = self._get_url('users/self/checkins')
|
||||
return requests.get(url).json().get('response', {}).get('checkins', {}).get('items', [])
|
||||
|
||||
# noinspection DuplicatedCode
|
||||
@action
|
||||
def search(self,
|
||||
latitude: Optional[float] = None,
|
||||
longitude: Optional[float] = None,
|
||||
altitude: Optional[float] = None,
|
||||
latlng_accuracy: Optional[float] = None,
|
||||
altitude_accuracy: Optional[float] = None,
|
||||
near: Optional[str] = None,
|
||||
query: Optional[str] = None,
|
||||
limit: Optional[int] = None,
|
||||
url: Optional[int] = None,
|
||||
categories: Optional[List[str]] = None,
|
||||
radius: Optional[int] = None,
|
||||
sw: Optional[Union[Tuple[float], List[float]]] = None,
|
||||
ne: Optional[Union[Tuple[float], List[float]]] = None,) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
Search for venues.
|
||||
|
||||
:param latitude: Search near this latitude. Note either ``latitude, longitude`` or ``near`` should be provided.
|
||||
:param longitude: Search near this latitude. Note either ``latitude, longitude`` or ``near`` should be provided.
|
||||
:param near: Search near this place (e.g. "Chicago, IL" or "Amsterdam, NL"). Note either
|
||||
``latitude, longitude`` or ``near`` should be provided.
|
||||
:param altitude: Search near this altitude in meters.
|
||||
:param latlng_accuracy: Latitude/longitude accuracy in meters.
|
||||
:param altitude_accuracy: Altitude accuracy in meters.
|
||||
:param query: Search query (e.g. "coffee shops" or "restaurants").
|
||||
:param limit: Maximum number of results.
|
||||
:param url: Venue URL to search.
|
||||
:param categories: List of ``category IDs <https://developer.foursquare.com/docs/resources/categories>``_
|
||||
to be searched.
|
||||
:param radius: Search radius in meters.
|
||||
:param sw: South/west boundary box as a ``[latitude, longitude]`` pair.
|
||||
:param ne: North/east boundary box as a ``[latitude, longitude]`` pair.
|
||||
:return: A list of venues, as returned by the Foursquare API.
|
||||
"""
|
||||
assert (latitude and longitude) or near, 'Specify either latitude/longitude or near'
|
||||
args = {}
|
||||
|
||||
if latitude and longitude:
|
||||
args['ll'] = ','.join([str(latitude), str(longitude)])
|
||||
if near:
|
||||
args['near'] = near
|
||||
if altitude:
|
||||
args['alt'] = altitude
|
||||
if latlng_accuracy:
|
||||
args['llAcc'] = latlng_accuracy
|
||||
if altitude_accuracy:
|
||||
args['altAcc'] = altitude_accuracy
|
||||
if query:
|
||||
args['query'] = query
|
||||
if limit:
|
||||
args['limit'] = int(limit)
|
||||
if url:
|
||||
args['url'] = url
|
||||
if categories:
|
||||
args['categoryId'] = ','.join(categories)
|
||||
if radius:
|
||||
args['radius'] = radius
|
||||
if sw:
|
||||
args['sw'] = sw
|
||||
if ne:
|
||||
args['ne'] = ne
|
||||
|
||||
url = self._get_url('venues/search')
|
||||
return requests.get(url, params=args).json().get('response', {}).get('venues', [])
|
||||
|
||||
# noinspection DuplicatedCode
|
||||
@action
|
||||
def explore(self,
|
||||
latitude: Optional[float] = None,
|
||||
longitude: Optional[float] = None,
|
||||
altitude: Optional[float] = None,
|
||||
latlng_accuracy: Optional[float] = None,
|
||||
altitude_accuracy: Optional[float] = None,
|
||||
section: Optional[str] = None,
|
||||
near: Optional[str] = None,
|
||||
query: Optional[str] = None,
|
||||
limit: Optional[int] = None,
|
||||
categories: Optional[List[str]] = None,
|
||||
radius: Optional[int] = None,
|
||||
open_now: bool = True,
|
||||
sort_by_distance: Optional[bool] = None,
|
||||
sort_by_popularity: Optional[bool] = None,
|
||||
price: Optional[List[int]] = None,
|
||||
saved: Optional[bool] = None) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
Explore venues around a location.
|
||||
|
||||
:param latitude: Search near this latitude. Note either ``latitude, longitude`` or ``near`` should be provided.
|
||||
:param longitude: Search near this latitude. Note either ``latitude, longitude`` or ``near`` should be provided.
|
||||
:param near: Search near this place (e.g. "Chicago, IL" or "Amsterdam, NL"). Note either
|
||||
``latitude, longitude`` or ``near`` should be provided.
|
||||
:param altitude: Search near this altitude in meters.
|
||||
:param latlng_accuracy: Latitude/longitude accuracy in meters.
|
||||
:param altitude_accuracy: Altitude accuracy in meters.
|
||||
:param section: Section to search. Supported values:
|
||||
|
||||
- food
|
||||
- drinks
|
||||
- coffee
|
||||
- shops
|
||||
- arts
|
||||
- outdoors
|
||||
- sights
|
||||
- trending
|
||||
- nextVenues
|
||||
|
||||
:param query: Search query (e.g. "coffee shops" or "restaurants"). The parameter has no effect if
|
||||
``section`` is specified.
|
||||
:param limit: Maximum number of results.
|
||||
:param categories: List of ``category IDs <https://developer.foursquare.com/docs/resources/categories>``_
|
||||
to be searched.
|
||||
:param radius: Search radius in meters.
|
||||
:param open_now: Filter by open/not open now.
|
||||
:param sort_by_distance: Sort by distance.
|
||||
:param sort_by_popularity: Sort by popularity
|
||||
:param price: Price ranges, within the range ``[1,2,3,4]``.
|
||||
:param saved: Filter by saved/unsaved venues.
|
||||
|
||||
:return: A list of venues, as returned by the Foursquare API.
|
||||
"""
|
||||
assert (latitude and longitude) or near, 'Specify either latitude/longitude or near'
|
||||
args = {}
|
||||
|
||||
if latitude and longitude:
|
||||
args['ll'] = ','.join([str(latitude), str(longitude)])
|
||||
if near:
|
||||
args['near'] = near
|
||||
if altitude:
|
||||
args['alt'] = altitude
|
||||
if latlng_accuracy:
|
||||
args['llAcc'] = latlng_accuracy
|
||||
if altitude_accuracy:
|
||||
args['altAcc'] = altitude_accuracy
|
||||
if section:
|
||||
args['section'] = section
|
||||
if query:
|
||||
args['query'] = query
|
||||
if categories:
|
||||
args['categoryId'] = ','.join(categories)
|
||||
if limit:
|
||||
args['limit'] = int(limit)
|
||||
if radius:
|
||||
args['radius'] = radius
|
||||
if open_now is not None:
|
||||
args['openNow'] = int(open_now)
|
||||
if sort_by_distance is not None:
|
||||
args['sortByDistance'] = int(sort_by_distance)
|
||||
if sort_by_popularity is not None:
|
||||
args['sortByPopularity'] = sort_by_popularity
|
||||
if saved is not None:
|
||||
args['saved'] = int(saved)
|
||||
if price:
|
||||
args['price'] = ','.join([str(p) for p in price])
|
||||
|
||||
url = self._get_url('venues/explore')
|
||||
return requests.get(url, params=args).json().get('response', {}).get('venues', [])
|
||||
|
||||
@action
|
||||
def trending(self,
|
||||
latitude: Optional[float] = None,
|
||||
longitude: Optional[float] = None,
|
||||
near: Optional[str] = None,
|
||||
limit: Optional[int] = None,
|
||||
radius: Optional[int] = None) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
Get the trending venues around a location.
|
||||
|
||||
:param latitude: Search near this latitude. Note either ``latitude, longitude`` or ``near`` should be provided.
|
||||
:param longitude: Search near this latitude. Note either ``latitude, longitude`` or ``near`` should be provided.
|
||||
:param near: Search near this place (e.g. "Chicago, IL" or "Amsterdam, NL"). Note either
|
||||
``latitude, longitude`` or ``near`` should be provided.
|
||||
:param limit: Maximum number of results.
|
||||
:param radius: Search radius in meters.
|
||||
|
||||
:return: A list of venues, as returned by the Foursquare API.
|
||||
"""
|
||||
assert (latitude and longitude) or near, 'Specify either latitude/longitude or near'
|
||||
args = {}
|
||||
|
||||
if latitude and longitude:
|
||||
args['ll'] = ','.join([str(latitude), str(longitude)])
|
||||
if near:
|
||||
args['near'] = near
|
||||
if limit:
|
||||
args['limit'] = int(limit)
|
||||
if radius:
|
||||
args['radius'] = radius
|
||||
|
||||
url = self._get_url('venues/trending')
|
||||
return requests.get(url, params=args).json().get('response', {}).get('venues', [])
|
||||
|
||||
@staticmethod
|
||||
def _parse_time(t):
|
||||
if isinstance(t, int) or isinstance(t, float):
|
||||
return datetime.datetime.fromtimestamp(t)
|
||||
if isinstance(t, str):
|
||||
return datetime.datetime.fromisoformat(t)
|
||||
|
||||
assert isinstance(t, datetime.datetime), 'Cannot parse object of type {} into datetime: {}'.format(
|
||||
type(t), t)
|
||||
return t
|
||||
|
||||
@action
|
||||
def time_series(self,
|
||||
venue_id: Union[str, List[str]],
|
||||
start_at: Union[int, float, datetime.datetime, str],
|
||||
end_at: Union[int, float, datetime.datetime, str]) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
Get the visitors stats about one or multiple venues over a time range. The user must be a manager of
|
||||
those venues.
|
||||
|
||||
:param venue_id: Venue ID or list of IDs to get the stats for.
|
||||
:param start_at: Stats start time. Can be a UNIX timestamp, a datetime object or an ISO format datetime.
|
||||
:param end_at: Stats end time. Can be a UNIX timestamp, a datetime object or an ISO format datetime.
|
||||
|
||||
:return: A list of venues, as returned by the Foursquare API.
|
||||
"""
|
||||
if isinstance(venue_id, list):
|
||||
venue_id = ','.join(venue_id)
|
||||
|
||||
args = {
|
||||
'venueId': venue_id,
|
||||
'startAt': self._parse_time(start_at),
|
||||
'endAt': self._parse_time(end_at),
|
||||
}
|
||||
|
||||
url = self._get_url('venues/timeseries')
|
||||
return requests.get(url, params=args).json().get('response', {}).get('venues', [])
|
||||
|
||||
@action
|
||||
def stats(self,
|
||||
venue_id: str,
|
||||
start_at: Union[int, float, datetime.datetime, str],
|
||||
end_at: Union[int, float, datetime.datetime, str]) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
Get the stats about a venue over a time range. The user must be a manager of that venue.
|
||||
|
||||
:param venue_id: Venue ID.
|
||||
:param start_at: Stats start time. Can be a UNIX timestamp, a datetime object or an ISO format datetime.
|
||||
:param end_at: Stats end time. Can be a UNIX timestamp, a datetime object or an ISO format datetime.
|
||||
|
||||
:return: A list of venues, as returned by the Foursquare API.
|
||||
"""
|
||||
args = {
|
||||
'startAt': self._parse_time(start_at),
|
||||
'endAt': self._parse_time(end_at),
|
||||
}
|
||||
|
||||
url = self._get_url('venues/{}/stats'.format(venue_id))
|
||||
return requests.get(url, params=args).json().get('response', {}).get('venues', [])
|
||||
|
||||
@action
|
||||
def managed(self) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
Get the list of venues managed by the user.
|
||||
:return: A list of venues, as returned by the Foursquare API.
|
||||
"""
|
||||
url = self._get_url('venues/managed')
|
||||
return requests.get(url).json().get('response', {}).get('venues', []).get('items', [])
|
||||
|
||||
@action
|
||||
def checkin(self,
|
||||
venue_id: str,
|
||||
latitude: Optional[float] = None,
|
||||
longitude: Optional[float] = None,
|
||||
altitude: Optional[float] = None,
|
||||
latlng_accuracy: Optional[float] = None,
|
||||
altitude_accuracy: Optional[float] = None,
|
||||
shout: Optional[str] = None,
|
||||
broadcast: Optional[List[str]] = None) -> Dict[str, Any]:
|
||||
"""
|
||||
Create a new check-in.
|
||||
|
||||
:param venue_id: ID of the venue to check-in.
|
||||
:param latitude: Check-in latitude.
|
||||
:param longitude: Check-in longitude.
|
||||
:param altitude: Check-in altitude.
|
||||
:param latlng_accuracy: Latitude/longitude accuracy in meters.
|
||||
:param altitude_accuracy: Altitude accuracy in meters.
|
||||
:param shout: Add a custom message to the check-in.
|
||||
:param broadcast: List of Visibility/share types of the check-in. Default: ``public``. Possible values are:
|
||||
|
||||
- ``private``
|
||||
- ``public``
|
||||
- ``followers``
|
||||
- ``facebook``
|
||||
- ``twitter``
|
||||
|
||||
:return: Foursquare API response.
|
||||
"""
|
||||
args = {'venueId': venue_id}
|
||||
if latitude and longitude:
|
||||
args['ll'] = ','.join([str(latitude), str(longitude)])
|
||||
if altitude:
|
||||
args['alt'] = altitude
|
||||
if latlng_accuracy:
|
||||
args['llAcc'] = latlng_accuracy
|
||||
if altitude_accuracy:
|
||||
args['altAcc'] = altitude_accuracy
|
||||
if shout:
|
||||
args['shout'] = shout
|
||||
if broadcast:
|
||||
args['broadcast'] = ','.join(broadcast) if isinstance(broadcast, list) else broadcast
|
||||
|
||||
url = self._get_url('checkins/add')
|
||||
return requests.post(url, data=args).json().get('response', {}).get('checkin', {})
|
||||
|
||||
|
||||
# vim:sw=4:ts=4:et:
|
Loading…
Reference in a new issue