twscrape/twapi/models.py

191 строка
5.5 KiB
Python

import email.utils
from dataclasses import asdict, dataclass
from datetime import datetime
from typing import Optional
from .utils import get_or, int_or_none
@dataclass
class JSONTrait:
def json(self):
return asdict(self)
@dataclass
class Coordinates(JSONTrait):
longitude: float
latitude: float
@staticmethod
def parse(tw_obj: dict):
if tw_obj.get("coordinates"):
coords = tw_obj["coordinates"]["coordinates"]
return Coordinates(coords[0], coords[1])
if tw_obj.get("geo"):
coords = tw_obj["geo"]["coordinates"]
return Coordinates(coords[1], coords[0])
return None
@dataclass
class Place(JSONTrait):
id: str
fullName: str
name: str
type: str
country: str
countryCode: str
@staticmethod
def parse(obj: dict):
return Place(
id=obj["id"],
fullName=obj["full_name"],
name=obj["name"],
type=obj["place_type"],
country=obj["country"],
countryCode=obj["country_code"],
)
@dataclass
class TextLink(JSONTrait):
url: str
text: str | None
tcourl: str | None
indices: tuple[int, int]
@staticmethod
def parse(obj: dict):
return TextLink(
url=obj["expanded_url"],
text=obj["display_url"],
tcourl=obj["url"],
indices=tuple(obj["indices"]),
)
@dataclass
class UserRef(JSONTrait):
id: int
username: str
displayname: str
@staticmethod
def parse(obj: dict):
return UserRef(id=int(obj["id_str"]), username=obj["screen_name"], displayname=obj["name"])
@dataclass
class User(JSONTrait):
id: int
username: str
displayname: str
rawDescription: str
created: datetime
followersCount: int
friendsCount: int
statusesCount: int
favouritesCount: int
listedCount: int
mediaCount: int
location: str
profileImageUrl: str
profileBannerUrl: str | None = None
protected: bool | None = None
verified: bool | None = None
# descriptionLinks: typing.Optional[typing.List[TextLink]] = None
# link: typing.Optional[TextLink] = None
# label: typing.Optional["UserLabel"] = None
@property
def url(self) -> str:
return f"https://twitter.com/{self.username}"
@staticmethod
def parse(obj: dict):
return User(
id=int(obj["id_str"]),
username=obj["screen_name"],
displayname=obj["name"],
rawDescription=obj["description"],
created=email.utils.parsedate_to_datetime(obj["created_at"]),
followersCount=obj["followers_count"],
friendsCount=obj["friends_count"],
statusesCount=obj["statuses_count"],
favouritesCount=obj["favourites_count"],
listedCount=obj["listed_count"],
mediaCount=obj["media_count"],
location=obj["location"],
profileImageUrl=obj["profile_image_url_https"],
profileBannerUrl=obj.get("profile_banner_url"),
verified=obj.get("verified"),
protected=obj.get("protected"),
)
@dataclass
class Tweet(JSONTrait):
id: int
date: datetime
user: User
lang: str
rawContent: str
replyCount: int
retweetCount: int
likeCount: int
quoteCount: int
conversationId: int
hashtags: list[str]
cashtags: list[str]
mentionedUsers: list[UserRef]
links: list[TextLink]
viewCount: int | None = None
retweetedTweet: Optional["Tweet"] = None
quotedTweet: Optional["Tweet"] = None
place: Optional[Place] = None
coordinates: Optional[Coordinates] = None
# renderedContent: str
# source: str | None = None
# sourceUrl: str | None = None
# sourceLabel: str | None = None
# media: typing.Optional[typing.List["Medium"]] = None
# inReplyToTweetId: typing.Optional[int] = None
# inReplyToUser: typing.Optional["User"] = None
# card: typing.Optional["Card"] = None
# vibe: typing.Optional["Vibe"] = None
@property
def url(self):
return f"https://twitter.com/{self.user.username}/status/{self.id}"
@staticmethod
def parse(obj: dict, res: dict):
rt_obj = get_or(res, f"tweets.{obj.get('retweeted_status_id_str')}")
qt_obj = get_or(res, f"tweets.{obj.get('quoted_status_id_str')}")
return Tweet(
id=int(obj["id_str"]),
date=email.utils.parsedate_to_datetime(obj["created_at"]),
user=User.parse(res["users"][obj["user_id_str"]]),
lang=obj["lang"],
rawContent=obj["full_text"],
replyCount=obj["reply_count"],
retweetCount=obj["retweet_count"],
likeCount=obj["favorite_count"],
quoteCount=obj["quote_count"],
conversationId=int(obj["conversation_id_str"]),
hashtags=[x["text"] for x in get_or(obj, "entities.hashtags", [])],
cashtags=[x["text"] for x in get_or(obj, "entities.symbols", [])],
mentionedUsers=[UserRef.parse(x) for x in get_or(obj, "entities.user_mentions", [])],
links=[TextLink.parse(x) for x in get_or(obj, "entities.urls", [])],
viewCount=int_or_none(obj, "ext_views.count"),
retweetedTweet=Tweet.parse(rt_obj, res) if rt_obj else None,
quotedTweet=Tweet.parse(qt_obj, res) if qt_obj else None,
place=Place.parse(obj["place"]) if obj.get("place") else None,
coordinates=Coordinates.parse(obj),
)