Ready-to-run Python scripts covering common Twitter/X operations. Each scenario is implemented three ways — choose the approach that fits your setup.
""" S01 · Scheduled Tweet — ClawBot SDK ===================================== EN: Post via TweetClaw browser extension. Add to TweetPilot Task Manager for automatic scheduling. 中文:通过 TweetClaw 浏览器扩展发推。加入任务管理器实现定时自动发送。 Manual run / 手动运行: PYTHONPATH="$HOME/.tweetpilot/clawbot" python3 clawbot.py """ from datetime import datetime from clawbot import ClawBotClient # ── Config — edit TWEET_TEXT; {date} inserts today's date (YYYY-MM-DD) # 配置 — 修改 TWEET_TEXT;{date} 将被替换为当天日期 TWEET_TEXT = "Daily update {date} — powered by TweetPilot" def main(): client = ClawBotClient() # Auto-select the first connected TweetClaw instance. # For multi-account use, ask TweetPilot AI for guidance. # 自动选择第一个已连接的 TweetClaw 实例;多账号请询问 TweetPilot AI。 instances = client.x.status.get_instances() instance_id = None if isinstance(instances, list) and instances: first = instances[0] instance_id = first.get("instanceId") or first.get("id") text = TWEET_TEXT.format(date=datetime.now().strftime("%Y-%m-%d")) result = client.x.actions.create_tweet(text, instance_id=instance_id) print(f"Tweet sent / 发推成功: {text}") print(f"Result: {result}") if __name__ == "__main__": main()
""" S01 · Scheduled Tweet — xdk SDK ================================== EN: Post via Twitter OAuth using xdk. TweetPilot provides the token automatically — no manual setup. 中文:通过 Twitter OAuth + xdk 客户端发推,TweetPilot 自动提供 token。 pip install xdk requests """ import sys import requests from datetime import datetime from xdk import Client # ── Config — replace with your Twitter numeric ID # 配置 — 替换为你的 Twitter 数字 ID(TweetPilot → 账号设置 → 账号 ID) TWITTER_ID = "YOUR_TWITTER_ID" TWEET_TEXT = "Daily update {date} — powered by TweetPilot" def get_access_token(twitter_id: str) -> str: # Fetch OAuth token from TweetPilot rust-bridge (port 20088) # 从 TweetPilot rust-bridge(端口 20088)获取 OAuth token try: resp = requests.post( "http://127.0.0.1:20088/api/v1/x/oauth/access-token", json={"twitter_id": twitter_id}, timeout=10, ) resp.raise_for_status() return resp.json()["access_token"] except requests.exceptions.ConnectionError: print("ERROR: TweetPilot not running / TweetPilot 未在运行") sys.exit(1) except requests.exceptions.HTTPError as e: print(f"ERROR: {e.response.status_code} — {e.response.text}") sys.exit(1) def main(): # Guard: TWITTER_ID must be replaced before running (OAuth accounts only) # 运行前检查:TWITTER_ID 必须替换为真实 ID(仅支持 OAuth 授权账号) if TWITTER_ID == "YOUR_TWITTER_ID": print("ERROR: Set TWITTER_ID to your numeric Twitter ID (OAuth accounts only).") print("错误:请将 TWITTER_ID 替换为数字 ID,仅限 TweetPilot 中 x 开头的 OAuth 账号。") sys.exit(1) # ⚠️ Must use access_token= (User OAuth 2.0), NOT bearer_token= (read-only App token) # ⚠️ 必须用 access_token= 传入,bearer_token= 是只读的 App token,写操作会报错 client = Client(access_token=get_access_token(TWITTER_ID)) text = TWEET_TEXT.format(date=datetime.now().strftime("%Y-%m-%d")) result = client.posts.create(body={"text": text}) print(f"Tweet sent / 发推成功: {text}") print(f"Tweet ID: {result.data.id}") if __name__ == "__main__": main()
""" S01 · Scheduled Tweet — HTTP REST API (LocalBridge) ===================================================== EN: Call TweetPilot's LocalBridge REST API directly — no OAuth token needed. The browser extension handles authentication transparently. Requires TweetClaw extension online in the browser. 中文:直接调用 TweetPilot LocalBridge REST API 发推,无需 OAuth token。 浏览器扩展自动处理认证,需 TweetClaw 扩展在线。 pip install requests """ import sys import requests from datetime import datetime LOCAL_BRIDGE = "http://127.0.0.1:20088" TWEET_TEXT = "Daily update {date} — powered by TweetPilot" def post_tweet(text: str) -> str: # POST /api/v1/x/tweets — LocalBridge proxies to Twitter via browser extension # LocalBridge 通过浏览器扩展代理发推,无需手动传 token try: resp = requests.post( f"{LOCAL_BRIDGE}/api/v1/x/tweets", json={"text": text}, timeout=15, ) resp.raise_for_status() except requests.exceptions.ConnectionError: print("ERROR: TweetPilot not running / TweetPilot 未在运行") sys.exit(1) except requests.exceptions.HTTPError as e: print(f"ERROR: {e.response.status_code} — {e.response.text}") sys.exit(1) data = resp.json() # LocalBridge returns HTTP 200 even when Twitter rejects the tweet. # Must check the response body for GraphQL-level errors (e.g. code 187 = duplicate). # LocalBridge 即使推特拒绝也返回 HTTP 200,必须检查响应体中的 GraphQL 错误。 errors = data.get("data", {}).get("errors") if errors: for err in errors: print(f"ERROR: Twitter rejected — code {err.get('code')}: {err.get('message')}") sys.exit(1) try: return data["data"]["data"]["create_tweet"]["tweet_results"]["result"]["rest_id"] except (KeyError, TypeError): return "unknown" def main(): text = TWEET_TEXT.format(date=datetime.now().strftime("%Y-%m-%d")) tweet_id = post_tweet(text) print(f"Tweet sent / 发推成功: {text}") print(f"Tweet ID: {tweet_id}") if __name__ == "__main__": main()
# Post a tweet via TweetPilot LocalBridge — no token needed
# 通过 TweetPilot LocalBridge 发推,无需 token
curl -s -X POST http://127.0.0.1:20088/api/v1/x/tweets \
-H "Content-Type: application/json" \
-d '{"text": "Daily update — powered by TweetPilot"}'
""" S02 · Tweet with Image — ClawBot SDK ====================================== EN: Upload a local image and post it as a tweet via browser extension. ClawBot handles chunked upload + tweet creation in one call. Requires TweetClaw extension online in the browser. 中文:通过浏览器扩展上传本地图片并发推。 ClawBot 封装了分块上传和发推的完整流程,一行调用即可完成。 pip install requests (clawbot pre-installed via TweetPilot) """ import sys, os from clawbot import ClawBotClient IMAGE_PATH = "image.jpg" # ← Replace / 替换为你的图片路径 TWEET_TEXT = "🚀 TweetPilot v1 is live — the Social AI Agent built for X.\nAutomate posts, replies, likes & research while you stay focused.\nBuilt for creators, builders & teams → tweetpilot.ai" def main(): if not os.path.exists(IMAGE_PATH): print(f"ERROR: Image not found: {IMAGE_PATH}") sys.exit(1) client = ClawBotClient() # Resolve instance_id for multi-extension environments # 多扩展实例环境下,获取第一个可用的 instanceId instances = client.x.status.get_instances() instance_id = None if isinstance(instances, list) and instances: instance_id = instances[0].get("instanceId") or instances[0].get("id") # post_tweet() uploads the image then creates the tweet in one call # post_tweet() 内部自动完成上传 + 发推,无需手动处理 media_id result = client.media.post_tweet( text=TWEET_TEXT, file_paths=[IMAGE_PATH], instance_id=instance_id, ) print(f"Tweet sent / 发推成功: {TWEET_TEXT}") print(f"Result: {result}") if __name__ == "__main__": main()
""" S02 · Tweet with Image — xdk SDK =================================== EN: Upload image via Twitter media API and attach to a tweet. TweetPilot provides the OAuth access token automatically. Requires OAuth account authorized in TweetPilot (x-prefix accounts). Note: media.upload() requires the 'media.write' OAuth scope. 中文:通过 Twitter media API 上传图片并发推。 TweetPilot 自动提供 OAuth token,需 x 开头的 OAuth 授权账号。 注:media.upload() 需要 media.write OAuth scope。 pip install xdk requests """ import sys, os, base64 import requests from xdk import Client TWITTER_ID = "YOUR_TWITTER_ID" # ← OAuth account numeric ID / OAuth 账号数字 ID IMAGE_PATH = "image.jpg" # ← Replace / 替换为你的图片路径 TWEET_TEXT = "🚀 TweetPilot v1 is live — the Social AI Agent built for X.\nAutomate posts, replies, likes & research while you stay focused.\nBuilt for creators, builders & teams → tweetpilot.ai" def get_access_token(twitter_id): try: resp = requests.post( "http://127.0.0.1:20088/api/v1/x/oauth/access-token", json={"twitter_id": twitter_id}, timeout=10, ) resp.raise_for_status() return resp.json()["access_token"] except requests.exceptions.ConnectionError: print("ERROR: TweetPilot not running / TweetPilot 未在运行") sys.exit(1) except requests.exceptions.HTTPError as e: print(f"ERROR: {e.response.status_code} — {e.response.text}") sys.exit(1) def main(): if TWITTER_ID == "YOUR_TWITTER_ID": print("ERROR: Set TWITTER_ID to your numeric Twitter ID (OAuth accounts only).") sys.exit(1) if not os.path.exists(IMAGE_PATH): print(f"ERROR: Image not found: {IMAGE_PATH}") sys.exit(1) # Must use access_token= (User OAuth 2.0), NOT bearer_token= (read-only) # 必须用 access_token= 传入,bearer_token= 是只读的 App token client = Client(access_token=get_access_token(TWITTER_ID)) # Read image and base64-encode for Twitter media API (requires media.write scope) # 读取图片并 base64 编码(需要 media.write scope) with open(IMAGE_PATH, "rb") as f: media_b64 = base64.b64encode(f.read()).decode() upload_result = client.media.upload(body={ "media": media_b64, "media_category": "tweet_image", }) media_id = (upload_result.data["id"] if isinstance(upload_result.data, dict) else upload_result.data.id) result = client.posts.create(body={ "text": TWEET_TEXT, "media": {"media_ids": [media_id]}, }) tweet_id = result.data["id"] if isinstance(result.data, dict) else result.data.id print(f"Tweet sent / 发推成功: {TWEET_TEXT[:50]}...") print(f"Tweet ID: {tweet_id}") if __name__ == "__main__": main()
""" S02 · Tweet with Image — HTTP REST API (LocalBridge) pip install requests """ import sys, os, time, mimetypes import requests LOCAL_BRIDGE = "http://127.0.0.1:20088" IMAGE_PATH = "image.jpg" # ← Replace / 替换为你的图片路径 TWEET_TEXT = "Check this out — powered by TweetPilot" def get_instance_id(): resp = requests.get(f"{LOCAL_BRIDGE}/api/v1/x/instances", timeout=10) resp.raise_for_status() instances = resp.json() if not instances: print("ERROR: No TweetClaw instance connected"); sys.exit(1) return instances[0]["instanceId"] def upload_image(path, instance_id): # Step 1: create task / 创建任务 task_id = requests.post(f"{LOCAL_BRIDGE}/api/v1/tasks", json={ "clientName": "tweetClaw", "instanceId": instance_id, "taskKind": "x.media_upload", "inputMode": "chunked_binary", "params": {}, }, timeout=10).json()["taskId"] # Step 2: upload file (single chunk for images < 5 MB) / 上传文件 with open(path, "rb") as f: data = f.read() requests.put(f"{LOCAL_BRIDGE}/api/v1/tasks/{task_id}/input/0", data=data, headers={"Content-Type": "application/octet-stream"}, timeout=30).raise_for_status() # Step 3: seal → start → poll → result ct = mimetypes.guess_type(path)[0] or "image/jpeg" requests.post(f"{LOCAL_BRIDGE}/api/v1/tasks/{task_id}/seal", json={"totalParts": 1, "totalBytes": len(data), "contentType": ct}, timeout=10).raise_for_status() requests.post(f"{LOCAL_BRIDGE}/api/v1/tasks/{task_id}/start", timeout=10).raise_for_status() deadline = time.time() + 60 while time.time() < deadline: st = requests.get(f"{LOCAL_BRIDGE}/api/v1/tasks/{task_id}", timeout=10).json() if st["state"] == "completed": break if st["state"] in ("failed","cancelled"): print(f"ERROR: {st.get('errorMessage')}"); sys.exit(1) time.sleep(2) result = requests.get(f"{LOCAL_BRIDGE}/api/v1/tasks/{task_id}/result", timeout=10).json() return result["mediaId"] def main(): if not os.path.exists(IMAGE_PATH): print(f"ERROR: Image not found: {IMAGE_PATH}"); sys.exit(1) try: instance_id = get_instance_id() except requests.exceptions.ConnectionError: print("ERROR: TweetPilot not running"); sys.exit(1) print(f"Uploading / 上传中: {IMAGE_PATH}") media_id = upload_image(IMAGE_PATH, instance_id) print(f"Upload done / 上传完成, media_id: {media_id}") resp = requests.post(f"{LOCAL_BRIDGE}/api/v1/x/tweets", json={"text": TWEET_TEXT, "media_ids": [media_id]}, timeout=15) resp.raise_for_status() data = resp.json() errors = data.get("data", {}).get("errors") if errors: for e in errors: print(f"ERROR: code {e.get('code')}: {e.get('message')}") sys.exit(1) try: tweet_id = data["data"]["data"]["create_tweet"]["tweet_results"]["result"]["rest_id"] except (KeyError, TypeError): tweet_id = "unknown" print(f"Tweet sent / 发推成功: {TWEET_TEXT}") print(f"Tweet ID: {tweet_id}") if __name__ == "__main__": main()
# Step 1: Get instance ID / 获取实例 ID INSTANCE=$(curl -s http://127.0.0.1:20088/api/v1/x/instances \ | python3 -c "import sys,json; print(json.load(sys.stdin)[0]['instanceId'])") # Step 2: Create upload task / 创建上传任务 TASK_ID=$(curl -s -X POST http://127.0.0.1:20088/api/v1/tasks \ -H "Content-Type: application/json" \ -d "{\"clientName\":\"tweetClaw\",\"instanceId\":\"$INSTANCE\",\"taskKind\":\"x.media_upload\",\"inputMode\":\"chunked_binary\",\"params\":{}}" \ | python3 -c "import sys,json; print(json.load(sys.stdin)['taskId'])") # Step 3: Upload image / 上传图片 curl -s -X PUT http://127.0.0.1:20088/api/v1/tasks/$TASK_ID/input/0 \ -H "Content-Type: application/octet-stream" \ --data-binary @image.jpg # Step 4: Seal + Start / 封装并启动任务 SIZE=$(wc -c < image.jpg | tr -d ' ') curl -s -X POST http://127.0.0.1:20088/api/v1/tasks/$TASK_ID/seal \ -H "Content-Type: application/json" \ -d "{\"totalParts\":1,\"totalBytes\":$SIZE,\"contentType\":\"image/jpeg\"}" curl -s -X POST http://127.0.0.1:20088/api/v1/tasks/$TASK_ID/start # Step 5: Get media_id from result / 从结果中提取 media_id MEDIA_ID=$(curl -s http://127.0.0.1:20088/api/v1/tasks/$TASK_ID/result \ | python3 -c "import sys,json; print(json.load(sys.stdin)['mediaId'])") # Step 6: Post tweet with image / 带图发推 curl -s -X POST http://127.0.0.1:20088/api/v1/x/tweets \ -H "Content-Type: application/json" \ -d "{\"text\":\"Check this out — powered by TweetPilot\",\"media_ids\":[\"$MEDIA_ID\"]}"
""" S03 · Search & Auto-Reply — ClawBot SDK ========================================== EN: Search tweets by keyword, reply ONLY to the first result. ⚠️ Replying to many tweets at once risks rate-limiting or suspension. Requires TweetClaw extension online in the browser. 中文:按关键词搜索推文,只回复第一条。 ⚠️ 批量回复极易触发限流,建议每 30 分钟以上定时执行。 pip install requests (clawbot pre-installed via TweetPilot) """ import sys from clawbot import ClawBotClient SEARCH_QUERY = "TweetPilot" # ← Keyword / 搜索关键词 REPLY_TEXT = "Thanks for mentioning us! 🙌 — TweetPilot" def main(): client = ClawBotClient() instances = client.x.status.get_instances() instance_id = None if isinstance(instances, list) and instances: instance_id = instances[0].get("instanceId") or instances[0].get("id") print(f'Searching: "{SEARCH_QUERY}"') tweets = client.x.search.search_tweets(SEARCH_QUERY, count=5, instance_id=instance_id) if not tweets: print("No tweets found / 未找到相关推文"); return # ⚠️ Reply ONLY to the first tweet — never loop over all results # ⚠️ 只回复第一条,不循环全部结果 tweet = tweets[0] print(f"Replying to {tweet.id}: {(tweet.text or '')[:60]!r}") try: result = client.x.actions.reply(tweet_id=tweet.id, text=REPLY_TEXT, instance_id=instance_id) print(f"Done / 完成: {result}") except Exception as e: print(f"Failed / 失败: {e}") sys.exit(1) if __name__ == "__main__": main()
""" S03 · Search & Auto-Reply — xdk SDK ======================================= EN: Search recent tweets via Twitter API v2, reply to the first replyable result. Tries each result in order; skips retweets and reply-restricted tweets. Requires OAuth account authorized in TweetPilot Account Settings. 中文:通过 Twitter API v2 搜索推文,回复第一条可回复的推文。 按顺序尝试,自动跳过转发和有回复限制的推文。 pip install xdk requests """ import sys import requests from xdk import Client TWITTER_ID = "YOUR_TWITTER_ID" # ← OAuth account numeric ID SEARCH_QUERY = "TweetPilot" REPLY_TEXT = "Thanks for mentioning us! 🙌 — TweetPilot" def get_access_token(twitter_id): try: resp = requests.post("http://127.0.0.1:20088/api/v1/x/oauth/access-token", json={"twitter_id": twitter_id}, timeout=10) resp.raise_for_status() return resp.json()["access_token"] except requests.exceptions.ConnectionError: print("ERROR: TweetPilot not running"); sys.exit(1) except requests.exceptions.HTTPError as e: print(f"ERROR: {e.response.status_code} — {e.response.text}"); sys.exit(1) def main(): if TWITTER_ID == "YOUR_TWITTER_ID": print("ERROR: Set TWITTER_ID to your numeric Twitter ID (OAuth only).") sys.exit(1) # Must use access_token= (User OAuth 2.0), NOT bearer_token= (read-only) client = Client(access_token=get_access_token(TWITTER_ID)) print(f'Searching: "{SEARCH_QUERY}"') first_page = next(iter(client.posts.search_recent(query=SEARCH_QUERY, max_results=10)), None) tweets = (first_page.data or []) if first_page else [] if not tweets: print("No tweets found / 未找到相关推文"); return # Try each tweet; skip RTs; on 403 (reply restricted) move to next. # 逐条尝试;跳过转发;遇到 403(回复被限制)换下一条。 replied = False for t in tweets: t_text = (t.get("text") if isinstance(t, dict) else getattr(t, "text", "") or "") tweet_id = (t.get("id") if isinstance(t, dict) else t.id) if t_text.startswith("RT @"): continue print(f"Trying {tweet_id}: {t_text[:60]!r}") try: result = client.posts.create(body={ "text": REPLY_TEXT, "reply": {"in_reply_to_tweet_id": tweet_id}, }) reply_id = (result.data["id"] if isinstance(result.data, dict) else result.data.id) print(f"Done / 完成, reply ID: {reply_id}") replied = True break except Exception as e: err = getattr(getattr(e, "response", None), "text", str(e)) print(f" Skipped: {err}") continue if not replied: print("Failed / 失败: no replyable tweet found / 搜索结果中无可回复推文") sys.exit(1) if __name__ == "__main__": main()print(f"Failed / 失败: {e}") sys.exit(1) if __name__ == "__main__": main() main()
""" S03 · Search & Auto-Reply — HTTP REST API (LocalBridge) EN: Search tweets via LocalBridge, reply ONLY to the first result. ⚠️ Replying to many tweets at once risks rate-limiting or suspension. pip install requests """ import sys import requests LOCAL_BRIDGE = "http://127.0.0.1:20088" SEARCH_QUERY = "TweetPilot" # ← Keyword / 搜索关键词 REPLY_TEXT = "Thanks for mentioning us! 🙌 — TweetPilot" def search_tweets(query, count): try: resp = requests.get(f"{LOCAL_BRIDGE}/api/v1/x/search", params={"query": query, "count": count}, timeout=15) resp.raise_for_status() except requests.exceptions.ConnectionError: print("ERROR: TweetPilot not running"); sys.exit(1) except requests.exceptions.HTTPError as e: print(f"ERROR: {e.response.status_code}"); sys.exit(1) tweets = [] try: instructions = (resp.json().get("data", {}) .get("data", {}) .get("search_by_raw_query", {}).get("search_timeline", {}) .get("timeline", {}).get("instructions", [])) for instr in instructions: for entry in instr.get("entries", []): r = entry.get("content", {}).get("itemContent", {}).get("tweet_results", {}).get("result", {}) if not r: continue t = r.get("tweet") or r rid = t.get("rest_id") or r.get("rest_id") if rid: tweets.append({"id": rid, "text": t.get("legacy", {}).get("full_text", "")}) except Exception as e: print(f"WARNING: parse error: {e}") return tweets def reply_to_tweet(tweet_id, text): resp = requests.post(f"{LOCAL_BRIDGE}/api/v1/x/replies", json={"tweetId": tweet_id, "text": text}, timeout=15) resp.raise_for_status() data = resp.json() errors = data.get("data", {}).get("errors") if errors: raise RuntimeError("; ".join(f"code {e.get('code')}: {e.get('message')}" for e in errors)) try: return data["data"]["data"]["create_tweet"]["tweet_results"]["result"]["rest_id"] except (KeyError, TypeError): return "unknown" def main(): print(f'Searching: "{SEARCH_QUERY}"') tweets = search_tweets(SEARCH_QUERY, count=10) if not tweets: print("No tweets found / 未找到相关推文"); sys.exit(1) # Try each tweet; skip RTs; on failure move to next. # 逐条尝试;跳过转发;失败则换下一条。 replied = False for tweet in tweets: if tweet["text"].startswith("RT @"): continue print(f"Trying tweet {tweet['id']}: {tweet['text'][:60]!r}") try: reply_id = reply_to_tweet(tweet["id"], REPLY_TEXT) print(f"Done / 完成, reply ID: {reply_id}") replied = True break except Exception as e: print(f" Skipped: {e}"); continue if not replied: print("Failed / 失败: no replyable tweet found / 搜索结果中无可回复推文") sys.exit(1) if __name__ == "__main__": main()
# Step 1: Search tweets / 搜索推文 curl -s "http://127.0.0.1:20088/api/v1/x/search?query=TweetPilot&count=5" # Step 2: Reply to a tweet / 回复推文(替换 TWEET_ID) curl -s -X POST http://127.0.0.1:20088/api/v1/x/replies \ -H "Content-Type: application/json" \ -d '{"tweetId": "TWEET_ID", "text": "Thanks for mentioning us! — TweetPilot"}'
""" S04 · Timeline Auto-Like — ClawBot SDK EN: Read home timeline, like ONLY the first tweet matching a keyword. ⚠️ Only ONE like per run — schedule every 30+ min to avoid rate-limits. 中文:读取主页时间线,只点赞第一条匹配关键词的推文。⚠️ 每次只点赞一条。 pip install requests (clawbot pre-installed via TweetPilot) """ import sys from clawbot import ClawBotClient KEYWORD = "AI" # ← Keyword filter (case-insensitive), "" = like first tweet / 关键词(空=点赞第一条) def main(): client = ClawBotClient() instances = client.x.status.get_instances() instance_id = None if isinstance(instances, list) and instances: instance_id = instances[0].get("instanceId") or instances[0].get("id") print("Fetching timeline / 获取时间线...") tweets = client.x.timeline.list_timeline_tweets(instance_id=instance_id) if not tweets: print("No tweets in timeline / 时间线为空"); return # Find the first matching tweet — like ONLY that one # 找到第一条匹配推文,只点赞这一条 target = next( (t for t in tweets if not KEYWORD or KEYWORD.lower() in (t.text or "").lower()), None ) if not target: print(f'No tweet matched "{KEYWORD}" / 未找到包含关键词的推文'); return print(f"Liking {target.id}: {(target.text or '')[:60]!r}") try: result = client.x.actions.like(tweet_id=target.id, instance_id=instance_id) print(f"Done / 完成: {result}") except Exception as e: print(f"Failed / 失败: {e}") sys.exit(1) if __name__ == "__main__": main()
""" S04 · Timeline Auto-Like — xdk SDK EN: Read home timeline via Twitter API v2, like ONLY the first matching tweet. ⚠️ Only ONE like per run — schedule every 30+ min to avoid rate-limits. Requires OAuth account authorized in TweetPilot (x-prefix accounts). 中文:通过 Twitter API v2 读取主页时间线,只点赞第一条匹配推文。 pip install xdk requests """ import sys import requests from xdk import Client TWITTER_ID = "YOUR_TWITTER_ID" # ← OAuth account numeric ID KEYWORD = "AI" # ← Keyword filter, "" = like first tweet def get_access_token(twitter_id): try: resp = requests.post("http://127.0.0.1:20088/api/v1/x/oauth/access-token", json={"twitter_id": twitter_id}, timeout=10) resp.raise_for_status() return resp.json()["access_token"] except requests.exceptions.ConnectionError: print("ERROR: TweetPilot not running"); sys.exit(1) except requests.exceptions.HTTPError as e: print(f"ERROR: {e.response.status_code} — {e.response.text}"); sys.exit(1) def main(): if TWITTER_ID == "YOUR_TWITTER_ID": print("ERROR: Set TWITTER_ID to your numeric Twitter ID (OAuth only).") sys.exit(1) # Must use access_token= (User OAuth 2.0), NOT bearer_token= (read-only) # 必须用 access_token= 传入,bearer_token= 是只读的 App token client = Client(access_token=get_access_token(TWITTER_ID)) print("Fetching timeline / 获取时间线...") target = None try: for page in client.users.get_timeline(id=TWITTER_ID, max_results=20): for tweet in (page.data or []): if not KEYWORD or KEYWORD.lower() in (tweet.text or "").lower(): target = tweet break if target: break except Exception as e: print(f"ERROR: {e}"); sys.exit(1) if not target: print(f'No tweet matched "{KEYWORD}" / 未找到包含关键词的推文'); return print(f"Liking {target.id}: {(target.text or '')[:60]!r}") try: client.users.like_post(id=TWITTER_ID, body={"tweet_id": target.id}) print(f"Done / 完成: liked tweet {target.id}") except Exception as e: print(f"Failed / 失败: {e}") sys.exit(1) if __name__ == "__main__": main()
""" S04 · Timeline Auto-Like — HTTP REST API (LocalBridge) EN: Read home timeline via LocalBridge, like ONLY the first matching tweet. ⚠️ Only ONE like per run — schedule every 30+ min to avoid rate-limits. pip install requests """ import sys import requests LOCAL_BRIDGE = "http://127.0.0.1:20088" KEYWORD = "AI" # ← Keyword filter, "" = like first tweet / 关键词(空=点赞第一条) def get_timeline(): try: resp = requests.get(f"{LOCAL_BRIDGE}/api/v1/x/timeline", timeout=15) resp.raise_for_status() except requests.exceptions.ConnectionError: print("ERROR: TweetPilot not running"); sys.exit(1) except requests.exceptions.HTTPError as e: print(f"ERROR: {e.response.status_code}"); sys.exit(1) tweets = [] try: instructions = (resp.json().get("data", {}).get("data", {}).get("home", {}) .get("home_timeline_urt", {}).get("instructions", [])) for instr in instructions: for entry in instr.get("entries", []): r = entry.get("content", {}).get("itemContent", {}).get("tweet_results", {}).get("result", {}) if not r: continue t = r.get("tweet") or r rid = t.get("rest_id") or r.get("rest_id") if rid: tweets.append({"id": rid, "text": t.get("legacy", {}).get("full_text", "")}) except Exception as e: print(f"WARNING: parse error: {e}") return tweets def main(): print("Fetching timeline / 获取时间线...") tweets = get_timeline() if not tweets: print("No tweets in timeline / 时间线为空"); return # Find the first matching tweet — like ONLY that one # 找到第一条匹配推文,只点赞这一条 target = next( (t for t in tweets if not KEYWORD or KEYWORD.lower() in t["text"].lower()), None ) if not target: print(f'No tweet matched "{KEYWORD}" / 未找到包含关键词的推文'); return print(f"Liking {target['id']}: {target['text'][:60]!r}") try: resp = requests.post(f"{LOCAL_BRIDGE}/api/v1/x/likes", json={"tweetId": target["id"]}, timeout=15) resp.raise_for_status() if resp.json().get("ok"): print(f"Done / 完成: liked tweet {target['id']}") else: print(f"Failed / 失败: {resp.json()}") sys.exit(1) except Exception as e: print(f"Failed / 失败: {e}") sys.exit(1) if __name__ == "__main__": main()
# Step 1: Fetch timeline / 获取时间线 curl -s http://127.0.0.1:20088/api/v1/x/timeline # Step 2: Like a tweet / 点赞推文(替换 TWEET_ID) curl -s -X POST http://127.0.0.1:20088/api/v1/x/likes \ -H "Content-Type: application/json" \ -d '{"tweetId": "TWEET_ID"}'
""" S05 · Batch Follow — ClawBot SDK EN: Follow a list of user IDs in bulk via the TweetClaw browser extension. Adds a delay between each follow to avoid rate-limiting. 中文:通过 TweetClaw 浏览器扩展批量关注一组用户 ID,每次关注间隔等待。 pip install requests (clawbot pre-installed via TweetPilot) """ import sys, time from clawbot import ClawBotClient TARGET_IDS = [ "44196397", # @elonmusk "783214", # @x ] DELAY_SECONDS = 5 # ← seconds between each follow / 每次关注间隔秒数 def main(): client = ClawBotClient() instances = client.x.status.get_instances() instance_id = None if isinstance(instances, list) and instances: instance_id = instances[0].get("instanceId") or instances[0].get("id") for i, user_id in enumerate(TARGET_IDS): print(f"Following {user_id} ({i + 1}/{len(TARGET_IDS)})...") try: # follow() → POST /api/v1/x/follows via LocalBridge result = client.x.actions.follow(user_id=user_id, instance_id=instance_id) print(f" Done / 完成: {result}") except Exception as e: print(f" Failed / 失败: {e}"); sys.exit(1) if i < len(TARGET_IDS) - 1: time.sleep(DELAY_SECONDS) print(f"\nAll done / 全部完成: followed {len(TARGET_IDS)} users") if __name__ == "__main__": main()
""" S05 · Batch Follow — xdk SDK EN: Follow a list of user IDs in bulk via Twitter API v2. Adds a delay between each follow to avoid rate-limiting. Requires OAuth account authorized in TweetPilot. 中文:通过 Twitter API v2 批量关注一组用户 ID,每次关注间隔等待。 pip install xdk requests """ import sys, time, requests from xdk import Client TWITTER_ID = "YOUR_TWITTER_ID" # ← OAuth account numeric ID / OAuth 账号数字 ID TARGET_IDS = ["44196397", "783214"] # ← IDs to follow / 要关注的用户 ID 列表 DELAY_SECONDS = 5 def get_access_token(twitter_id): try: resp = requests.post("http://127.0.0.1:20088/api/v1/x/oauth/access-token", json={"twitter_id": twitter_id}, timeout=10) resp.raise_for_status() return resp.json()["access_token"] except requests.exceptions.ConnectionError: print("ERROR: TweetPilot not running"); sys.exit(1) except requests.exceptions.HTTPError as e: print(f"ERROR: {e.response.status_code} — {e.response.text}"); sys.exit(1) def main(): if TWITTER_ID == "YOUR_TWITTER_ID": print("ERROR: Set TWITTER_ID to your numeric Twitter ID."); sys.exit(1) # Must use access_token= (User OAuth 2.0), NOT bearer_token= (read-only) client = Client(access_token=get_access_token(TWITTER_ID)) for i, target_id in enumerate(TARGET_IDS): print(f"Following {target_id} ({i + 1}/{len(TARGET_IDS)})...") try: # follow_user requires your own user ID + target user ID client.users.follow_user(id=TWITTER_ID, body={"target_user_id": target_id}) print(f" Done / 完成: followed {target_id}") except Exception as e: body = e.response.text if hasattr(e, "response") and e.response else "" print(f" Failed / 失败: {body or e}"); sys.exit(1) if i < len(TARGET_IDS) - 1: time.sleep(DELAY_SECONDS) print(f"\nAll done / 全部完成: followed {len(TARGET_IDS)} users") if __name__ == "__main__": main()
""" S05 · Batch Follow — HTTP REST API (LocalBridge) EN: Follow a list of user IDs in bulk via LocalBridge. Adds a delay between each follow to avoid rate-limiting. pip install requests """ import sys, time, requests LOCAL_BRIDGE = "http://127.0.0.1:20088" TARGET_IDS = ["44196397", "783214"] # ← IDs to follow / 要关注的用户 ID 列表 DELAY_SECONDS = 5 def follow_user(user_id): # POST /api/v1/x/follows — body: {"userId": "..."} try: resp = requests.post(f"{LOCAL_BRIDGE}/api/v1/x/follows", json={"userId": user_id}, timeout=15) resp.raise_for_status() except requests.exceptions.ConnectionError: print("ERROR: TweetPilot not running"); sys.exit(1) except requests.exceptions.HTTPError as e: print(f"ERROR: {e.response.status_code} — {e.response.text}"); sys.exit(1) data = resp.json() if not data.get("ok") and not data.get("success"): raise RuntimeError(f"Follow failed: {data}") def main(): for i, user_id in enumerate(TARGET_IDS): print(f"Following {user_id} ({i + 1}/{len(TARGET_IDS)})...") try: follow_user(user_id) print(f" Done / 完成: followed {user_id}") except Exception as e: print(f" Failed / 失败: {e}"); sys.exit(1) if i < len(TARGET_IDS) - 1: time.sleep(DELAY_SECONDS) print(f"\nAll done / 全部完成: followed {len(TARGET_IDS)} users") if __name__ == "__main__": main()
# Follow a user via LocalBridge / 通过 LocalBridge 关注用户(替换 USER_ID)
curl -s -X POST http://127.0.0.1:20088/api/v1/x/follows \
-H "Content-Type: application/json" \
-d '{"userId": "USER_ID"}'
""" S06 · Tweet Metrics Report — ClawBot SDK EN: Fetch engagement metrics for a tweet via TweetClaw browser extension. Metrics live in the raw GraphQL "legacy" field. 中文:通过 TweetClaw 浏览器扩展获取指定推文的互动数据(点赞/转推/回复/书签)。 pip install requests (clawbot pre-installed via TweetPilot) """ import sys from clawbot import ClawBotClient TWEET_ID = "1234567890123456789" # ← Replace with target tweet ID / 替换为目标推文 ID def main(): client = ClawBotClient() instances = client.x.status.get_instances() instance_id = None if isinstance(instances, list) and instances: instance_id = instances[0].get("instanceId") or instances[0].get("id") print(f"Fetching tweet {TWEET_ID}...") try: # get_tweet() fetches full tweet object; metrics are in tweet.raw["legacy"] tweet = client.x.tweets.get_tweet(tweet_id=TWEET_ID, instance_id=instance_id) except Exception as e: print(f"ERROR: {e}"); sys.exit(1) if not tweet or not tweet.id: print("ERROR: Tweet not found / 未找到该推文"); sys.exit(1) legacy = tweet.raw.get("legacy", {}) print(f"\n── Tweet Metrics Report / 推文互动报告 ──") print(f"ID : {tweet.id}") print(f"Author : @{tweet.author_screen_name}") print(f"Text : {(tweet.text or '')[:120]}") print(f"Likes : {legacy.get('favorite_count', 'N/A')}") print(f"Retweets : {legacy.get('retweet_count', 'N/A')}") print(f"Replies : {legacy.get('reply_count', 'N/A')}") print(f"Quotes : {legacy.get('quote_count', 'N/A')}") print(f"Bookmarks : {legacy.get('bookmark_count', 'N/A')}") if __name__ == "__main__": main()
""" S06 · Tweet Metrics Report — xdk SDK EN: Fetch engagement metrics via Twitter API v2 public_metrics field. Requires OAuth account authorized in TweetPilot. 中文:通过 Twitter API v2 public_metrics 字段获取推文互动数据。 pip install xdk requests """ import sys, requests from xdk import Client TWITTER_ID = "YOUR_TWITTER_ID" # ← OAuth account numeric ID / OAuth 账号数字 ID TWEET_ID = "1234567890123456789" # ← Replace with target tweet ID / 替换为目标推文 ID def get_access_token(twitter_id): try: resp = requests.post("http://127.0.0.1:20088/api/v1/x/oauth/access-token", json={"twitter_id": twitter_id}, timeout=10) resp.raise_for_status() return resp.json()["access_token"] except requests.exceptions.ConnectionError: print("ERROR: TweetPilot not running"); sys.exit(1) except requests.exceptions.HTTPError as e: print(f"ERROR: {e.response.status_code} — {e.response.text}"); sys.exit(1) def main(): if TWITTER_ID == "YOUR_TWITTER_ID": print("ERROR: Set TWITTER_ID to your numeric Twitter ID."); sys.exit(1) client = Client(access_token=get_access_token(TWITTER_ID)) print(f"Fetching tweet {TWEET_ID}...") try: # get_by_id with public_metrics returns likes/retweets/replies/impressions resp = client.posts.get_by_id(id=TWEET_ID, tweet_fields=["public_metrics", "text", "author_id"]) except Exception as e: body = e.response.text if hasattr(e, "response") and e.response else "" print(f"ERROR: {body or e}"); sys.exit(1) tweet = resp.data if hasattr(resp, "data") else resp if not tweet: print("ERROR: Tweet not found"); sys.exit(1) # xdk may return a dict or an object — handle both text = tweet.get("text", "") if isinstance(tweet, dict) else (tweet.text or "") metrics = tweet.get("public_metrics", {}) if isinstance(tweet, dict) else (tweet.public_metrics or {}) m = metrics if isinstance(metrics, dict) else {} print(f"\n── Tweet Metrics Report / 推文互动报告 ──") print(f"ID : {TWEET_ID}") print(f"Text : {text[:120]}") print(f"Likes : {m.get('like_count', 'N/A')}") print(f"Retweets : {m.get('retweet_count', 'N/A')}") print(f"Replies : {m.get('reply_count', 'N/A')}") print(f"Quotes : {m.get('quote_count', 'N/A')}") print(f"Impressions : {m.get('impression_count', 'N/A')}") if __name__ == "__main__": main()
""" S06 · Tweet Metrics Report — HTTP REST API (LocalBridge) EN: Fetch tweet engagement metrics via LocalBridge GET /api/v1/x/tweets. Metrics live in the GraphQL response under the "legacy" key. pip install requests """ import sys, requests LOCAL_BRIDGE = "http://127.0.0.1:20088" TWEET_ID = "1234567890123456789" # ← Replace with target tweet ID / 替换为目标推文 ID def get_tweet(tweet_id): # GET /api/v1/x/tweets?tweetId=<id> # Response: {"success": true, "data": {"data": {"threaded_conversation_with_injections_v2": {...}}}} try: resp = requests.get(f"{LOCAL_BRIDGE}/api/v1/x/tweets", params={"tweetId": tweet_id}, timeout=15) resp.raise_for_status() except requests.exceptions.ConnectionError: print("ERROR: TweetPilot not running"); sys.exit(1) except requests.exceptions.HTTPError as e: print(f"ERROR: {e.response.status_code}"); sys.exit(1) return resp.json().get("data", {}).get("data", {}) def main(): print(f"Fetching tweet {TWEET_ID}...") data = get_tweet(TWEET_ID) if not data: print("ERROR: Tweet not found"); sys.exit(1) # Navigate threaded_conversation → instructions → first TimelineTweet entry instructions = (data.get("threaded_conversation_with_injections_v2", {}) .get("instructions", [])) tweet = None for instr in instructions: for entry in instr.get("entries", []): item = entry.get("content", {}).get("itemContent", {}) if item.get("itemType") == "TimelineTweet": r = item.get("tweet_results", {}).get("result", {}) tweet = r.get("tweet") or r break if tweet: break if not tweet: print("ERROR: Could not parse tweet data"); sys.exit(1) legacy = tweet.get("legacy", {}) user_result = tweet.get("core", {}).get("user_results", {}).get("result", {}) screen_name = (user_result.get("core", {}).get("screen_name") or user_result.get("legacy", {}).get("screen_name", "unknown")) print(f"\n── Tweet Metrics Report / 推文互动报告 ──") print(f"ID : {TWEET_ID}") print(f"Author : @{screen_name}") print(f"Text : {legacy.get('full_text', '')[:120]}") print(f"Likes : {legacy.get('favorite_count', 'N/A')}") print(f"Retweets : {legacy.get('retweet_count', 'N/A')}") print(f"Replies : {legacy.get('reply_count', 'N/A')}") print(f"Quotes : {legacy.get('quote_count', 'N/A')}") print(f"Bookmarks : {legacy.get('bookmark_count', 'N/A')}") if __name__ == "__main__": main()
# Fetch tweet metrics via LocalBridge / 获取推文互动数据(替换 TWEET_ID)
curl -s "http://127.0.0.1:20088/api/v1/x/tweets?tweetId=TWEET_ID" | \
python3 -c "
import json,sys
d=json.load(sys.stdin).get('data',{}).get('data',{})
r=d.get('tweetResult',{}).get('result',{})
t=r.get('tweet') or r
lg=t.get('legacy',{})
print('Likes:',lg.get('favorite_count'))
print('Retweets:',lg.get('retweet_count'))
print('Replies:',lg.get('reply_count'))
"
/api/v1/feishu/send endpoint is only accessible locally — it listens on 127.0.0.1 at the REST API port you set in TweetPilot's system settings (default 20088). Remote or public IPs will be rejected./api/v1/feishu/send 仅监听本机 127.0.0.1 + 系统设置中配置的 REST API 端口(默认 20088),外部 IP 访问无效。
""" S07 · Mention Monitor + Feishu Alert — ClawBot SDK EN: Search recent @mentions via TweetClaw, send Feishu alert for new ones. Uses TweetPilot's built-in Feishu channel — no Webhook URL needed. Stores last-seen tweet ID locally so only new mentions are reported. 中文:通过 TweetClaw 搜索最新提及,通过 TweetPilot 内置飞书通道推送新提及。 无需配置飞书 Webhook,每次只上报新提及。 pip install requests (clawbot pre-installed via TweetPilot) """ import sys, requests from pathlib import Path from clawbot import ClawBotClient LOCAL_BRIDGE = "http://127.0.0.1:20088" SCREEN_NAME = "your_handle" # ← Your Twitter handle (no @) / 你的用户名(不含 @) STATE_FILE = Path.home() / ".tweetpilot" / "s07_last_mention_id.txt" def load_last_id(): try: return STATE_FILE.read_text().strip() or None except FileNotFoundError: return None def save_last_id(tid): STATE_FILE.parent.mkdir(parents=True, exist_ok=True) STATE_FILE.write_text(tid) def send_feishu(text, tweet_id, author): url = f"https://x.com/{author}/status/{tweet_id}" msg = f"🔔 New mention\n@{author}: {text[:200]}\n{url}" # POST /api/v1/feishu/send — TweetPilot built-in Feishu channel, no Webhook needed r = requests.post(f"{LOCAL_BRIDGE}/api/v1/feishu/send", json={"text": msg}, timeout=10) r.raise_for_status() if not r.json().get("ok"): raise RuntimeError(f"Feishu send failed: {r.json()}") def main(): if SCREEN_NAME == "your_handle": print("ERROR: Set SCREEN_NAME to your Twitter handle."); sys.exit(1) client = ClawBotClient() instances = client.x.status.get_instances() instance_id = None if isinstance(instances, list) and instances: instance_id = instances[0].get("instanceId") or instances[0].get("id") last_id = load_last_id() print(f"Searching @{SCREEN_NAME} mentions (since_id={last_id or 'none'})...") try: # search_tweets returns newest-first results for the query tweets = client.x.tweets.search_tweets( query=f"@{SCREEN_NAME}", count=20, instance_id=instance_id) except Exception as e: print(f"ERROR: {e}"); sys.exit(1) # Keep only tweets newer than last seen ID new_tweets = [t for t in tweets if not last_id or t.id > last_id] if not new_tweets: print("No new mentions / 无新提及"); return print(f"Found {len(new_tweets)} new mention(s) / 发现 {len(new_tweets)} 条新提及") for t in reversed(new_tweets): author = getattr(t, "author_screen_name", "unknown") or "unknown" text = getattr(t, "text", "") or "" print(f" @{author}: {text[:80]!r}") try: send_feishu(text, t.id, author) except Exception as e: print(f" Feishu failed: {e}") save_last_id(new_tweets[0].id) print(f"Done / 完成. Last ID: {new_tweets[0].id}") if __name__ == "__main__": main()
""" S07 · Mention Monitor + Feishu Alert — xdk SDK EN: Poll recent mentions via Twitter API v2, send Feishu alert for new ones. Uses TweetPilot's built-in Feishu channel — no Webhook URL needed. Passes since_id so only mentions newer than last run are fetched. Requires OAuth account authorized in TweetPilot. 中文:通过 Twitter API v2 轮询最新提及,通过 TweetPilot 内置飞书通道推送新提及。 无需配置飞书 Webhook,传入 since_id 只获取上次运行后的新提及。 pip install xdk requests """ import sys, requests from pathlib import Path from xdk import Client LOCAL_BRIDGE = "http://127.0.0.1:20088" TWITTER_ID = "YOUR_TWITTER_ID" # ← OAuth account numeric ID / OAuth 账号数字 ID STATE_FILE = Path.home() / ".tweetpilot" / "s07_last_mention_id.txt" def get_access_token(twitter_id): try: resp = requests.post(f"{LOCAL_BRIDGE}/api/v1/x/oauth/access-token", json={"twitter_id": twitter_id}, timeout=10) resp.raise_for_status() return resp.json()["access_token"] except requests.exceptions.ConnectionError: print("ERROR: TweetPilot not running"); sys.exit(1) except requests.exceptions.HTTPError as e: print(f"ERROR: {e.response.status_code} — {e.response.text}"); sys.exit(1) def load_last_id(): try: return STATE_FILE.read_text().strip() or None except FileNotFoundError: return None def save_last_id(tid): STATE_FILE.parent.mkdir(parents=True, exist_ok=True) STATE_FILE.write_text(tid) def send_feishu(text, tweet_id): url = f"https://x.com/i/web/status/{tweet_id}" msg = f"🔔 New mention\n{text[:200]}\n{url}" # POST /api/v1/feishu/send — TweetPilot built-in Feishu channel, no Webhook needed r = requests.post(f"{LOCAL_BRIDGE}/api/v1/feishu/send", json={"text": msg}, timeout=10) r.raise_for_status() if not r.json().get("ok"): raise RuntimeError(f"Feishu send failed: {r.json()}") def main(): if TWITTER_ID == "YOUR_TWITTER_ID": print("ERROR: Set TWITTER_ID to your numeric Twitter ID."); sys.exit(1) # Must use access_token= (User OAuth 2.0), NOT bearer_token= (read-only) client = Client(access_token=get_access_token(TWITTER_ID)) last_id = load_last_id() print(f"Fetching mentions for {TWITTER_ID} (since_id={last_id or 'none'})...") new_tweets = [] try: # get_mentions returns newest-first paginated mention timeline # Pass since_id to only fetch mentions newer than the last run for page in client.users.get_mentions( id=TWITTER_ID, max_results=20, since_id=last_id, tweet_fields=["text", "author_id"]): new_tweets.extend(page.data or []) break # one page per run is enough except Exception as e: body = e.response.text if hasattr(e, "response") and e.response else "" print(f"ERROR: {body or e}"); sys.exit(1) if not new_tweets: print("No new mentions / 无新提及"); return print(f"Found {len(new_tweets)} new mention(s) / 发现 {len(new_tweets)} 条新提及") for t in reversed(new_tweets): # xdk get_mentions returns dicts — use dict access text = t.get("text", "") if isinstance(t, dict) else (t.text or "") tweet_id = t.get("id") if isinstance(t, dict) else t.id print(f" {tweet_id}: {text[:80]!r}") try: send_feishu(text, tweet_id) except Exception as e: print(f" Feishu failed: {e}") first = new_tweets[0] newest_id = first.get("id") if isinstance(first, dict) else first.id save_last_id(newest_id) print(f"Done / 完成. Last ID: {newest_id}") if __name__ == "__main__": main()
""" S07 · Mention Monitor + Feishu Alert — HTTP REST API (LocalBridge) EN: Search @mentions via LocalBridge, send Feishu alert for new ones. Uses TweetPilot's built-in Feishu channel — no Webhook URL needed. Stores last-seen tweet ID locally so only new mentions are reported. pip install requests """ import sys, requests from pathlib import Path LOCAL_BRIDGE = "http://127.0.0.1:20088" SCREEN_NAME = "your_handle" # ← Your Twitter handle (no @) / 你的用户名(不含 @) STATE_FILE = Path.home() / ".tweetpilot" / "s07_last_mention_id.txt" def load_last_id(): try: return STATE_FILE.read_text().strip() or None except FileNotFoundError: return None def save_last_id(tid): STATE_FILE.parent.mkdir(parents=True, exist_ok=True) STATE_FILE.write_text(tid) def search_mentions(query): # GET /api/v1/x/search?query=@handle&count=20 # Response: {"success": true, "data": {"data": {...GraphQL...}}} try: resp = requests.get(f"{LOCAL_BRIDGE}/api/v1/x/search", params={"query": query, "count": 20}, timeout=15) resp.raise_for_status() except requests.exceptions.ConnectionError: print("ERROR: TweetPilot not running"); sys.exit(1) except requests.exceptions.HTTPError as e: print(f"ERROR: {e.response.status_code}"); sys.exit(1) tweets = [] try: instructions = (resp.json().get("data", {}).get("data", {}) .get("search_by_raw_query", {}).get("search_timeline", {}) .get("timeline", {}).get("instructions", [])) for instr in instructions: for entry in instr.get("entries", []): r = entry.get("content", {}).get("itemContent", {}).get("tweet_results", {}).get("result", {}) if not r: continue t = r.get("tweet") or r rid = t.get("rest_id") or r.get("rest_id") sn = (t.get("core", {}).get("user_results", {}) .get("result", {}).get("core", {}).get("screen_name", "unknown")) if rid: tweets.append({"id": rid, "text": t.get("legacy", {}).get("full_text", ""), "author_screen_name": sn}) except Exception as e: print(f"WARNING: parse error: {e}") return tweets def send_feishu(text, tweet_id, author): url = f"https://x.com/{author}/status/{tweet_id}" msg = f"🔔 New mention\n@{author}: {text[:200]}\n{url}" # POST /api/v1/feishu/send — TweetPilot built-in Feishu channel, no Webhook needed r = requests.post(f"{LOCAL_BRIDGE}/api/v1/feishu/send", json={"text": msg}, timeout=10) r.raise_for_status() if not r.json().get("ok"): raise RuntimeError(f"Feishu send failed: {r.json()}") def main(): if SCREEN_NAME == "your_handle": print("ERROR: Set SCREEN_NAME to your Twitter handle."); sys.exit(1) last_id = load_last_id() print(f"Searching @{SCREEN_NAME} mentions (since_id={last_id or 'none'})...") tweets = search_mentions(f"@{SCREEN_NAME}") new_tweets = [t for t in tweets if not last_id or t["id"] > last_id] if not new_tweets: print("No new mentions / 无新提及"); return print(f"Found {len(new_tweets)} new mention(s) / 发现 {len(new_tweets)} 条新提及") for t in reversed(new_tweets): print(f" @{t['author_screen_name']}: {t['text'][:80]!r}") try: send_feishu(t["text"], t["id"], t["author_screen_name"]) except Exception as e: print(f" Feishu failed: {e}") save_last_id(new_tweets[0]["id"]) print(f"Done / 完成. Last ID: {new_tweets[0]['id']}") if __name__ == "__main__": main()
# Search @mentions via LocalBridge / 搜索提及(替换 your_handle) curl -s "http://127.0.0.1:20088/api/v1/x/search?query=%40your_handle&count=20" # Send Feishu alert via TweetPilot built-in channel / 通过 TweetPilot 内置飞书通道发送通知 curl -s -X POST "http://127.0.0.1:20088/api/v1/feishu/send" \ -H "Content-Type: application/json" \ -d '{"text":"🔔 New mention: MESSAGE"}'
""" S08 · Retweet Trending Topics — ClawBot SDK ============================================ EN: Search for high-engagement tweets on a given topic via TweetClaw, sort by likes, then retweet the top N results. No trending API needed — uses keyword search + engagement filtering. 中文:通过 TweetClaw 搜索指定话题的高互动推文, 按点赞数排序后转推前 N 条。 无需 trending API,以关键词搜索 + 互动数过滤实现。 Requirements / 依赖: pip install requests TweetPilot running + TweetClaw browser extension online """ import sys import time from clawbot import ClawBotClient # ── Config / 配置 ──────────────────────────────────────────────────── # EN: Topic keyword or hashtag to search. # 中文:搜索关键词或 hashtag。 TOPIC = "#AI" # ← Replace / 替换为你想搜索的话题 # EN: Only retweet tweets with at least this many likes. # 中文:仅转推点赞数不低于此值的推文。 MIN_LIKES = 100 # ← Minimum likes threshold / 最低点赞数 # EN: Maximum number of tweets to retweet per run. # 中文:每次最多转推条数。 TOP_N = 3 # ← Max retweets per run / 每次最多转推数 # EN: Seconds to wait between retweets (avoid rate limiting). # 中文:每次转推之间的等待秒数(避免频率限制)。 DELAY_SECONDS = 3 def parse_search_raw(raw: dict) -> list: """ EN: Parse raw LocalBridge search response into a flat list of tweet dicts. Each dict has: id, text, author_screen_name, likes. 中文:解析 LocalBridge 搜索原始响应,返回推文字典列表。 每条含 id、text、author_screen_name、likes。 """ tweets = [] try: instructions = ( raw.get("data", {}) .get("data", {}) .get("search_by_raw_query", {}) .get("search_timeline", {}) .get("timeline", {}) .get("instructions", []) ) for instruction in instructions: for entry in instruction.get("entries", []): result = ( entry.get("content", {}) .get("itemContent", {}) .get("tweet_results", {}) .get("result", {}) ) if not result: continue # EN: Handle TweetWithVisibilityResults wrapper. # 中文:兼容 TweetWithVisibilityResults 包装格式。 tweet = result.get("tweet") or result rest_id = tweet.get("rest_id") or result.get("rest_id") legacy = tweet.get("legacy", {}) text = legacy.get("full_text", "") likes = legacy.get("favorite_count", 0) screen_name = ( tweet.get("core", {}) .get("user_results", {}) .get("result", {}) .get("core", {}) .get("screen_name", "unknown") ) if rest_id: tweets.append({ "id": rest_id, "text": text, "author_screen_name": screen_name, "likes": likes, }) except Exception as e: print(f"WARNING: Could not parse search results: {e}") return tweets def main(): client = ClawBotClient() # EN: Auto-select first connected TweetClaw instance. # 中文:自动选择第一个已连接的 TweetClaw 实例。 instances = client.x.status.get_instances() instance_id = None if isinstance(instances, list) and instances: instance_id = instances[0].get("instanceId") or instances[0].get("id") print(f"Searching tweets for topic {TOPIC!r}...") try: raw = client.x.tweets.transport.search_raw( query=TOPIC, count=20, instance_id=instance_id, ) except Exception as e: print(f"ERROR: Search failed: {e}") sys.exit(1) tweets = parse_search_raw(raw) if not tweets: print("No tweets found / 未找到推文") return # EN: Sort by likes descending, then apply MIN_LIKES filter. # 中文:按点赞数降序排序,再过滤低于门槛的推文。 sorted_tweets = sorted(tweets, key=lambda t: t["likes"], reverse=True) top_tweets = [t for t in sorted_tweets if t["likes"] >= MIN_LIKES][:TOP_N] if not top_tweets: print(f"No tweets with >= {MIN_LIKES} likes / 没有点赞数达到 {MIN_LIKES} 的推文") return print(f"Retweeting top {len(top_tweets)} tweet(s) / 转推前 {len(top_tweets)} 条...") for t in top_tweets: print(f" [{t['likes']} likes] @{t['author_screen_name']}: {t['text'][:80]!r}") try: client.x.actions.retweet(tweet_id=t["id"], instance_id=instance_id) print(f" ✓ Retweeted / 已转推 {t['id']}") except Exception as e: print(f" Retweet failed: {e}") time.sleep(DELAY_SECONDS) print("Done / 完成") if __name__ == "__main__": main()
""" S08 · Retweet Trending Topics — xdk SDK ========================================= EN: Search for high-engagement tweets on a given topic via Twitter API v2, sort by public metrics, then retweet the top N results. TweetPilot provides the OAuth access token automatically. 中文:通过 Twitter API v2 搜索指定话题的高互动推文, 按互动数排序后转推前 N 条。 TweetPilot 自动提供 OAuth access token。 Requirements / 依赖: pip install xdk requests TweetPilot running + OAuth account authorized in Account Settings """ import sys import time import requests from xdk import Client # ── Config / 配置 ──────────────────────────────────────────────────── LOCAL_BRIDGE = "http://127.0.0.1:20088" TWITTER_ID = "YOUR_TWITTER_ID" # ← OAuth account numeric ID / OAuth 账号数字 ID # EN: Topic keyword or hashtag to search. # 中文:搜索关键词或 hashtag。 TOPIC = "#AI" # ← Replace / 替换为你想搜索的话题 # EN: Only retweet tweets with at least this many likes. # 中文:仅转推点赞数不低于此值的推文。 MIN_LIKES = 100 # ← Minimum likes threshold / 最低点赞数 # EN: Maximum number of tweets to retweet per run. # 中文:每次最多转推条数。 TOP_N = 3 # ← Max retweets per run / 每次最多转推数 # EN: Seconds to wait between retweets (avoid rate limiting). # 中文:每次转推之间的等待秒数(避免频率限制)。 DELAY_SECONDS = 3 def get_access_token(twitter_id: str) -> str: try: resp = requests.post( f"{LOCAL_BRIDGE}/api/v1/x/oauth/access-token", json={"twitter_id": twitter_id}, timeout=10, ) resp.raise_for_status() return resp.json()["access_token"] except requests.exceptions.ConnectionError: print("ERROR: TweetPilot not running / TweetPilot 未在运行") sys.exit(1) except requests.exceptions.HTTPError as e: print(f"ERROR: {e.response.status_code} — {e.response.text}") sys.exit(1) def get_likes(tweet) -> int: """Extract like count from tweet dict or object.""" if isinstance(tweet, dict): m = tweet.get("public_metrics") or {} return m.get("like_count", 0) m = getattr(tweet, "public_metrics", None) or {} return m.get("like_count", 0) if isinstance(m, dict) else 0 def main(): if TWITTER_ID == "YOUR_TWITTER_ID": print("ERROR: Set TWITTER_ID to your numeric Twitter ID."); sys.exit(1) # EN: Must use access_token= (User OAuth 2.0), NOT bearer_token=. # 中文:必须用 access_token= 传入,bearer_token= 是只读的 App token。 client = Client(access_token=get_access_token(TWITTER_ID)) print(f"Searching tweets for topic {TOPIC!r}...") tweets = [] try: for page in client.posts.search_recent( query=TOPIC, max_results=20, tweet_fields=["public_metrics", "text", "author_id"], ): tweets.extend(page.data or []) break # EN: One page per run is enough. / 每次运行取一页即可。 except Exception as e: err_body = getattr(getattr(e, "response", None), "text", "") or str(e) print(f"ERROR: Search failed: {err_body}") sys.exit(1) if not tweets: print("No tweets found / 未找到推文") return # EN: Sort by likes descending, then apply MIN_LIKES filter. # 中文:按点赞数降序排序,再过滤低于门槛的推文。 sorted_tweets = sorted(tweets, key=get_likes, reverse=True) top_tweets = [t for t in sorted_tweets if get_likes(t) >= MIN_LIKES][:TOP_N] if not top_tweets: print(f"No tweets with >= {MIN_LIKES} likes / 没有点赞数达到 {MIN_LIKES} 的推文") return print(f"Retweeting top {len(top_tweets)} tweet(s) / 转推前 {len(top_tweets)} 条...") for t in top_tweets: tweet_id = t.get("id") if isinstance(t, dict) else t.id text = (t.get("text", "") if isinstance(t, dict) else getattr(t, "text", "") or "")[:80] likes = get_likes(t) print(f" [{likes} likes] {tweet_id}: {text!r}") try: # EN: repost_post uses body with tweet_id. # 中文:repost_post 需要 body 中包含 tweet_id。 client.users.repost_post( id=TWITTER_ID, body={"tweet_id": tweet_id}, ) print(f" ✓ Retweeted / 已转推 {tweet_id}") except Exception as e: err_body = getattr(getattr(e, "response", None), "text", "") or str(e) print(f" Retweet failed: {err_body}") time.sleep(DELAY_SECONDS) print("Done / 完成") if __name__ == "__main__": main()
""" S08 · Retweet Trending Topics — HTTP REST API (LocalBridge) ============================================================= EN: Search for high-engagement tweets on a given topic via LocalBridge, sort by likes, then retweet the top N results. Uses the browser extension as proxy — no OAuth token needed. 中文:通过 LocalBridge 搜索指定话题的高互动推文, 按点赞数排序后转推前 N 条。 以浏览器扩展作为代理,无需 OAuth token。 Requirements / 依赖: pip install requests TweetPilot running + TweetClaw browser extension online """ import sys import time import requests # ── Config / 配置 ──────────────────────────────────────────────────── LOCAL_BRIDGE = "http://127.0.0.1:20088" # EN: Topic keyword or hashtag to search. # 中文:搜索关键词或 hashtag。 TOPIC = "#AI" # ← Replace / 替换为你想搜索的话题 # EN: Only retweet tweets with at least this many likes. # 中文:仅转推点赞数不低于此值的推文。 MIN_LIKES = 100 # ← Minimum likes threshold / 最低点赞数 # EN: Maximum number of tweets to retweet per run. # 中文:每次最多转推条数。 TOP_N = 3 # ← Max retweets per run / 每次最多转推数 # EN: Seconds to wait between retweets (avoid rate limiting). # 中文:每次转推之间的等待秒数(避免频率限制)。 DELAY_SECONDS = 3 def search_tweets(query: str) -> list: """ EN: Search tweets via LocalBridge GET /api/v1/x/search. Returns a list of tweet dicts with id, text, author_screen_name, and likes. 中文:通过 LocalBridge GET /api/v1/x/search 搜索推文。 返回含 id、text、author_screen_name、likes 的推文字典列表。 """ try: resp = requests.get( f"{LOCAL_BRIDGE}/api/v1/x/search", params={"query": query, "count": 20}, timeout=15, ) resp.raise_for_status() except requests.exceptions.ConnectionError: print("ERROR: TweetPilot not running / TweetPilot 未在运行") sys.exit(1) except requests.exceptions.HTTPError as e: print(f"ERROR: {e.response.status_code} — {e.response.text}") sys.exit(1) tweets = [] try: # EN: LocalBridge wraps response: {"success": true, "data": {"data": {...GraphQL...}}} # 中文:LocalBridge 包装响应:{"success": true, "data": {"data": {...GraphQL...}}} instructions = ( resp.json() .get("data", {}) .get("data", {}) .get("search_by_raw_query", {}) .get("search_timeline", {}) .get("timeline", {}) .get("instructions", []) ) for instruction in instructions: for entry in instruction.get("entries", []): result = ( entry.get("content", {}) .get("itemContent", {}) .get("tweet_results", {}) .get("result", {}) ) if not result: continue # EN: Handle TweetWithVisibilityResults wrapper. # 中文:兼容 TweetWithVisibilityResults 包装格式。 tweet = result.get("tweet") or result rest_id = tweet.get("rest_id") or result.get("rest_id") legacy = tweet.get("legacy", {}) text = legacy.get("full_text", "") likes = legacy.get("favorite_count", 0) screen_name = ( tweet.get("core", {}) .get("user_results", {}) .get("result", {}) .get("core", {}) .get("screen_name", "unknown") ) if rest_id: tweets.append({ "id": rest_id, "text": text, "author_screen_name": screen_name, "likes": likes, }) except Exception as e: print(f"WARNING: Could not parse search results: {e}") return tweets def retweet(tweet_id: str) -> None: """ EN: Retweet via LocalBridge POST /api/v1/x/retweets. 中文:通过 LocalBridge POST /api/v1/x/retweets 转推。 """ resp = requests.post( f"{LOCAL_BRIDGE}/api/v1/x/retweets", json={"tweetId": tweet_id}, timeout=15, ) resp.raise_for_status() def main(): print(f"Searching tweets for topic {TOPIC!r}...") tweets = search_tweets(TOPIC) if not tweets: print("No tweets found / 未找到推文") return # EN: Sort by likes descending, then apply MIN_LIKES filter. # 中文:按点赞数降序排序,再过滤低于门槛的推文。 sorted_tweets = sorted(tweets, key=lambda t: t["likes"], reverse=True) top_tweets = [t for t in sorted_tweets if t["likes"] >= MIN_LIKES][:TOP_N] if not top_tweets: print(f"No tweets with >= {MIN_LIKES} likes / 没有点赞数达到 {MIN_LIKES} 的推文") return print(f"Retweeting top {len(top_tweets)} tweet(s) / 转推前 {len(top_tweets)} 条...") for t in top_tweets: print(f" [{t['likes']} likes] @{t['author_screen_name']}: {t['text'][:80]!r}") try: retweet(t["id"]) print(f" ✓ Retweeted / 已转推 {t['id']}") except Exception as e: print(f" Retweet failed: {e}") time.sleep(DELAY_SECONDS) print("Done / 完成") if __name__ == "__main__": main()
# Search trending tweets via LocalBridge / 搜索热点推文(替换 %23AI 为你的话题,%23 = #) curl -s "http://127.0.0.1:20088/api/v1/x/search?query=%23AI&count=20" # Retweet a tweet via LocalBridge / 转推推文(替换 TWEET_ID) curl -s -X POST "http://127.0.0.1:20088/api/v1/x/retweets" \ -H "Content-Type: application/json" \ -d '{"tweetId":"TWEET_ID"}'