Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Read-only Twitter/X data: search tweets, user profiles, followers, replies, and retweets via twitterapi.io.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
tools.py
1"""2Twitter/X Tools — BaseTool subclasses for agent use.3413 read-only tools: search tweets, get tweets, user info, user tweets,5user followers, user followings, tweet replies, tweet retweeters, search users,6article, thread context, quote tweets, trends.7"""89import asyncio10import logging1112from core.tool import BaseTool, ToolContext, ToolResult13from .client import TwitterApiClient1415logger = logging.getLogger(__name__)1617# Module-level shared client instance18_client: TwitterApiClient = None192021def _get_client() -> TwitterApiClient:22global _client23if _client is None:24_client = TwitterApiClient()25return _client262728# ── Tweet Tools ──────────────────────────────────────────────────────────────293031class TwitterSearchTweetsTool(BaseTool):32"""Search tweets with advanced query syntax."""3334@property35def name(self) -> str:36return "twitter_search_tweets"3738@property39def description(self) -> str:40return """Search Twitter/X tweets using advanced query syntax.4142Supports operators: keyword matching, from:user, to:user, #hashtag, $cashtag,43lang:en, has:media, has:links, is:reply, min_faves:100, since:2024-01-01, until:2024-12-31.4445Parameters:46- query: Search query (required). Examples: "bitcoin", "from:elonmusk crypto", "$SOL min_faves:50"47- cursor: Pagination cursor from previous response (optional)4849Returns: tweets array with text, author, metrics, and next cursor for pagination"""5051@property52def parameters(self) -> dict:53return {54"type": "object",55"properties": {56"query": {57"type": "string",58"description": "Search query (supports advanced operators)",59},60"cursor": {61"type": "string",62"description": "Pagination cursor from previous response",63},64},65"required": ["query"],66}6768async def execute(self, ctx: ToolContext, query: str = "", cursor: str = None, **kwargs) -> ToolResult:69if not query:70return ToolResult(success=False, error="'query' is required")71try:72client = _get_client()73data = await asyncio.to_thread(client.search_tweets, query, cursor=cursor)74return ToolResult(success=True, output=data)75except Exception as e:76return ToolResult(success=False, error=str(e))777879class TwitterGetTweetsTool(BaseTool):80"""Get tweets by their IDs."""8182@property83def name(self) -> str:84return "twitter_get_tweets"8586@property87def description(self) -> str:88return """Get one or more tweets by their tweet IDs.8990Parameters:91- tweet_ids: Array of tweet ID strings (required, e.g. ["1234567890", "9876543210"])9293Returns: tweets array with full tweet data"""9495@property96def parameters(self) -> dict:97return {98"type": "object",99"properties": {100"tweet_ids": {101"type": "array",102"items": {"type": "string"},103"description": "Array of tweet ID strings",104},105},106"required": ["tweet_ids"],107}108109async def execute(self, ctx: ToolContext, tweet_ids: list = None, **kwargs) -> ToolResult:110if not tweet_ids:111return ToolResult(success=False, error="'tweet_ids' is required")112try:113client = _get_client()114data = await asyncio.to_thread(client.get_tweets, tweet_ids)115return ToolResult(success=True, output=data)116except Exception as e:117return ToolResult(success=False, error=str(e))118119120class TwitterTweetRepliesTool(BaseTool):121"""Get replies to a specific tweet."""122123@property124def name(self) -> str:125return "twitter_tweet_replies"126127@property128def description(self) -> str:129return """Get replies to a specific tweet.130131Parameters:132- tweet_id: Tweet ID to get replies for (required)133- cursor: Pagination cursor from previous response (optional)134135Returns: replies array with tweet data and next cursor"""136137@property138def parameters(self) -> dict:139return {140"type": "object",141"properties": {142"tweet_id": {143"type": "string",144"description": "Tweet ID to get replies for",145},146"cursor": {147"type": "string",148"description": "Pagination cursor from previous response",149},150},151"required": ["tweet_id"],152}153154async def execute(self, ctx: ToolContext, tweet_id: str = "", cursor: str = None, **kwargs) -> ToolResult:155if not tweet_id:156return ToolResult(success=False, error="'tweet_id' is required")157try:158client = _get_client()159data = await asyncio.to_thread(client.get_tweet_replies, tweet_id, cursor=cursor)160return ToolResult(success=True, output=data)161except Exception as e:162return ToolResult(success=False, error=str(e))163164165class TwitterTweetRetweetersTool(BaseTool):166"""Get users who retweeted a tweet."""167168@property169def name(self) -> str:170return "twitter_tweet_retweeters"171172@property173def description(self) -> str:174return """Get users who retweeted a specific tweet.175176Parameters:177- tweet_id: Tweet ID to get retweeters for (required)178- cursor: Pagination cursor from previous response (optional)179180Returns: users array with profile data and next cursor"""181182@property183def parameters(self) -> dict:184return {185"type": "object",186"properties": {187"tweet_id": {188"type": "string",189"description": "Tweet ID to get retweeters for",190},191"cursor": {192"type": "string",193"description": "Pagination cursor from previous response",194},195},196"required": ["tweet_id"],197}198199async def execute(self, ctx: ToolContext, tweet_id: str = "", cursor: str = None, **kwargs) -> ToolResult:200if not tweet_id:201return ToolResult(success=False, error="'tweet_id' is required")202try:203client = _get_client()204data = await asyncio.to_thread(client.get_tweet_retweeters, tweet_id, cursor=cursor)205return ToolResult(success=True, output=data)206except Exception as e:207return ToolResult(success=False, error=str(e))208209210# ── User Tools ───────────────────────────────────────────────────────────────211212213class TwitterGetArticleTool(BaseTool):214"""Get long-form article for a tweet."""215216@property217def name(self) -> str:218return "twitter_get_article"219220@property221def description(self) -> str:222return """Get X/Twitter long-form article by tweet ID.223224Parameters:225- tweet_id: Tweet ID of the article post (required)226227Returns: article object with title, preview text, cover media URL, and content blocks"""228229@property230def parameters(self) -> dict:231return {232"type": "object",233"properties": {234"tweet_id": {235"type": "string",236"description": "Tweet ID of the article post",237},238},239"required": ["tweet_id"],240}241242async def execute(self, ctx: ToolContext, tweet_id: str = "", **kwargs) -> ToolResult:243if not tweet_id:244return ToolResult(success=False, error="'tweet_id' is required")245try:246client = _get_client()247data = await asyncio.to_thread(client.get_article, tweet_id)248return ToolResult(success=True, output=data)249except Exception as e:250return ToolResult(success=False, error=str(e))251252253class TwitterTweetThreadContextTool(BaseTool):254"""Get complete thread context for a tweet."""255256@property257def name(self) -> str:258return "twitter_tweet_thread_context"259260@property261def description(self) -> str:262return """Get complete thread context for a tweet.263264Parameters:265- tweet_id: Tweet ID to get thread context for (required)266267Returns: parent tweets + direct replies in a single response"""268269@property270def parameters(self) -> dict:271return {272"type": "object",273"properties": {274"tweet_id": {275"type": "string",276"description": "Tweet ID to get thread context for",277},278},279"required": ["tweet_id"],280}281282async def execute(self, ctx: ToolContext, tweet_id: str = "", **kwargs) -> ToolResult:283if not tweet_id:284return ToolResult(success=False, error="'tweet_id' is required")285try:286client = _get_client()287data = await asyncio.to_thread(client.get_tweet_thread_context, tweet_id)288return ToolResult(success=True, output=data)289except Exception as e:290return ToolResult(success=False, error=str(e))291292293class TwitterTweetQuoteTool(BaseTool):294"""Get quote tweets for a tweet."""295296@property297def name(self) -> str:298return "twitter_tweet_quote"299300@property301def description(self) -> str:302return """Get quote tweets for a specific tweet.303304Parameters:305- tweet_id: Tweet ID to get quote tweets for (required)306- cursor: Pagination cursor from previous response (optional)307308Returns: quote tweets list and next cursor"""309310@property311def parameters(self) -> dict:312return {313"type": "object",314"properties": {315"tweet_id": {316"type": "string",317"description": "Tweet ID to get quote tweets for",318},319"cursor": {320"type": "string",321"description": "Pagination cursor from previous response",322},323},324"required": ["tweet_id"],325}326327async def execute(self, ctx: ToolContext, tweet_id: str = "", cursor: str = None, **kwargs) -> ToolResult:328if not tweet_id:329return ToolResult(success=False, error="'tweet_id' is required")330try:331client = _get_client()332data = await asyncio.to_thread(client.get_tweet_quote, tweet_id, cursor=cursor)333return ToolResult(success=True, output=data)334except Exception as e:335return ToolResult(success=False, error=str(e))336337338class TwitterGetTrendsTool(BaseTool):339"""Get Twitter/X trends."""340341@property342def name(self) -> str:343return "twitter_get_trends"344345@property346def description(self) -> str:347return """Get Twitter/X trends.348349Parameters:350- woeid: Where On Earth ID (optional)351- country: Country code/name (optional)352- category: Trend category (optional)353- limit: Number of trends to return (optional)354355Returns: trends list"""356357@property358def parameters(self) -> dict:359return {360"type": "object",361"properties": {362"woeid": {363"type": "string",364"description": "Where On Earth ID",365},366"country": {367"type": "string",368"description": "Country code/name",369},370"category": {371"type": "string",372"description": "Trend category",373},374"limit": {375"type": "integer",376"description": "Number of trends to return",377},378},379"required": [],380}381382async def execute(383self,384ctx: ToolContext,385woeid: str = None,386country: str = None,387category: str = None,388limit: int = None,389**kwargs,390) -> ToolResult:391try:392client = _get_client()393data = await asyncio.to_thread(394client.get_trends,395woeid=woeid,396country=country,397category=category,398limit=limit,399)400return ToolResult(success=True, output=data)401except Exception as e:402return ToolResult(success=False, error=str(e))403404405class TwitterUserInfoTool(BaseTool):406"""Get a Twitter user's profile information."""407408@property409def name(self) -> str:410return "twitter_user_info"411412@property413def description(self) -> str:414return """Get a Twitter/X user's profile information: bio, follower count, following count, tweet count, verification status.415416Parameters:417- username: Twitter handle without @ (required, e.g. "elonmusk")418419Returns: user profile with name, bio, followers_count, following_count, tweet_count, verified"""420421@property422def parameters(self) -> dict:423return {424"type": "object",425"properties": {426"username": {427"type": "string",428"description": "Twitter handle without @ (e.g. 'elonmusk')",429},430},431"required": ["username"],432}433434async def execute(self, ctx: ToolContext, username: str = "", **kwargs) -> ToolResult:435if not username:436return ToolResult(success=False, error="'username' is required")437try:438client = _get_client()439data = await asyncio.to_thread(client.get_user_info, username)440return ToolResult(success=True, output=data)441except Exception as e:442return ToolResult(success=False, error=str(e))443444445class TwitterUserTweetsTool(BaseTool):446"""Get a user's recent tweets."""447448@property449def name(self) -> str:450return "twitter_user_tweets"451452@property453def description(self) -> str:454return """Get a Twitter/X user's recent tweets.455456Parameters:457- username: Twitter handle without @ (required, e.g. "elonmusk")458- cursor: Pagination cursor from previous response (optional)459460Returns: tweets array with text, metrics, timestamps, and next cursor"""461462@property463def parameters(self) -> dict:464return {465"type": "object",466"properties": {467"username": {468"type": "string",469"description": "Twitter handle without @ (e.g. 'elonmusk')",470},471"cursor": {472"type": "string",473"description": "Pagination cursor from previous response",474},475},476"required": ["username"],477}478479async def execute(self, ctx: ToolContext, username: str = "", cursor: str = None, **kwargs) -> ToolResult:480if not username:481return ToolResult(success=False, error="'username' is required")482try:483client = _get_client()484data = await asyncio.to_thread(client.get_user_tweets, username, cursor=cursor)485return ToolResult(success=True, output=data)486except Exception as e:487return ToolResult(success=False, error=str(e))488489490class TwitterUserFollowersTool(BaseTool):491"""Get a user's followers."""492493@property494def name(self) -> str:495return "twitter_user_followers"496497@property498def description(self) -> str:499return """Get a Twitter/X user's followers.500501Parameters:502- username: Twitter handle without @ (required, e.g. "elonmusk")503- cursor: Pagination cursor from previous response (optional)504505Returns: users array with profile data and next cursor"""506507@property508def parameters(self) -> dict:509return {510"type": "object",511"properties": {512"username": {513"type": "string",514"description": "Twitter handle without @ (e.g. 'elonmusk')",515},516"cursor": {517"type": "string",518"description": "Pagination cursor from previous response",519},520},521"required": ["username"],522}523524async def execute(self, ctx: ToolContext, username: str = "", cursor: str = None, **kwargs) -> ToolResult:525if not username:526return ToolResult(success=False, error="'username' is required")527try:528client = _get_client()529data = await asyncio.to_thread(client.get_user_followers, username, cursor=cursor)530return ToolResult(success=True, output=data)531except Exception as e:532return ToolResult(success=False, error=str(e))533534535class TwitterUserFollowingsTool(BaseTool):536"""Get accounts a user follows."""537538@property539def name(self) -> str:540return "twitter_user_followings"541542@property543def description(self) -> str:544return """Get accounts that a Twitter/X user follows.545546Parameters:547- username: Twitter handle without @ (required, e.g. "elonmusk")548- cursor: Pagination cursor from previous response (optional)549550Returns: users array with profile data and next cursor"""551552@property553def parameters(self) -> dict:554return {555"type": "object",556"properties": {557"username": {558"type": "string",559"description": "Twitter handle without @ (e.g. 'elonmusk')",560},561"cursor": {562"type": "string",563"description": "Pagination cursor from previous response",564},565},566"required": ["username"],567}568569async def execute(self, ctx: ToolContext, username: str = "", cursor: str = None, **kwargs) -> ToolResult:570if not username:571return ToolResult(success=False, error="'username' is required")572try:573client = _get_client()574data = await asyncio.to_thread(client.get_user_followings, username, cursor=cursor)575return ToolResult(success=True, output=data)576except Exception as e:577return ToolResult(success=False, error=str(e))578579580class TwitterSearchUsersTool(BaseTool):581"""Search for Twitter users."""582583@property584def name(self) -> str:585return "twitter_search_users"586587@property588def description(self) -> str:589return """Search for Twitter/X users by name or keyword.590591Parameters:592- query: Search query (required, e.g. "crypto analyst", "bitcoin")593- cursor: Pagination cursor from previous response (optional)594595Returns: users array with profile data and next cursor"""596597@property598def parameters(self) -> dict:599return {600"type": "object",601"properties": {602"query": {603"type": "string",604"description": "Search query for users",605},606"cursor": {607"type": "string",608"description": "Pagination cursor from previous response",609},610},611"required": ["query"],612}613614async def execute(self, ctx: ToolContext, query: str = "", cursor: str = None, **kwargs) -> ToolResult:615if not query:616return ToolResult(success=False, error="'query' is required")617try:618client = _get_client()619data = await asyncio.to_thread(client.search_users, query, cursor=cursor)620return ToolResult(success=True, output=data)621except Exception as e:622return ToolResult(success=False, error=str(e))623