diff --git a/readme.md b/readme.md index eaad5ac..e3a407c 100644 --- a/readme.md +++ b/readme.md @@ -4,21 +4,51 @@ Twitter GraphQL and Search API implementation with [SNScrape](https://github.com ```python import asyncio -from twapi.client import UserClient -from twapi.pool import AccountsPool -from twapi.search import Search +from twapi.account import Account +from twapi.accounts_pool import AccountsPool +from twapi.api import API +from twapi.utils import gather async def main(): - acc1 = UserClient("user1", "pass1", "user1@example.com", "email_pass1") - acc2 = UserClient("user2", "pass2", "user2@example.com", "email_pass2") + acc1 = Account("user1", "pass1", "user1@example.com", "email_pass1") + acc2 = Account("user2", "pass2", "user2@example.com", "email_pass2") pool = AccountsPool() pool.add_account(acc1) pool.add_account(acc2) - search = Search(pool) - async for rep in search.query("elon musk"): - print(rep.status_code, rep.json()) + # login all accounts if required (not account file found) + # session file will be saved to `accounts/{username}.json` + await pool.login() + + api = API(pool) + + # search api + await gather(api.search("elon musk", limit=20)) # list[Tweet] + + # graphql api + tweet_id = 20 + user_id, user_login = 2244994945, "twitterdev" + + await api.tweet_details(tweet_id) # Tweet + await gather(api.retweeters(tweet_id, limit=20)) # list[User] + await gather(api.favoriters(tweet_id, limit=20)) # list[User] + + await api.user_by_id(user_id) # User + await api.user_by_login(user_login) # User + await gather(api.followers(user_id, limit=20)) # list[User] + await gather(api.following(user_id, limit=20)) # list[User] + await gather(api.user_tweets(user_id, limit=20)) # list[Tweet] + await gather(api.user_tweets_and_replies(user_id, limit=20)) # list[Tweet] + + # note 1: limit is optional, default is -1 (no limit) + # note 2: all methods have `raw` version e.g.: + + async for tweet in api.search("elon musk"): + print(tweet.id, tweet.user.username, tweet.rawContent) # tweet is `Tweet` object + + async for rep in api.search_raw("elon musk"): + print(rep.status_code, rep.json()) # rep is `httpx.Response` object if __name__ == "__main__": asyncio.run(main()) diff --git a/twapi/accounts_pool.py b/twapi/accounts_pool.py index 1f342bf..62a555a 100644 --- a/twapi/accounts_pool.py +++ b/twapi/accounts_pool.py @@ -2,7 +2,7 @@ import asyncio from loguru import logger -from .account import Account +from .account import Account, Status class AccountsPool: @@ -12,7 +12,16 @@ class AccountsPool: def add_account(self, account: Account): self.accounts.append(account) - def get_login_by_token(self, auth_token: str) -> str: + async def login(self): + for x in self.accounts: + try: + if x.status == Status.NEW: + await x.login() + except Exception as e: + logger.error(f"Error logging in to {x.username}: {e}") + pass + + def get_username_by_token(self, auth_token: str) -> str: for x in self.accounts: if x.client.cookies.get("auth_token") == auth_token: return x.username diff --git a/twapi/search.py b/twapi/api.py similarity index 99% rename from twapi/search.py rename to twapi/api.py index bbeaf74..56e0178 100644 --- a/twapi/search.py +++ b/twapi/api.py @@ -10,7 +10,7 @@ from .models import Tweet, User from .utils import encode_params, find_item, to_old_obj, to_search_like -class Search: +class API: def __init__(self, pool: AccountsPool): self.pool = pool @@ -21,7 +21,7 @@ class Search: ll = rep.headers.get("x-rate-limit-limit", -1) auth_token = rep.request.headers["cookie"].split("auth_token=")[1].split(";")[0] - username = self.pool.get_login_by_token(auth_token) + username = self.pool.get_username_by_token(auth_token) return f"{username} {lr}/{ll}" diff --git a/twapi/utils.py b/twapi/utils.py index 6523a87..c65da5a 100644 --- a/twapi/utils.py +++ b/twapi/utils.py @@ -1,6 +1,6 @@ import json from collections import defaultdict -from typing import Any, TypeVar +from typing import Any, AsyncGenerator, TypeVar from httpx import HTTPStatusError, Response from loguru import logger @@ -8,6 +8,13 @@ from loguru import logger T = TypeVar("T") +async def gather(gen: AsyncGenerator[T, None]) -> list[T]: + items = [] + async for x in gen: + items.append(x) + return items + + def raise_for_status(rep: Response, label: str): try: rep.raise_for_status()