Skip to content

Commit

Permalink
Geo update
Browse files Browse the repository at this point in the history
  • Loading branch information
d60 committed May 18, 2024
1 parent b2843b7 commit 74a83e3
Show file tree
Hide file tree
Showing 9 changed files with 417 additions and 1 deletion.
8 changes: 8 additions & 0 deletions docs/twikit.rst
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,14 @@ Notification
:show-inheritance:
:member-order: bysource

Geo
-------------------

.. automodule:: twikit.geo
:members:
:undoc-members:
:show-inheritance:
:member-order: bysource

Utils
-------------------
Expand Down
9 changes: 9 additions & 0 deletions docs/twikit.twikit_async.rst
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,15 @@ Notification
:show-inheritance:
:member-order: bysource

Geo
-------------------

.. automodule:: twikit.geo
:members:
:undoc-members:
:show-inheritance:
:member-order: bysource

Utils
---------------------------------

Expand Down
3 changes: 2 additions & 1 deletion twikit/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,14 @@
A Python library for interacting with the Twitter API.
"""

__version__ = '1.6.3'
__version__ = '1.6.4'

from .bookmark import BookmarkFolder
from .client import Client
from .community import (Community, CommunityCreator, CommunityMember,
CommunityRule)
from .errors import *
from .geo import Place
from .group import Group, GroupMessage
from .list import List
from .message import Message
Expand Down
116 changes: 116 additions & 0 deletions twikit/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,13 @@
from .errors import (
CouldNotTweet,
InvalidMedia,
TweetNotAvailable,
TwitterException,
UserNotFound,
UserUnavailable,
raise_exceptions_from_response
)
from .geo import Place, _places_from_response
from .group import Group, GroupMessage
from .http import HTTPClient
from .list import List
Expand Down Expand Up @@ -1307,6 +1309,117 @@ def get_user_by_id(self, user_id: str) -> User:
raise UserUnavailable(user_data.get('message'))
return User(self, user_data)

def reverse_geocode(
self, lat: float, long: float, accuracy: str | float | None = None,
granularity: str | None = None, max_results: int | None = None
) -> list[Place]:
"""
Given a latitude and a longitude, searches for up to 20 places that
Parameters
----------
lat : :class:`float`
The latitude to search around.
long : :class:`float`
The longitude to search around.
accuracy : :class:`str` | :class:`float` None, default=None
A hint on the "region" in which to search.
granularity : :class:`str` | None, default=None
This is the minimal granularity of place types to return and must
be one of: `neighborhood`, `city`, `admin` or `country`.
max_results : :class:`int` | None, default=None
A hint as to the number of results to return.
Returns
-------
list[:class:`.Place`]
"""
params = {
'lat': lat,
'long': long,
'accuracy': accuracy,
'granularity': granularity,
'max_results': max_results
}
for k, v in tuple(params.items()):
if v is None:
params.pop(k)

response = self.http.get(
Endpoint.REVERSE_GEOCODE,
params=params,
headers=self._base_headers
).json()
return _places_from_response(self, response)

def search_geo(
self, lat: float | None = None, long: float | None = None,
query: str | None = None, ip: str | None = None,
granularity: str | None = None, max_results: int | None = None
) -> list[Place]:
"""
Search for places that can be attached to a Tweet via POST
statuses/update.
Parameters
----------
lat : :class:`float` | None
The latitude to search around.
long : :class:`float` | None
The longitude to search around.
query : :class:`str` | None
Free-form text to match against while executing a geo-based query,
best suited for finding nearby locations by name.
Remember to URL encode the query.
ip : :class:`str` | None
An IP address. Used when attempting to
fix geolocation based off of the user's IP address.
granularity : :class:`str` | None
This is the minimal granularity of place types to return and must
be one of: `neighborhood`, `city`, `admin` or `country`.
max_results : :class:`int` | None
A hint as to the number of results to return.
Returns
-------
list[:class:`.Place`]
"""
params = {
'lat': lat,
'long': long,
'query': query,
'ip': ip,
'granularity': granularity,
'max_results': max_results
}
for k, v in tuple(params.items()):
if v is None:
params.pop(k)

response = self.http.get(
Endpoint.SEARCH_GEO,
params=params,
headers=self._base_headers
).json()
return _places_from_response(self, response)

def get_place(self, id: str) -> Place:
"""
Parameters
----------
id : :class:`str`
The ID of the place.
Returns
-------
:class:`.Place`
"""
response = self.http.get(
Endpoint.PLACE_BY_ID.format(id),
headers=self._base_headers
).json()
return Place(self, response)

def _get_tweet_detail(self, tweet_id: str, cursor: str | None):
variables = {
'focalTweetId': tweet_id,
Expand Down Expand Up @@ -1395,6 +1508,9 @@ def get_tweet_by_id(
"""
response = self._get_tweet_detail(tweet_id, cursor)

if 'errors' in response:
raise TweetNotAvailable(response['errors'][0]['message'])

entries = find_dict(response, 'entries')[0]
reply_to = []
replies_list = []
Expand Down
81 changes: 81 additions & 0 deletions twikit/geo.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
from __future__ import annotations

import warnings
from typing import TYPE_CHECKING
from .errors import TwitterException

if TYPE_CHECKING:
from .client import Client


class Place:
"""
Attributes
----------
id : :class:`str`
The ID of the place.
name : :class:`str`
The name of the place.
full_name : :class:`str`
The full name of the place.
country : :class:`str`
The country where the place is located.
country_code : :class:`str`
The ISO 3166-1 alpha-2 country code of the place.
url : :class:`str`
The URL providing more information about the place.
place_type : :class:`str`
The type of place.
attributes : :class:`dict`
bounding_box : :class:`dict`
The bounding box that defines the geographical area of the place.
centroid : list[:class:`float`]
The geographical center of the place, represented by latitude and
longitude.
contained_within : list[:class:`.Place`]
A list of places that contain this place.
"""

def __init__(self, client: Client, data: dict) -> None:
self._client = client

self.id: str = data['id']
self.name: str = data['name']
self.full_name: str = data['full_name']
self.country: str = data['country']
self.country_code: str = data['country_code']
self.url: str = data['url']
self.place_type: str = data['place_type']
self.attributes: dict = data['attributes']
self.bounding_box: dict = data['bounding_box']
self.centroid: list[float] = data['centroid']

self.contained_within: list[Place] = [
Place(client, place) for place in data.get('contained_within', [])
]

def update(self) -> None:
new = self._client.get_place(self.id)
self.__dict__.update(new.__dict__)

def __repr__(self) -> str:
return f'<Place id="{self.id}" name="{self.name}">'

def __eq__(self, __value: object) -> bool:
return isinstance(__value, Place) and self.id == __value.id

def __ne__(self, __value: object) -> bool:
return not self == __value


def _places_from_response(client: Client, response: dict) -> list[Place]:
if 'errors' in response:
e = response['errors'][0]
# No data available for the given coordinate.
if e['code'] == 6:
warnings.warn(e['message'])
else:
raise TwitterException(e['message'])

places = response['result']['places'] if 'result' in response else []
return [Place(client, place) for place in places]
1 change: 1 addition & 0 deletions twikit/twikit_async/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from .client import Client
from .community import (Community, CommunityCreator, CommunityMember,
CommunityRule)
from .geo import Place
from .group import Group, GroupMessage
from .list import List
from .message import Message
Expand Down
Loading

0 comments on commit 74a83e3

Please sign in to comment.