Source code for platypush.plugins.foursquare

import datetime
from typing import List, Dict, Any, Optional

import requests

from platypush.context import Variable
from platypush.message.event.foursquare import FoursquareCheckinEvent
from platypush.plugins import RunnablePlugin, action


[docs] class FoursquarePlugin(RunnablePlugin): """ Plugin to interact with the `Foursquare Places API <https://developer.foursquare.com/docs/api>`_. It also raises events when a new check-in occurs on the user's account. 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' _last_created_at_varname = '_foursquare_checkin_last_created_at' _http_timeout = 10 # API version to use, see https://docs.foursquare.com/developer/reference/versioning _api_version = '20240101'
[docs] def __init__(self, access_token: str, poll_interval: float = 120, **kwargs): """ :param poll_interval: Poll interval in seconds to check for new check-ins (default: 120). Set to 0 or ``None`` to disable polling of new check-ins. :param access_token: The access token to use to authenticate to the Foursquare API. """ super().__init__(poll_interval=poll_interval, **kwargs) self.access_token = access_token self._last_created_at = Variable(self._last_created_at_varname)
def _get_url(self, endpoint): return f'{self.api_base_url}/{endpoint}?oauth_token={self.access_token}&v={self._api_version}' def _get_checkins(self, limit: int, offset: int) -> List[Dict[str, Any]]: url = self._get_url('users/self/checkins') return ( requests.get( url, params={'limit': limit, 'offset': offset}, timeout=self._http_timeout, ) .json() .get('response', {}) .get('checkins', {}) .get('items', []) )
[docs] @action def get_checkins(self, limit: int = 20, offset: int = 0) -> List[Dict[str, Any]]: """ Get the list of check-ins of the current user. :param limit: Maximum number of check-ins to retrieve (default: 20). :param offset: Offset to start retrieving check-ins from (default: 0). :return: A list of checkins, as returned by the Foursquare API. """ return self._get_checkins(limit=limit, offset=offset)
[docs] @action def search( self, latitude: Optional[float] = None, longitude: Optional[float] = None, near: Optional[str] = None, query: Optional[str] = None, limit: Optional[int] = None, url: Optional[str] = None, categories: Optional[List[str]] = None, radius: Optional[int] = 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 query: Search query (e.g. "coffee shops" or "restaurants"). :param limit: Maximum number of results. :param url: A 3rd-party URL associated to the venue to be searched. :param categories: List of `category IDs <https://developer.foursquare.com/docs/resources/categories>`_ to be searched. :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 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 return ( requests.get( self._get_url('venues/search'), params=args, timeout=self._http_timeout ) .json() .get('response', {}) .get('venues', []) )
[docs] @action def explore( self, latitude: Optional[float] = None, longitude: Optional[float] = None, near: Optional[str] = None, query: Optional[str] = None, limit: Optional[int] = None, categories: Optional[List[str]] = None, radius: Optional[int] = 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 query: Search query (e.g. "coffee shops" or "restaurants"). :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. :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 query: args['query'] = query if categories: args['categoryId'] = ','.join(categories) if limit: args['limit'] = int(limit) if radius: args['radius'] = radius url = self._get_url('venues/search') return ( requests.get(url, params=args, timeout=self._http_timeout) .json() .get('response', {}) .get('venues', []) )
[docs] @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, timeout=self._http_timeout) .json() .get('response', {}) .get('venues', []) )
@staticmethod def _parse_time(t): if isinstance(t, (int, float)): return datetime.datetime.fromtimestamp(t) if isinstance(t, str): return datetime.datetime.fromisoformat(t) assert isinstance( t, datetime.datetime ), f'Cannot parse object of type {type(t)} into datetime: {t}' return t
[docs] @action def checkin( self, venue_id: str, shout: Optional[str] = None, ) -> Dict[str, Any]: """ Create a new check-in. :param venue_id: ID of the venue to check-in. :param shout: Add a custom message to the check-in. :return: Foursquare API response. """ args = {'venueId': venue_id} if shout: args['shout'] = shout url = self._get_url('checkins/add') return ( requests.post(url, data=args, timeout=self._http_timeout) .json() .get('response', {}) .get('checkin', {}) )
def main(self): if not self.poll_interval: # If no poll interval is set then we won't poll for new check-ins self.wait_stop() return while not self.should_stop(): try: checkins = self._get_checkins(limit=20, offset=0) if not checkins: continue last_checkin = checkins[0] last_checkin_created_at = last_checkin.get('createdAt', 0) last_created_at = float(self._last_created_at.get() or 0) if last_created_at and last_checkin_created_at <= last_created_at: continue self._bus.post(FoursquareCheckinEvent(checkin=last_checkin)) self._last_created_at.set(last_checkin_created_at) finally: self.wait_stop(self.poll_interval)
# vim:sw=4:ts=4:et: