89 lines
2.7 KiB
Python
89 lines
2.7 KiB
Python
|
|
from __future__ import annotations
|
||
|
|
|
||
|
|
import asyncio
|
||
|
|
import logging
|
||
|
|
|
||
|
|
import tweepy
|
||
|
|
|
||
|
|
logger = logging.getLogger(__name__)
|
||
|
|
|
||
|
|
X_API_V2_BASE = "https://api.x.com"
|
||
|
|
X_UPLOAD_BASE = "https://upload.twitter.com"
|
||
|
|
|
||
|
|
|
||
|
|
def create_tweepy_client(account: dict) -> tweepy.Client:
|
||
|
|
return tweepy.Client(
|
||
|
|
consumer_key=account["api_key"],
|
||
|
|
consumer_secret=account["api_secret"],
|
||
|
|
access_token=account["access_token"],
|
||
|
|
access_token_secret=account["access_secret"],
|
||
|
|
return_type=dict,
|
||
|
|
wait_on_rate_limit=False,
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
async def verify_credentials(
|
||
|
|
account: dict, timeout_seconds: float = 5.0
|
||
|
|
) -> dict | None:
|
||
|
|
try:
|
||
|
|
client = create_tweepy_client(account)
|
||
|
|
response = await asyncio.to_thread(
|
||
|
|
client.get_me,
|
||
|
|
user_fields=["id", "username", "name", "profile_image_url"],
|
||
|
|
)
|
||
|
|
data = response.get("data", {})
|
||
|
|
if data:
|
||
|
|
return {
|
||
|
|
"user_id": data.get("id", ""),
|
||
|
|
"username": data.get("username", ""),
|
||
|
|
"name": data.get("name", ""),
|
||
|
|
"profile_image_url": data.get("profile_image_url", ""),
|
||
|
|
}
|
||
|
|
except tweepy.Unauthorized:
|
||
|
|
logger.warning("X API: OAuth 1.0a credentials invalid or revoked")
|
||
|
|
except tweepy.TooManyRequests:
|
||
|
|
logger.warning("X API: rate limited during credential verification")
|
||
|
|
except Exception:
|
||
|
|
logger.exception("X API: credential verification failed")
|
||
|
|
return None
|
||
|
|
|
||
|
|
|
||
|
|
async def probe(account: dict, timeout_seconds: float = 5.0) -> bool:
|
||
|
|
user = await verify_credentials(account, timeout_seconds)
|
||
|
|
return user is not None
|
||
|
|
|
||
|
|
|
||
|
|
async def get_user_by_username(account: dict, username: str) -> dict | None:
|
||
|
|
client = create_tweepy_client(account)
|
||
|
|
try:
|
||
|
|
response = await asyncio.to_thread(
|
||
|
|
client.get_user,
|
||
|
|
username=username,
|
||
|
|
user_fields=[
|
||
|
|
"id",
|
||
|
|
"username",
|
||
|
|
"name",
|
||
|
|
"description",
|
||
|
|
"public_metrics",
|
||
|
|
"profile_image_url",
|
||
|
|
],
|
||
|
|
)
|
||
|
|
return response.get("data", {})
|
||
|
|
except Exception as e:
|
||
|
|
logger.warning("Twitter get_user_by_username error: %s", e)
|
||
|
|
return None
|
||
|
|
|
||
|
|
|
||
|
|
async def get_users_by_ids(account: dict, user_ids: list[str]) -> list[dict]:
|
||
|
|
client = create_tweepy_client(account)
|
||
|
|
try:
|
||
|
|
response = await asyncio.to_thread(
|
||
|
|
client.get_users,
|
||
|
|
ids=user_ids,
|
||
|
|
user_fields=["id", "username", "name", "profile_image_url"],
|
||
|
|
)
|
||
|
|
return response.get("data", [])
|
||
|
|
except Exception as e:
|
||
|
|
logger.warning("Twitter get_users_by_ids error: %s", e)
|
||
|
|
return []
|