Repository: resciencelab/opc-skills
Low Risk
No security issues found
Skill manifest does not include a 'license' field. Specifying a license helps users understand usage terms.
Remediation Add 'license' field to SKILL.md frontmatter (e.g., MIT, Apache-2.0)
Description
Search and retrieve content from Twitter/X. Get user info, tweets, replies, followers, communities, spaces, and trends via twitterapi.io. Use when user mentions Twitter, X, or tweets.
Skill Files
# Twitter/X Skill Get user profiles, tweets, replies, followers/following, communities, spaces, and trends from Twitter/X via twitterapi.io. ## Prerequisites Set API key in `~/.zshrc`: ```bash export TWITTERAPI_API_KEY="your_api_key" ``` **Quick Check**: ```bash cd <skill_directory> python3 scripts/get_user_info.py elonmusk ``` ## Commands All commands run from the skill directory. ### User Endpoints ```bash python3 scripts/get_user_info.py USERNAME python3 scripts/get_user_about.py USERNAME python3 scripts/batch_get_users.py USER_ID1,USER_ID2 python3 scripts/get_user_tweets.py USERNAME --limit 20 python3 scripts/get_user_mentions.py USERNAME --limit 20 python3 scripts/get_followers.py USERNAME --limit 100 python3 scripts/get_following.py USERNAME --limit 100 python3 scripts/get_verified_followers.py USERNAME --limit 20 python3 scripts/check_relationship.py USER1 USER2 python3 scripts/search_users.py "query" --limit 20 ``` ### Tweet Endpoints ```bash python3 scripts/get_tweet.py TWEET_ID [TWEET_ID2...] python3 scripts/search_tweets.py "query" --type Latest --limit 20 python3 scripts/get_tweet_replies.py TWEET_ID --limit 20 python3 scripts/get_tweet_quotes.py TWEET_ID --limit 20 python3 scripts/get_tweet_retweeters.py TWEET_ID --limit 50 python3 scripts/get_tweet_thread.py TWEET_ID python3 scripts/get_article.py TWEET_ID ``` ### List Endpoints ```bash python3 scripts/get_list_followers.py LIST_ID --limit 20 python3 scripts/get_list_members.py LIST_ID --limit 20 ``` ### Community Endpoints ```bash python3 scripts/get_community.py COMMUNITY_ID python3 scripts/get_community_members.py COMMUNITY_ID --limit 20 python3 scripts/get_community_moderators.py COMMUNITY_ID python3 scripts/get_community_tweets.py COMMUNITY_ID --limit 20 python3 scripts/search_community_tweets.py "query" --limit 20 ``` ### Other Endpoints ```bash python3 scripts/get_space.py SPACE_ID python3 scripts/get_trends.py --woeid 1 # Worldwide ``` ## Search Query Syntax ```bash # Basic search python3 scripts/search_tweets.py "AI agent" # From specific user python3 scripts/search_tweets.py "from:elonmusk" # Date range python3 scripts/search_tweets.py "AI since:2024-01-01 until:2024-12-31" # Exclude retweets python3 scripts/search_tweets.py "AI -filter:retweets" # With media python3 scripts/search_tweets.py "AI filter:media" # Minimum engagement python3 scripts/search_tweets.py "AI min_faves:1000" ``` ## API: twitterapi.io - Base URL: https://api.twitterapi.io/twitter - Auth: X-API-Key header - Pricing: ~$0.15-0.18/1k requests - Docs: https://docs.twitterapi.io/
{
"name": "twitter",
"version": "1.0.0",
"description": "Search and retrieve content from Twitter/X. Get user info, tweets, replies, followers, communities, spaces, and trends via twitterapi.io.",
"author": {
"name": "ReScienceLab"
},
"homepage": "https://github.com/ReScienceLab/opc-skills/tree/main/skills/twitter",
"repository": "https://github.com/ReScienceLab/opc-skills",
"license": "MIT",
"keywords": [
"twitter",
"X",
"tweet"
],
"skills": [
"./SKILL.md"
],
"commands": [
"./scripts/"
]
}
#!/usr/bin/env python3
"""
Batch get user info by user IDs
Usage: python3 scripts/batch_get_users.py USER_ID1,USER_ID2,USER_ID3
"""
import argparse
from twitter_api import api_get, print_users_list
def main():
parser = argparse.ArgumentParser(description="Batch get Twitter users by IDs")
parser.add_argument("user_ids", help="Comma-separated user IDs")
args = parser.parse_args()
data = api_get("user/batch_info_by_ids", {"userIds": args.user_ids})
users = data.get("users") or data.get("data") or []
print(f"total: {len(users)}")
print_users_list(users)
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""
Check follow relationship between two users
Usage: python3 scripts/check_relationship.py USER1 USER2
"""
import argparse
from twitter_api import api_get
def main():
parser = argparse.ArgumentParser(description="Check follow relationship")
parser.add_argument("source", help="Source username")
parser.add_argument("target", help="Target username")
args = parser.parse_args()
params = {"source_user_name": args.source, "target_user_name": args.target}
data = api_get("user/check_follow_relationship", params)
result = data.get("data") or data
print(f"source: @{args.source}")
print(f"target: @{args.target}")
print(f"source_follows_target: {result.get('following', False)}")
print(f"target_follows_source: {result.get('followed_by', False)}")
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""
Twitter API credential management.
Reads from environment: TWITTERAPI_API_KEY
"""
import os
def get_twitter_api_key() -> str | None:
"""Get TwitterAPI.io API key"""
return os.environ.get("TWITTERAPI_API_KEY")
def has_twitter_key() -> bool:
"""Check if API key is available"""
return get_twitter_api_key() is not None
#!/usr/bin/env python3
"""
Get long-form article from tweet
Usage: python3 scripts/get_article.py TWEET_ID
"""
import argparse
import json
from twitter_api import api_get
def main():
parser = argparse.ArgumentParser(description="Get Twitter article")
parser.add_argument("tweet_id", help="Tweet ID with article")
parser.add_argument("--json", "-j", action="store_true", help="Output as JSON")
args = parser.parse_args()
data = api_get("article", {"tweet_id": args.tweet_id})
article = data.get("data") or data
if args.json:
print(json.dumps(article, indent=2))
return
print(f"tweet_id: {args.tweet_id}")
if article.get("title"):
print(f"title: {article['title']}")
if article.get("content"):
print(f"---")
print(article["content"][:2000])
if len(article.get("content", "")) > 2000:
print(f"... (truncated, {len(article['content'])} chars total)")
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""
Get community info by ID
Usage: python3 scripts/get_community.py COMMUNITY_ID
"""
import argparse
import json
from twitter_api import api_get, format_count
def main():
parser = argparse.ArgumentParser(description="Get community info")
parser.add_argument("community_id", help="Community ID")
parser.add_argument("--json", "-j", action="store_true", help="Output as JSON")
args = parser.parse_args()
data = api_get("community", {"communityId": args.community_id})
community = data.get("data") or data
if args.json:
print(json.dumps(community, indent=2))
return
print(f"id: {community.get('id', '')}")
print(f"name: {community.get('name', '')}")
print(f"description: {community.get('description', '')[:200]}")
print(f"members: {format_count(community.get('member_count'))}")
print(f"created: {community.get('created_at', '')}")
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""
Get community members
Usage: python3 scripts/get_community_members.py COMMUNITY_ID --limit 20
"""
import argparse
from twitter_api import api_get, print_users_list, print_pagination
def main():
parser = argparse.ArgumentParser(description="Get community members")
parser.add_argument("community_id", help="Community ID")
parser.add_argument("--limit", "-l", type=int, default=20, help="Max members")
parser.add_argument("--cursor", "-c", help="Pagination cursor")
args = parser.parse_args()
params = {"communityId": args.community_id, "cursor": args.cursor}
data = api_get("community/members", params)
users = (data.get("members") or data.get("users") or [])[:args.limit]
print(f"community_id: {args.community_id}")
print_users_list(users, "members")
print_pagination(data)
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""
Get community moderators
Usage: python3 scripts/get_community_moderators.py COMMUNITY_ID
"""
import argparse
from twitter_api import api_get, print_users_list, print_pagination
def main():
parser = argparse.ArgumentParser(description="Get community moderators")
parser.add_argument("community_id", help="Community ID")
parser.add_argument("--cursor", "-c", help="Pagination cursor")
args = parser.parse_args()
params = {"communityId": args.community_id, "cursor": args.cursor}
data = api_get("community/moderators", params)
users = data.get("moderators") or data.get("users") or []
print(f"community_id: {args.community_id}")
print_users_list(users, "moderators")
print_pagination(data)
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""
Get community tweets
Usage: python3 scripts/get_community_tweets.py COMMUNITY_ID --limit 20
"""
import argparse
from twitter_api import api_get, print_tweets_list, print_pagination
def main():
parser = argparse.ArgumentParser(description="Get community tweets")
parser.add_argument("community_id", help="Community ID")
parser.add_argument("--limit", "-l", type=int, default=20, help="Max tweets")
parser.add_argument("--cursor", "-c", help="Pagination cursor")
args = parser.parse_args()
params = {"communityId": args.community_id, "cursor": args.cursor}
data = api_get("community/tweets", params)
tweets = (data.get("tweets") or [])[:args.limit]
print(f"community_id: {args.community_id}")
print_tweets_list(tweets)
print_pagination(data)
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""
Get user's followers
Usage: python3 scripts/get_followers.py USERNAME --limit 100
"""
import argparse
from twitter_api import api_get, print_users_list, print_pagination
def main():
parser = argparse.ArgumentParser(description="Get user's followers")
parser.add_argument("username", help="Twitter username (without @)")
parser.add_argument("--limit", "-l", type=int, default=100, help="Max followers (max 200/page)")
parser.add_argument("--cursor", "-c", help="Pagination cursor")
args = parser.parse_args()
params = {
"userName": args.username,
"cursor": args.cursor,
"pageSize": min(args.limit, 200),
}
data = api_get("user/followers", params)
users = (data.get("followers") or data.get("users") or [])[:args.limit]
print(f"username: @{args.username}")
print_users_list(users, "followers")
print_pagination(data)
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""
Get user's following
Usage: python3 scripts/get_following.py USERNAME --limit 100
"""
import argparse
from twitter_api import api_get, print_users_list, print_pagination
def main():
parser = argparse.ArgumentParser(description="Get user's following")
parser.add_argument("username", help="Twitter username (without @)")
parser.add_argument("--limit", "-l", type=int, default=100, help="Max following (max 200/page)")
parser.add_argument("--cursor", "-c", help="Pagination cursor")
args = parser.parse_args()
params = {
"userName": args.username,
"cursor": args.cursor,
"pageSize": min(args.limit, 200),
}
data = api_get("user/followings", params)
users = (data.get("followings") or data.get("users") or [])[:args.limit]
print(f"username: @{args.username}")
print_users_list(users, "following")
print_pagination(data)
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""
Get list followers
Usage: python3 scripts/get_list_followers.py LIST_ID --limit 20
"""
import argparse
from twitter_api import api_get, print_users_list, print_pagination
def main():
parser = argparse.ArgumentParser(description="Get list followers")
parser.add_argument("list_id", help="List ID")
parser.add_argument("--limit", "-l", type=int, default=20, help="Max followers")
parser.add_argument("--cursor", "-c", help="Pagination cursor")
args = parser.parse_args()
params = {"list_id": args.list_id, "cursor": args.cursor}
data = api_get("list/followers", params)
users = (data.get("followers") or data.get("users") or [])[:args.limit]
print(f"list_id: {args.list_id}")
print_users_list(users, "followers")
print_pagination(data)
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""
Get list members
Usage: python3 scripts/get_list_members.py LIST_ID --limit 20
"""
import argparse
from twitter_api import api_get, print_users_list, print_pagination
def main():
parser = argparse.ArgumentParser(description="Get list members")
parser.add_argument("list_id", help="List ID")
parser.add_argument("--limit", "-l", type=int, default=20, help="Max members")
parser.add_argument("--cursor", "-c", help="Pagination cursor")
args = parser.parse_args()
params = {"list_id": args.list_id, "cursor": args.cursor}
data = api_get("list/members", params)
users = (data.get("members") or data.get("users") or [])[:args.limit]
print(f"list_id: {args.list_id}")
print_users_list(users, "members")
print_pagination(data)
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""
Get Twitter Space details
Usage: python3 scripts/get_space.py SPACE_ID
"""
import argparse
import json
from twitter_api import api_get, format_count
def main():
parser = argparse.ArgumentParser(description="Get Twitter Space details")
parser.add_argument("space_id", help="Space ID")
parser.add_argument("--json", "-j", action="store_true", help="Output as JSON")
args = parser.parse_args()
data = api_get("space", {"spaceId": args.space_id})
space = data.get("data") or data
if args.json:
print(json.dumps(space, indent=2))
return
print(f"id: {space.get('id', '')}")
print(f"title: {space.get('title', '')}")
print(f"state: {space.get('state', '')}")
print(f"host: @{space.get('creator', {}).get('userName', '')}")
print(f"participants: {format_count(space.get('participant_count'))}")
print(f"created: {space.get('created_at', '')}")
print(f"started: {space.get('started_at', '')}")
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""
Get Twitter trends by location
Usage: python3 scripts/get_trends.py --woeid 1
"""
import argparse
import json
from twitter_api import api_get, format_count
# Common WOEIDs
WOEIDS = {
"worldwide": 1,
"usa": 23424977,
"uk": 23424975,
"japan": 23424856,
"india": 23424848,
"brazil": 23424768,
"canada": 23424775,
"australia": 23424748,
"germany": 23424829,
"france": 23424819,
}
def main():
parser = argparse.ArgumentParser(description="Get Twitter trends")
parser.add_argument("--woeid", "-w", type=int, default=1,
help="WOEID (1=Worldwide, 23424977=USA)")
parser.add_argument("--location", "-l", choices=list(WOEIDS.keys()),
help="Location name (alternative to woeid)")
parser.add_argument("--json", "-j", action="store_true", help="Output as JSON")
args = parser.parse_args()
woeid = WOEIDS.get(args.location, args.woeid) if args.location else args.woeid
data = api_get("trends", {"woeid": woeid})
trends = data.get("trends") or data.get("data") or []
if args.json:
print(json.dumps(trends, indent=2))
return
print(f"woeid: {woeid}")
print(f"trends[{len(trends)}]{{name,posts}}:")
for t in trends[:30]:
trend = t.get("trend", t)
name = trend.get("name", "")
posts = trend.get("meta_description", "-")
print(f" {name},{posts}")
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""
Get tweets by IDs
Usage: python3 scripts/get_tweet.py TWEET_ID [TWEET_ID2...]
"""
import argparse
from twitter_api import api_get, clean_tweet, print_tweet
def main():
parser = argparse.ArgumentParser(description="Get tweets by IDs")
parser.add_argument("tweet_ids", nargs="+", help="Tweet ID(s)")
args = parser.parse_args()
ids = ",".join(args.tweet_ids)
data = api_get("tweets", {"tweet_ids": ids})
tweets = data.get("tweets") or []
for i, tweet in enumerate(tweets):
if i > 0:
print("---")
print_tweet(clean_tweet(tweet))
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""
Get quote tweets
Usage: python3 scripts/get_tweet_quotes.py TWEET_ID --limit 20
"""
import argparse
from twitter_api import api_get, print_tweets_list, print_pagination
def main():
parser = argparse.ArgumentParser(description="Get quote tweets")
parser.add_argument("tweet_id", help="Tweet ID")
parser.add_argument("--limit", "-l", type=int, default=20, help="Max quotes")
parser.add_argument("--cursor", "-c", help="Pagination cursor")
args = parser.parse_args()
params = {"tweetId": args.tweet_id, "cursor": args.cursor}
data = api_get("tweet/quotes", params)
tweets = (data.get("tweets") or data.get("quotes") or [])[:args.limit]
print(f"tweet_id: {args.tweet_id}")
print_tweets_list(tweets, "quotes")
print_pagination(data)
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""
Get tweet replies
Usage: python3 scripts/get_tweet_replies.py TWEET_ID --limit 20
"""
import argparse
from twitter_api import api_get, print_tweets_list, print_pagination
def main():
parser = argparse.ArgumentParser(description="Get tweet replies")
parser.add_argument("tweet_id", help="Tweet ID")
parser.add_argument("--limit", "-l", type=int, default=20, help="Max replies")
parser.add_argument("--cursor", "-c", help="Pagination cursor")
args = parser.parse_args()
params = {"tweetId": args.tweet_id, "cursor": args.cursor}
data = api_get("tweet/replies", params)
tweets = (data.get("tweets") or data.get("replies") or [])[:args.limit]
print(f"tweet_id: {args.tweet_id}")
print_tweets_list(tweets, "replies")
print_pagination(data)
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""
Get users who retweeted a tweet
Usage: python3 scripts/get_tweet_retweeters.py TWEET_ID --limit 50
"""
import argparse
from twitter_api import api_get, print_users_list, print_pagination
def main():
parser = argparse.ArgumentParser(description="Get tweet retweeters")
parser.add_argument("tweet_id", help="Tweet ID")
parser.add_argument("--limit", "-l", type=int, default=50, help="Max retweeters")
parser.add_argument("--cursor", "-c", help="Pagination cursor")
args = parser.parse_args()
params = {"tweetId": args.tweet_id, "cursor": args.cursor}
data = api_get("tweet/retweeters", params)
users = (data.get("users") or data.get("retweeters") or [])[:args.limit]
print(f"tweet_id: {args.tweet_id}")
print_users_list(users, "retweeters")
print_pagination(data)
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""
Get tweet thread context
Usage: python3 scripts/get_tweet_thread.py TWEET_ID
"""
import argparse
from twitter_api import api_get, print_tweets_list, print_pagination
def main():
parser = argparse.ArgumentParser(description="Get tweet thread context")
parser.add_argument("tweet_id", help="Tweet ID")
parser.add_argument("--cursor", "-c", help="Pagination cursor")
args = parser.parse_args()
params = {"tweetId": args.tweet_id, "cursor": args.cursor}
data = api_get("tweet/thread_context", params)
tweets = data.get("replies") or data.get("tweets") or []
print(f"tweet_id: {args.tweet_id}")
print_tweets_list(tweets, "thread")
print_pagination(data)
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""
Get user profile about (extended info)
Usage: python3 scripts/get_user_about.py USERNAME
"""
import argparse
import json
from twitter_api import api_get
def main():
parser = argparse.ArgumentParser(description="Get Twitter user about/profile")
parser.add_argument("username", help="Twitter username (without @)")
parser.add_argument("--json", "-j", action="store_true", help="Output as JSON")
args = parser.parse_args()
data = api_get("user_about", {"userName": args.username})
user = data.get("data") or data
if args.json:
print(json.dumps(user, indent=2))
return
print(f"id: {user.get('id', '')}")
print(f"username: @{user.get('userName', '')}")
print(f"name: {user.get('name', '')}")
print(f"verified: {user.get('isBlueVerified', False)}")
print(f"protected: {user.get('protected', False)}")
print(f"created: {user.get('createdAt', '')}")
about = user.get("about_profile", {})
if about:
if about.get("account_based_in"):
print(f"based_in: {about['account_based_in']}")
changes = about.get("username_changes", {})
if changes.get("count"):
print(f"username_changes: {changes['count']}")
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""
Get user info by username
Usage: python3 scripts/get_user_info.py USERNAME
"""
import argparse
from twitter_api import api_get, clean_user, print_user
def main():
parser = argparse.ArgumentParser(description="Get Twitter user info")
parser.add_argument("username", help="Twitter username (without @)")
args = parser.parse_args()
data = api_get("user/info", {"userName": args.username})
user = data.get("data") or data
print_user(clean_user(user))
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""
Get user's mentions
Usage: python3 scripts/get_user_mentions.py USERNAME --limit 20
"""
import argparse
from twitter_api import api_get, print_tweets_list, print_pagination
def main():
parser = argparse.ArgumentParser(description="Get user's mentions")
parser.add_argument("username", help="Twitter username (without @)")
parser.add_argument("--limit", "-l", type=int, default=20, help="Max mentions")
parser.add_argument("--cursor", "-c", help="Pagination cursor")
args = parser.parse_args()
params = {"userName": args.username, "cursor": args.cursor}
data = api_get("user/mentions", params)
tweets = (data.get("tweets") or [])[:args.limit]
print(f"username: @{args.username}")
print_tweets_list(tweets, "mentions")
print_pagination(data)
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""
Get user's recent tweets
Usage: python3 scripts/get_user_tweets.py USERNAME --limit 20
"""
import argparse
from twitter_api import api_get, print_tweets_list, print_pagination
def main():
parser = argparse.ArgumentParser(description="Get user's tweets")
parser.add_argument("username", help="Twitter username (without @)")
parser.add_argument("--limit", "-l", type=int, default=20, help="Max tweets")
parser.add_argument("--cursor", "-c", help="Pagination cursor")
parser.add_argument("--include-replies", action="store_true", help="Include replies")
args = parser.parse_args()
params = {
"userName": args.username,
"cursor": args.cursor,
"includeReplies": "true" if args.include_replies else "false",
}
data = api_get("user/last_tweets", params)
tweets = (data.get("tweets") or [])[:args.limit]
print(f"username: @{args.username}")
print_tweets_list(tweets)
print_pagination(data)
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""
Get user's verified followers only
Usage: python3 scripts/get_verified_followers.py USERNAME --limit 20
"""
import argparse
from twitter_api import api_get, print_users_list, print_pagination
def main():
parser = argparse.ArgumentParser(description="Get user's verified followers")
parser.add_argument("username", help="Twitter username (without @)")
parser.add_argument("--limit", "-l", type=int, default=20, help="Max results")
parser.add_argument("--cursor", "-c", help="Pagination cursor")
args = parser.parse_args()
# First get user_id from username
user_data = api_get("user/info", {"userName": args.username})
user_id = (user_data.get("data") or user_data).get("id")
params = {"user_id": user_id, "cursor": args.cursor}
data = api_get("user/verifiedFollowers", params)
users = (data.get("followers") or data.get("users") or [])[:args.limit]
print(f"username: @{args.username}")
print_users_list(users, "verified_followers")
print_pagination(data)
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""
Search tweets from all communities
Usage: python3 scripts/search_community_tweets.py "query" --limit 20
"""
import argparse
from twitter_api import api_get, print_tweets_list, print_pagination
def main():
parser = argparse.ArgumentParser(description="Search community tweets")
parser.add_argument("query", help="Search query")
parser.add_argument("--limit", "-l", type=int, default=20, help="Max tweets")
parser.add_argument("--cursor", "-c", help="Pagination cursor")
args = parser.parse_args()
params = {"query": args.query, "queryType": "Latest", "cursor": args.cursor}
data = api_get("community/get_tweets_from_all_community", params)
tweets = (data.get("tweets") or [])[:args.limit]
print(f"query: {args.query}")
print_tweets_list(tweets)
print_pagination(data)
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""
Advanced tweet search
Usage: python3 scripts/search_tweets.py "query" --type Latest --limit 20
"""
import argparse
from twitter_api import api_get, print_tweets_list, print_pagination
def main():
parser = argparse.ArgumentParser(description="Search tweets")
parser.add_argument("query", help="Search query")
parser.add_argument("--type", "-t", choices=["Latest", "Top"], default="Latest",
help="Query type (default: Latest)")
parser.add_argument("--limit", "-l", type=int, default=20, help="Max results")
parser.add_argument("--cursor", "-c", help="Pagination cursor")
args = parser.parse_args()
params = {
"query": args.query,
"queryType": args.type,
"cursor": args.cursor,
}
data = api_get("tweet/advanced_search", params)
tweets = (data.get("tweets") or [])[:args.limit]
print(f"query: {args.query}")
print(f"type: {args.type}")
print_tweets_list(tweets)
print_pagination(data)
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""
Search users by keyword
Usage: python3 scripts/search_users.py "AI researcher" --limit 20
"""
import argparse
from twitter_api import api_get, print_users_list, print_pagination
def main():
parser = argparse.ArgumentParser(description="Search Twitter users")
parser.add_argument("query", help="Search query")
parser.add_argument("--limit", "-l", type=int, default=20, help="Max results")
parser.add_argument("--cursor", "-c", help="Pagination cursor")
args = parser.parse_args()
params = {"query": args.query, "cursor": args.cursor}
data = api_get("user/search", params)
users = (data.get("users") or [])[:args.limit]
print(f"query: {args.query}")
print_users_list(users)
print_pagination(data)
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""
Twitter API wrapper using twitterapi.io
"""
import urllib.request
import urllib.parse
import json
import sys
from credential import get_twitter_api_key
API_BASE = "https://api.twitterapi.io/twitter"
def api_get(endpoint: str, params: dict = None) -> dict:
"""Make GET request to twitterapi.io"""
api_key = get_twitter_api_key()
if not api_key:
print("error: TWITTERAPI_API_KEY not set", file=sys.stderr)
sys.exit(1)
url = f"{API_BASE}/{endpoint}"
if params:
filtered = {k: v for k, v in params.items() if v is not None}
if filtered:
url += "?" + urllib.parse.urlencode(filtered)
req = urllib.request.Request(url, headers={"User-Agent": "Mozilla/5.0", "X-API-Key": api_key})
try:
with urllib.request.urlopen(req, timeout=30) as resp:
return json.loads(resp.read().decode())
except urllib.error.HTTPError as e:
error_body = e.read().decode()
print(f"error: HTTP {e.code} - {error_body}", file=sys.stderr)
sys.exit(1)
except Exception as e:
print(f"error: {e}", file=sys.stderr)
sys.exit(1)
def format_count(n) -> str:
"""Format numbers (1234567 -> 1.2M)"""
if n is None:
return "0"
n = int(n)
if n >= 1_000_000_000:
return f"{n/1_000_000_000:.1f}B"
if n >= 1_000_000:
return f"{n/1_000_000:.1f}M"
if n >= 1_000:
return f"{n/1_000:.1f}K"
return str(n)
def clean_user(u: dict) -> dict:
"""Clean user object"""
if not u:
return None
return {
"id": u.get("id"),
"username": u.get("userName"),
"name": u.get("name"),
"verified": u.get("isBlueVerified"),
"followers": u.get("followers"),
"following": u.get("following"),
"tweets": u.get("statusesCount"),
"description": (u.get("description") or "")[:150],
"location": u.get("location"),
"created": u.get("createdAt"),
}
def clean_tweet(t: dict) -> dict:
"""Clean tweet object"""
if not t:
return None
author = t.get("author", {})
return {
"id": t.get("id"),
"text": t.get("text"),
"author": author.get("userName") if author else None,
"author_name": author.get("name") if author else None,
"created": t.get("createdAt"),
"retweets": t.get("retweetCount"),
"likes": t.get("likeCount"),
"replies": t.get("replyCount"),
"quotes": t.get("quoteCount"),
"views": t.get("viewCount"),
"lang": t.get("lang"),
"isReply": t.get("isReply"),
}
def print_user(u: dict):
"""Print user in TOON format"""
if not u:
return
print(f"id: {u.get('id', '')}")
print(f"username: @{u.get('username', '')}")
print(f"name: {u.get('name', '')}")
print(f"verified: {u.get('verified', False)}")
print(f"followers: {format_count(u.get('followers'))}")
print(f"following: {format_count(u.get('following'))}")
print(f"tweets: {format_count(u.get('tweets'))}")
if u.get('location'):
print(f"location: {u['location']}")
if u.get('description'):
print(f"bio: {u['description']}")
if u.get('created'):
print(f"created: {u['created']}")
def print_tweet(t: dict):
"""Print tweet in TOON format"""
if not t:
return
print(f"id: {t.get('id', '')}")
print(f"author: @{t.get('author', '')} ({t.get('author_name', '')})")
text = (t.get('text') or '')[:280]
print(f"text: {text}")
print(f"stats: {format_count(t.get('retweets'))} RT | {format_count(t.get('likes'))} likes | {format_count(t.get('replies'))} replies | {format_count(t.get('views'))} views")
print(f"created: {t.get('created', '')}")
def print_users_list(users: list, label: str = "users"):
"""Print list of users"""
cleaned = [clean_user(u) for u in users if u]
print(f"{label}[{len(cleaned)}]{{username,name,followers,verified}}:")
for u in cleaned:
print(f" @{u['username']},{u['name']},{format_count(u['followers'])},{u['verified']}")
def print_tweets_list(tweets: list, label: str = "tweets"):
"""Print list of tweets"""
cleaned = [clean_tweet(t) for t in tweets if t]
print(f"{label}[{len(cleaned)}]{{id,author,text,likes}}:")
for t in cleaned:
text = (t['text'] or '')[:60].replace('\n', ' ')
print(f" {t['id']},@{t['author']},{text},{format_count(t['likes'])}")
def print_pagination(data: dict):
"""Print pagination info"""
has_next = data.get("has_next_page", False)
cursor = data.get("next_cursor", "")
if has_next and cursor:
print(f"---")
print(f"has_next_page: {has_next}")
print(f"next_cursor: {cursor}")