mirror of
https://github.com/Starchild-ai-agent/official-skills.git
synced 2026-09-18 10:19:40 +08:00
Update twitter skill to v1.4.0 - sync local changes
- Update SKILL.md with new tools and improved documentation - Sync tools.py, client.py, __init__.py - Bump version to 1.4.0 in skills.json Co-authored-by: Starchild <noreply@iamstarchild.com>
This commit is contained in:
+1
-1
@@ -184,7 +184,7 @@
|
||||
{
|
||||
"name": "twitter",
|
||||
"path": "twitter/SKILL.md",
|
||||
"version": "1.3.0",
|
||||
"version": "1.4.0",
|
||||
"description": "Twitter/X (x.com) data lookup — fetch tweets by URL or ID, search tweets, user profiles, followers, replies. Use for ANY x.com or twitter.com URL."
|
||||
},
|
||||
{
|
||||
|
||||
+36
-31
@@ -1,35 +1,34 @@
|
||||
---
|
||||
name: twitter
|
||||
version: 1.3.0
|
||||
description: Twitter/X (x.com) data lookup — fetch tweets by URL or ID, search tweets, user profiles, followers, replies. Use for ANY x.com or twitter.com URL.
|
||||
version: 1.4.0
|
||||
description: "Twitter/X (x.com) data lookup \u2014 fetch tweets by URL or ID, search\
|
||||
\ tweets, user profiles, followers, replies. Use for ANY x.com or twitter.com URL."
|
||||
tools:
|
||||
- twitter_search_tweets
|
||||
- twitter_get_tweets
|
||||
- twitter_user_info
|
||||
- twitter_user_tweets
|
||||
- twitter_user_followers
|
||||
- twitter_user_followings
|
||||
- twitter_tweet_replies
|
||||
- twitter_tweet_retweeters
|
||||
- twitter_search_users
|
||||
|
||||
- twitter_search_tweets
|
||||
- twitter_get_tweets
|
||||
- twitter_user_info
|
||||
- twitter_user_tweets
|
||||
- twitter_user_followers
|
||||
- twitter_user_followings
|
||||
- twitter_tweet_replies
|
||||
- twitter_tweet_retweeters
|
||||
- twitter_search_users
|
||||
- twitter_get_article
|
||||
- twitter_tweet_thread_context
|
||||
- twitter_tweet_quote
|
||||
- twitter_get_trends
|
||||
metadata:
|
||||
starchild:
|
||||
emoji: "🐦"
|
||||
emoji: "\U0001F426"
|
||||
skillKey: twitter
|
||||
requires:
|
||||
env: [TWITTER_API_KEY]
|
||||
|
||||
env:
|
||||
- TWITTER_API_KEY
|
||||
user-invocable: false
|
||||
disable-model-invocation: false
|
||||
---
|
||||
|
||||
# Twitter / X Data
|
||||
|
||||
Read-only access to Twitter/X via twitterapi.io. Use these tools to look up tweets, users, followers, and social activity.
|
||||
|
||||
## 🔴 HARD LIMITS — READ FIRST
|
||||
|
||||
> **⛔ CALL AT MOST 3 TWITTER TOOLS PER RESPONSE. STOP AFTER 3 CALLS.**
|
||||
> After each tool call, check: "Do I have enough data to answer?" If yes → STOP AND REPLY.
|
||||
> **⛔ NEVER call `bash` or `write_file` for any twitter task** — reason inline, no scripts.
|
||||
@@ -38,7 +37,6 @@ Read-only access to Twitter/X via twitterapi.io. Use these tools to look up twee
|
||||
> **⛔ NEVER call `coin_price`, `cg_trending`, `cg_coins_markets`** — 价格数据超出 Twitter skill 范围。
|
||||
|
||||
## 🔗 URL Handling — x.com / twitter.com
|
||||
|
||||
> **⛔ NEVER use `web_fetch` for x.com or twitter.com URLs** — Twitter blocks scraping, you'll only get a login wall.
|
||||
> **✅ ALWAYS extract the tweet ID from the URL and use `twitter_get_tweets`.**
|
||||
|
||||
@@ -54,7 +52,6 @@ Read-only access to Twitter/X via twitterapi.io. Use these tools to look up twee
|
||||
→ Never: `web_fetch("https://x.com/...")`
|
||||
|
||||
## 💡 Few-Shot Examples
|
||||
|
||||
**Q: 找 3 个关于 BTC ETF 的高赞推文,只要 ID 和点赞数**
|
||||
→ PLAN: 1 call `twitter_search_tweets("BTC ETF min_faves:100")` → pick top 3 from results → reply JSON
|
||||
→ STOP after 1 call. Total tools: 1
|
||||
@@ -72,7 +69,6 @@ Read-only access to Twitter/X via twitterapi.io. Use these tools to look up twee
|
||||
→ Total tools: 3. STOP.
|
||||
|
||||
## ⚡ FAST PATHS (act immediately, no clarification needed)
|
||||
|
||||
| Trigger keywords | Action |
|
||||
|-----------------|--------|
|
||||
| x.com or twitter.com URL with `/status/{id}` | Extract tweet ID → `twitter_get_tweets(tweet_ids=["{id}"])` — **never web_fetch** |
|
||||
@@ -83,7 +79,6 @@ Read-only access to Twitter/X via twitterapi.io. Use these tools to look up twee
|
||||
| what did @username post | Call `twitter_user_tweets` |
|
||||
|
||||
## Tool Decision Tree
|
||||
|
||||
**"Search for tweets about a topic"** → `twitter_search_tweets`
|
||||
Advanced query with operators: keywords, from:user, #hashtag, $cashtag, min_faves, date ranges.
|
||||
|
||||
@@ -111,12 +106,23 @@ Users who retweeted a specific tweet.
|
||||
**"Find accounts related to a topic"** → `twitter_search_users`
|
||||
Search users by name or keyword.
|
||||
|
||||
**"Read a long X article"** → `twitter_get_article`
|
||||
Pass the article tweet ID. Returns title, preview, cover, and content blocks.
|
||||
|
||||
**"Get full thread context"** → `twitter_tweet_thread_context`
|
||||
One call returns parent chain + direct replies for the target tweet.
|
||||
|
||||
**"Who quoted this tweet?"** → `twitter_tweet_quote`
|
||||
Get quote tweets for a specific tweet ID.
|
||||
|
||||
**"What is trending now?"** → `twitter_get_trends`
|
||||
Get trends with optional `woeid`, `country`, `category`, `limit`.
|
||||
|
||||
**"Crypto sentiment scan / 情绪扫描 / market mood"** → `twitter_search_tweets` (call once per coin)
|
||||
For BTC/ETH/SOL sentiment: search `"$BTC"`, `"$ETH"`, `"$SOL"` separately, then summarize tone inline.
|
||||
⛔ NEVER call `twitter_user_info`, `twitter_user_followers`, or `twitter_user_tweets` during a sentiment scan — text analysis only.
|
||||
|
||||
## Available Tools
|
||||
|
||||
| Tool | Description | Key Params |
|
||||
|------|-------------|------------|
|
||||
| `twitter_search_tweets` | Advanced tweet search | `query` (required), `cursor` |
|
||||
@@ -128,9 +134,12 @@ For BTC/ETH/SOL sentiment: search `"$BTC"`, `"$ETH"`, `"$SOL"` separately, then
|
||||
| `twitter_tweet_replies` | Replies to a tweet | `tweet_id` (required), `cursor` |
|
||||
| `twitter_tweet_retweeters` | Who retweeted | `tweet_id` (required), `cursor` |
|
||||
| `twitter_search_users` | Search for users | `query` (required), `cursor` |
|
||||
| `twitter_get_article` | Get long-form article | `tweet_id` (required) |
|
||||
| `twitter_tweet_thread_context` | Get full thread context | `tweet_id` (required) |
|
||||
| `twitter_tweet_quote` | Get quote tweets | `tweet_id` (required), `cursor` |
|
||||
| `twitter_get_trends` | Get trends | `woeid`, `country`, `category`, `limit` |
|
||||
|
||||
## Usage Patterns
|
||||
|
||||
### ⚠️ Token Budget Rules
|
||||
- Sentiment scan: max **3 `twitter_search_tweets` calls** (one per coin), then summarize. Stop.
|
||||
- Account research: max **2 tool calls total** unless user asks for more depth.
|
||||
@@ -146,7 +155,6 @@ For BTC/ETH/SOL sentiment: search `"$BTC"`, `"$ETH"`, `"$SOL"` separately, then
|
||||
2. `twitter_search_users` with the topic — find relevant accounts
|
||||
|
||||
## Output Constraints (IMPORTANT for small models)
|
||||
|
||||
- **Max 1 `twitter_search_tweets` call per coin/topic** — do not repeat searches for same query. First result set is sufficient.
|
||||
- **Max 3 `twitter_user_info` calls per response** — only look up the most relevant accounts.
|
||||
- **Never call `bash` or `write_file` for Twitter data** — reason inline directly from tool results.
|
||||
@@ -165,7 +173,6 @@ For BTC/ETH/SOL sentiment: search `"$BTC"`, `"$ETH"`, `"$SOL"` separately, then
|
||||
3. `twitter_user_tweets` to check content quality
|
||||
|
||||
## Search Query Operators
|
||||
|
||||
The `twitter_search_tweets` tool supports advanced operators:
|
||||
|
||||
| Operator | Example | Description |
|
||||
@@ -188,13 +195,11 @@ The `twitter_search_tweets` tool supports advanced operators:
|
||||
Combine operators: `from:VitalikButerin $ETH min_faves:100 since:2024-01-01`
|
||||
|
||||
## Pagination
|
||||
|
||||
Most endpoints support cursor-based pagination. When a response includes a cursor value, pass it as the `cursor` parameter to get the next page. If no cursor is returned, you've reached the end.
|
||||
|
||||
## Notes
|
||||
|
||||
- **API key required**: Set `TWITTER_API_KEY` environment variable. Tools will error without it.
|
||||
- **Read-only**: These tools only retrieve data. No posting, liking, or following.
|
||||
- **Usernames**: Always pass without the `@` prefix (e.g. `"elonmusk"` not `"@elonmusk"`).
|
||||
- **Tweet IDs**: Use string format for tweet IDs to avoid integer overflow issues.
|
||||
- **Rate limits**: The API has rate limits. If you get rate-limited, wait before retrying.
|
||||
- **Rate limits**: The API has rate limits. If you get rate-limited, wait before retrying.
|
||||
+23
-3
@@ -1,9 +1,13 @@
|
||||
"""
|
||||
Twitter/X Extension — Read-only data via twitterapi.io
|
||||
|
||||
Provides 9 tools for Twitter/X data:
|
||||
Provides 13 tools for Twitter/X data:
|
||||
- twitter_search_tweets: Advanced tweet search
|
||||
- twitter_get_tweets: Get tweets by ID
|
||||
- twitter_get_article: Get long-form article by tweet ID
|
||||
- twitter_tweet_thread_context: Get complete thread context
|
||||
- twitter_tweet_quote: Get quote tweets for a tweet
|
||||
- twitter_get_trends: Get trends
|
||||
- twitter_user_info: User profile lookup
|
||||
- twitter_user_tweets: User's recent tweets
|
||||
- twitter_user_followers: User's followers
|
||||
@@ -41,6 +45,10 @@ def register(api) -> List[str]:
|
||||
from .tools import (
|
||||
TwitterSearchTweetsTool,
|
||||
TwitterGetTweetsTool,
|
||||
TwitterGetArticleTool,
|
||||
TwitterTweetThreadContextTool,
|
||||
TwitterTweetQuoteTool,
|
||||
TwitterGetTrendsTool,
|
||||
TwitterUserInfoTool,
|
||||
TwitterUserTweetsTool,
|
||||
TwitterUserFollowersTool,
|
||||
@@ -52,6 +60,10 @@ def register(api) -> List[str]:
|
||||
|
||||
api.register_tool(TwitterSearchTweetsTool())
|
||||
api.register_tool(TwitterGetTweetsTool())
|
||||
api.register_tool(TwitterGetArticleTool())
|
||||
api.register_tool(TwitterTweetThreadContextTool())
|
||||
api.register_tool(TwitterTweetQuoteTool())
|
||||
api.register_tool(TwitterGetTrendsTool())
|
||||
api.register_tool(TwitterUserInfoTool())
|
||||
api.register_tool(TwitterUserTweetsTool())
|
||||
api.register_tool(TwitterUserFollowersTool())
|
||||
@@ -63,6 +75,10 @@ def register(api) -> List[str]:
|
||||
registered = [
|
||||
"twitter_search_tweets",
|
||||
"twitter_get_tweets",
|
||||
"twitter_get_article",
|
||||
"twitter_tweet_thread_context",
|
||||
"twitter_tweet_quote",
|
||||
"twitter_get_trends",
|
||||
"twitter_user_info",
|
||||
"twitter_user_tweets",
|
||||
"twitter_user_followers",
|
||||
@@ -82,11 +98,15 @@ def register(api) -> List[str]:
|
||||
# Extension metadata
|
||||
EXTENSION_INFO = {
|
||||
"name": "twitter",
|
||||
"version": "1.0.0",
|
||||
"description": "Twitter/X data — search tweets, user profiles, followers, replies",
|
||||
"version": "1.1.0",
|
||||
"description": "Twitter/X data — search tweets, article, thread context, quote tweets, trends, users",
|
||||
"tools": [
|
||||
"twitter_search_tweets",
|
||||
"twitter_get_tweets",
|
||||
"twitter_get_article",
|
||||
"twitter_tweet_thread_context",
|
||||
"twitter_tweet_quote",
|
||||
"twitter_get_trends",
|
||||
"twitter_user_info",
|
||||
"twitter_user_tweets",
|
||||
"twitter_user_followers",
|
||||
|
||||
+33
-2
@@ -1,7 +1,8 @@
|
||||
"""
|
||||
Twitter API client — wraps twitterapi.io endpoints.
|
||||
|
||||
Read-only: search tweets, user profiles, followers, replies, etc.
|
||||
Read-only: search tweets, user profiles, followers, replies, thread context,
|
||||
quote tweets, article, and trends.
|
||||
Auth: X-API-Key header from TWITTER_API_KEY env var.
|
||||
"""
|
||||
|
||||
@@ -11,6 +12,8 @@ from typing import Any, List
|
||||
|
||||
from core.http_client import proxied_get
|
||||
|
||||
CALLER_ID = "chat:twitter-skill"
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
TWITTERAPI_BASE_URL = "https://api.twitterapi.io"
|
||||
@@ -31,7 +34,7 @@ class TwitterApiClient:
|
||||
"""GET a twitterapi.io endpoint."""
|
||||
url = f"{self.base_url}{path}"
|
||||
|
||||
headers = {}
|
||||
headers = {"SC-CALLER-ID": CALLER_ID}
|
||||
if self.api_key:
|
||||
headers["X-API-Key"] = self.api_key
|
||||
|
||||
@@ -68,6 +71,34 @@ class TwitterApiClient:
|
||||
params["cursor"] = cursor
|
||||
return self._get("/twitter/tweet/retweeters", params)
|
||||
|
||||
def get_tweet_thread_context(self, tweet_id: str) -> dict:
|
||||
"""Get complete thread context for a tweet (parents + direct replies)."""
|
||||
return self._get("/twitter/tweet/thread_context", {"tweetId": tweet_id})
|
||||
|
||||
def get_tweet_quote(self, tweet_id: str, cursor: str = None) -> dict:
|
||||
"""Get quote tweets for a tweet."""
|
||||
params = {"tweetId": tweet_id}
|
||||
if cursor:
|
||||
params["cursor"] = cursor
|
||||
return self._get("/twitter/tweet/quotes", params)
|
||||
|
||||
def get_article(self, tweet_id: str) -> dict:
|
||||
"""Get article content by tweet ID (long-form X article)."""
|
||||
return self._get("/twitter/article", {"tweet_id": tweet_id})
|
||||
|
||||
def get_trends(self, woeid: str = None, country: str = None, category: str = None, limit: int = None) -> dict:
|
||||
"""Get trending topics."""
|
||||
params = {}
|
||||
if woeid:
|
||||
params["woeid"] = woeid
|
||||
if country:
|
||||
params["country"] = country
|
||||
if category:
|
||||
params["category"] = category
|
||||
if limit is not None:
|
||||
params["limit"] = limit
|
||||
return self._get("/twitter/trends", params)
|
||||
|
||||
# ── User Endpoints ───────────────────────────────────────────────────
|
||||
|
||||
def get_user_info(self, username: str) -> dict:
|
||||
|
||||
+195
-2
@@ -1,8 +1,9 @@
|
||||
"""
|
||||
Twitter/X Tools — BaseTool subclasses for agent use.
|
||||
|
||||
9 read-only tools: search tweets, get tweets, user info, user tweets,
|
||||
user followers, user followings, tweet replies, tweet retweeters, search users.
|
||||
13 read-only tools: search tweets, get tweets, user info, user tweets,
|
||||
user followers, user followings, tweet replies, tweet retweeters, search users,
|
||||
article, thread context, quote tweets, trends.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
@@ -209,6 +210,198 @@ Returns: users array with profile data and next cursor"""
|
||||
# ── User Tools ───────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
class TwitterGetArticleTool(BaseTool):
|
||||
"""Get long-form article for a tweet."""
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
return "twitter_get_article"
|
||||
|
||||
@property
|
||||
def description(self) -> str:
|
||||
return """Get X/Twitter long-form article by tweet ID.
|
||||
|
||||
Parameters:
|
||||
- tweet_id: Tweet ID of the article post (required)
|
||||
|
||||
Returns: article object with title, preview text, cover media URL, and content blocks"""
|
||||
|
||||
@property
|
||||
def parameters(self) -> dict:
|
||||
return {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"tweet_id": {
|
||||
"type": "string",
|
||||
"description": "Tweet ID of the article post",
|
||||
},
|
||||
},
|
||||
"required": ["tweet_id"],
|
||||
}
|
||||
|
||||
async def execute(self, ctx: ToolContext, tweet_id: str = "", **kwargs) -> ToolResult:
|
||||
if not tweet_id:
|
||||
return ToolResult(success=False, error="'tweet_id' is required")
|
||||
try:
|
||||
client = _get_client()
|
||||
data = await asyncio.to_thread(client.get_article, tweet_id)
|
||||
return ToolResult(success=True, output=data)
|
||||
except Exception as e:
|
||||
return ToolResult(success=False, error=str(e))
|
||||
|
||||
|
||||
class TwitterTweetThreadContextTool(BaseTool):
|
||||
"""Get complete thread context for a tweet."""
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
return "twitter_tweet_thread_context"
|
||||
|
||||
@property
|
||||
def description(self) -> str:
|
||||
return """Get complete thread context for a tweet.
|
||||
|
||||
Parameters:
|
||||
- tweet_id: Tweet ID to get thread context for (required)
|
||||
|
||||
Returns: parent tweets + direct replies in a single response"""
|
||||
|
||||
@property
|
||||
def parameters(self) -> dict:
|
||||
return {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"tweet_id": {
|
||||
"type": "string",
|
||||
"description": "Tweet ID to get thread context for",
|
||||
},
|
||||
},
|
||||
"required": ["tweet_id"],
|
||||
}
|
||||
|
||||
async def execute(self, ctx: ToolContext, tweet_id: str = "", **kwargs) -> ToolResult:
|
||||
if not tweet_id:
|
||||
return ToolResult(success=False, error="'tweet_id' is required")
|
||||
try:
|
||||
client = _get_client()
|
||||
data = await asyncio.to_thread(client.get_tweet_thread_context, tweet_id)
|
||||
return ToolResult(success=True, output=data)
|
||||
except Exception as e:
|
||||
return ToolResult(success=False, error=str(e))
|
||||
|
||||
|
||||
class TwitterTweetQuoteTool(BaseTool):
|
||||
"""Get quote tweets for a tweet."""
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
return "twitter_tweet_quote"
|
||||
|
||||
@property
|
||||
def description(self) -> str:
|
||||
return """Get quote tweets for a specific tweet.
|
||||
|
||||
Parameters:
|
||||
- tweet_id: Tweet ID to get quote tweets for (required)
|
||||
- cursor: Pagination cursor from previous response (optional)
|
||||
|
||||
Returns: quote tweets list and next cursor"""
|
||||
|
||||
@property
|
||||
def parameters(self) -> dict:
|
||||
return {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"tweet_id": {
|
||||
"type": "string",
|
||||
"description": "Tweet ID to get quote tweets for",
|
||||
},
|
||||
"cursor": {
|
||||
"type": "string",
|
||||
"description": "Pagination cursor from previous response",
|
||||
},
|
||||
},
|
||||
"required": ["tweet_id"],
|
||||
}
|
||||
|
||||
async def execute(self, ctx: ToolContext, tweet_id: str = "", cursor: str = None, **kwargs) -> ToolResult:
|
||||
if not tweet_id:
|
||||
return ToolResult(success=False, error="'tweet_id' is required")
|
||||
try:
|
||||
client = _get_client()
|
||||
data = await asyncio.to_thread(client.get_tweet_quote, tweet_id, cursor=cursor)
|
||||
return ToolResult(success=True, output=data)
|
||||
except Exception as e:
|
||||
return ToolResult(success=False, error=str(e))
|
||||
|
||||
|
||||
class TwitterGetTrendsTool(BaseTool):
|
||||
"""Get Twitter/X trends."""
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
return "twitter_get_trends"
|
||||
|
||||
@property
|
||||
def description(self) -> str:
|
||||
return """Get Twitter/X trends.
|
||||
|
||||
Parameters:
|
||||
- woeid: Where On Earth ID (optional)
|
||||
- country: Country code/name (optional)
|
||||
- category: Trend category (optional)
|
||||
- limit: Number of trends to return (optional)
|
||||
|
||||
Returns: trends list"""
|
||||
|
||||
@property
|
||||
def parameters(self) -> dict:
|
||||
return {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"woeid": {
|
||||
"type": "string",
|
||||
"description": "Where On Earth ID",
|
||||
},
|
||||
"country": {
|
||||
"type": "string",
|
||||
"description": "Country code/name",
|
||||
},
|
||||
"category": {
|
||||
"type": "string",
|
||||
"description": "Trend category",
|
||||
},
|
||||
"limit": {
|
||||
"type": "integer",
|
||||
"description": "Number of trends to return",
|
||||
},
|
||||
},
|
||||
"required": [],
|
||||
}
|
||||
|
||||
async def execute(
|
||||
self,
|
||||
ctx: ToolContext,
|
||||
woeid: str = None,
|
||||
country: str = None,
|
||||
category: str = None,
|
||||
limit: int = None,
|
||||
**kwargs,
|
||||
) -> ToolResult:
|
||||
try:
|
||||
client = _get_client()
|
||||
data = await asyncio.to_thread(
|
||||
client.get_trends,
|
||||
woeid=woeid,
|
||||
country=country,
|
||||
category=category,
|
||||
limit=limit,
|
||||
)
|
||||
return ToolResult(success=True, output=data)
|
||||
except Exception as e:
|
||||
return ToolResult(success=False, error=str(e))
|
||||
|
||||
|
||||
class TwitterUserInfoTool(BaseTool):
|
||||
"""Get a Twitter user's profile information."""
|
||||
|
||||
|
||||
Reference in New Issue
Block a user