From eaf703b101bec3957e530f9523a2d307fe232570 Mon Sep 17 00:00:00 2001 From: Taylor Caldwell Date: Sat, 6 Dec 2025 14:16:43 -0800 Subject: [PATCH 1/3] Update READMEs and directory structure --- README.md | 25 ++- java/README.md | 51 +++++- javascript/README.md | 106 ++++++++--- python/README.md | 172 ++++++++++-------- ...reate_compliance_job.py => create_jobs.py} | 0 ...pliance_results.py => download_results.py} | 0 ...information_by_id.py => get_jobs_by_id.py} | 0 .../compliance/get_list_of_compliance_jobs.py | 27 --- .../{add_list_member.py => add_member.py} | 0 ...remove_list_member.py => remove_member.py} | 0 ...get_resposted_by.py => get_reposted_by.py} | 0 .../{get_by_ids.py => get_spaces_by_ids.py} | 0 python/users/{ => block}/get_blocking.py | 0 .../bookmark}/create_bookmark.py | 0 .../bookmark}/delete_bookmark.py | 0 .../bookmark/get_bookmarks.py} | 0 python/users/{ => follow}/get_followers.py | 0 python/users/{ => follow}/get_following.py | 0 ...rer_token.py => get_users_by_usernames.py} | 0 ...=> get_users_by_usernames_user_context.py} | 0 ...ers_me_user_context.py => get_users_me.py} | 0 .../{posts => users/like}/get_liked_posts.py | 0 python/{posts => users/like}/like_post.py | 0 python/{posts => users/like}/unlike_post.py | 0 python/{ => users}/lists/follow_list.py | 0 .../{ => users}/lists/get_followed_lists.py | 0 .../{ => users}/lists/get_list_memberships.py | 0 python/{ => users}/lists/get_owned_lists.py | 0 python/{ => users}/lists/get_pinned_lists.py | 0 python/{ => users}/lists/pin_list.py | 0 python/{ => users}/lists/unfollow_list.py | 0 python/{ => users}/lists/unpin_list.py | 0 python/users/{ => mute}/get_muting.py | 0 python/users/{ => mute}/mute_user.py | 0 python/users/{ => mute}/unmute_user.py | 0 python/{posts => users/repost}/repost_post.py | 0 .../{posts => users/repost}/unrepost_post.py | 0 .../timeline/get_home_timeline.py} | 0 .../timeline}/get_mentions.py | 0 .../timeline}/get_posts.py | 0 ruby/README.md | 87 +++++++-- 41 files changed, 307 insertions(+), 161 deletions(-) rename python/compliance/{create_compliance_job.py => create_jobs.py} (100%) rename python/compliance/{download_compliance_results.py => download_results.py} (100%) rename python/compliance/{get_compliance_job_information_by_id.py => get_jobs_by_id.py} (100%) delete mode 100644 python/compliance/get_list_of_compliance_jobs.py rename python/lists/{add_list_member.py => add_member.py} (100%) rename python/lists/{remove_list_member.py => remove_member.py} (100%) rename python/posts/{get_resposted_by.py => get_reposted_by.py} (100%) rename python/spaces/{get_by_ids.py => get_spaces_by_ids.py} (100%) rename python/users/{ => block}/get_blocking.py (100%) rename python/{bookmarks => users/bookmark}/create_bookmark.py (100%) rename python/{bookmarks => users/bookmark}/delete_bookmark.py (100%) rename python/{bookmarks/bookmarks_lookup.py => users/bookmark/get_bookmarks.py} (100%) rename python/users/{ => follow}/get_followers.py (100%) rename python/users/{ => follow}/get_following.py (100%) rename python/users/{get_users_by_usernames_bearer_token.py => get_users_by_usernames.py} (100%) rename python/users/{get_user_by_usernams_user_context.py => get_users_by_usernames_user_context.py} (100%) rename python/users/{get_users_me_user_context.py => get_users_me.py} (100%) rename python/{posts => users/like}/get_liked_posts.py (100%) rename python/{posts => users/like}/like_post.py (100%) rename python/{posts => users/like}/unlike_post.py (100%) rename python/{ => users}/lists/follow_list.py (100%) rename python/{ => users}/lists/get_followed_lists.py (100%) rename python/{ => users}/lists/get_list_memberships.py (100%) rename python/{ => users}/lists/get_owned_lists.py (100%) rename python/{ => users}/lists/get_pinned_lists.py (100%) rename python/{ => users}/lists/pin_list.py (100%) rename python/{ => users}/lists/unfollow_list.py (100%) rename python/{ => users}/lists/unpin_list.py (100%) rename python/users/{ => mute}/get_muting.py (100%) rename python/users/{ => mute}/mute_user.py (100%) rename python/users/{ => mute}/unmute_user.py (100%) rename python/{posts => users/repost}/repost_post.py (100%) rename python/{posts => users/repost}/unrepost_post.py (100%) rename python/{timelines/get_timeline.py => users/timeline/get_home_timeline.py} (100%) rename python/{timelines => users/timeline}/get_mentions.py (100%) rename python/{timelines => users/timeline}/get_posts.py (100%) diff --git a/README.md b/README.md index b1fbeff..9ae445d 100644 --- a/README.md +++ b/README.md @@ -7,8 +7,8 @@ Working code samples for the **X API v2** in Python, JavaScript, Ruby, Java, and ## 📁 Repository Structure ``` -├── python/ # 108 Python examples -├── javascript/ # 70 JavaScript examples +├── python/ # 65 Python examples +├── javascript/ # 59 JavaScript examples ├── ruby/ # 58 Ruby examples ├── java/ # 19 Java examples ├── r/ # 5 R examples @@ -35,31 +35,36 @@ export CONSUMER_SECRET='your_consumer_secret' ```bash # Python cd python && pip install -r requirements.txt -python posts/recent_search.py +python posts/search_recent.py # JavaScript -cd javascript && npm install -node posts/recent_search.js +cd javascript +node posts/search_recent.js # Ruby cd ruby && bundle install -ruby posts/recent_search.rb +ruby posts/search_recent.rb + +# Java +cd java +javac -cp ".:lib/*" posts/RecentSearchDemo.java +java -cp ".:lib/*" RecentSearchDemo ``` ## 📚 Examples by Category | Category | Python | JavaScript | Ruby | Java | R | |----------|--------|------------|------|------|---| -| Posts (search, create, delete, likes, retweets) | ✅ | ✅ | ✅ | ✅ | ✅ | -| Users (lookup, followers, blocks, mutes) | ✅ | ✅ | ✅ | ✅ | ✅ | +| Posts (search, create, delete, likes, reposts) | ✅ | ✅ | ✅ | ✅ | ✅ | +| Users (lookup, followers, following, blocks, mutes) | ✅ | ✅ | ✅ | ✅ | ✅ | | Timelines (user, mentions, home) | ✅ | ✅ | ✅ | ✅ | | | Streams (filtered, sampled) | ✅ | ✅ | ✅ | ✅ | | -| Lists (lookup, manage, members) | ✅ | ✅ | ✅ | | | +| Lists (lookup, manage, members, follow) | ✅ | ✅ | ✅ | | | | Spaces (lookup, search) | ✅ | ✅ | ✅ | ✅ | | | Bookmarks | ✅ | ✅ | ✅ | | | | Direct Messages | ✅ | | | | | | Media Upload | ✅ | | | | | -| Compliance | ✅ | ✅ | | ✅ | | +| Compliance | ✅ | ✅ | | | | | Usage | ✅ | ✅ | | ✅ | | ## 🔐 Authentication diff --git a/java/README.md b/java/README.md index 44fbe43..e4b76aa 100644 --- a/java/README.md +++ b/java/README.md @@ -39,23 +39,56 @@ export BEARER_TOKEN='your_bearer_token' ## Examples by Category ### Posts -| File | Description | Auth | -|------|-------------|------| -| `posts/SearchRecent.java` | Search recent posts (7 days) | Bearer | +- `posts/FullArchiveSearchDemo.java` - Full archive search demo +- `posts/FullArchiveTweetCountsDemo.java` - Full archive tweet counts demo +- `posts/QuoteTweetsDemo.java` - Get posts that quote a post +- `posts/RecentSearchDemo.java` - Recent search demo (last 7 days) +- `posts/RecentTweetCountsDemo.java` - Recent tweet counts demo +- `posts/SearchRecent.java` - Recent search (last 7 days) +- `posts/TweetsDemo.java` - Look up posts by ID ### Users -| File | Description | Auth | -|------|-------------|------| -| `users/Lookup.java` | Look up users by username | Bearer | +- `users/FollowersLookupDemo.java` - Get user's followers +- `users/FollowingLookupDemo.java` - Get users a user is following +- `users/Lookup.java` - Look up users by username +- `users/UsersDemo.java` - Users lookup demo + +### Timelines +- `timelines/reverse-chron-home-timeline-java-sdk.java` - Get home timeline (reverse chronological) +- `timelines/UserMentionsDemo.java` - Get user mentions timeline +- `timelines/UserTweetsDemo.java` - Get user's posts timeline + +### Streams +- `streams/FilteredStreamDemo.java` - Filtered stream with rules +- `streams/SampledStream.java` - Sampled stream + +### Spaces +- `spaces/SearchSpacesDemo.java` - Search for Spaces +- `spaces/SpacesLookupDemo.java` - Look up Spaces by ID + +### Usage +- `usage/UsageTweetsDemo.java` - Get API usage information ## Building and Running +### Compile + ```bash -# Compile +# Compile a single file javac -cp ".:lib/*" posts/SearchRecent.java -# Run -java -cp ".:lib/*" SearchRecent +# Or compile all files +find . -name "*.java" -exec javac -cp ".:lib/*" {} \; +``` + +### Run + +```bash +# Run a single example +java -cp ".:lib/*" posts.SearchRecent + +# Or with package structure +java -cp ".:lib/*" posts/RecentSearchDemo ``` ## More Information diff --git a/javascript/README.md b/javascript/README.md index 216afe5..ae5f4b0 100644 --- a/javascript/README.md +++ b/javascript/README.md @@ -12,9 +12,7 @@ node --version ### 2. Install dependencies -```bash -npm install -``` +No package.json is required. Examples use Node.js built-in modules or standard libraries. ### 3. Set environment variables @@ -32,38 +30,96 @@ export CONSUMER_SECRET='your_consumer_secret' ## Examples by Category ### Posts -| File | Description | Auth | -|------|-------------|------| -| `posts/create_post.js` | Create a new post | OAuth 1.0a | -| `posts/delete_post.js` | Delete a post | OAuth 1.0a | -| `posts/lookup.js` | Look up posts by ID | Bearer | -| `posts/search_recent.js` | Search recent posts (7 days) | Bearer | +- `posts/create_post.js` - Create a new post +- `posts/delete_post.js` - Delete a post +- `posts/get_liking_users.js` - Get users who liked a post +- `posts/get_post_counts_all.js` - Get post counts (full archive) +- `posts/get_post_counts_recent.js` - Get post counts (recent) +- `posts/get_posts_by_ids.js` - Look up posts by ID (bearer token) +- `posts/get_posts_by_ids_user_context.js` - Look up posts by ID (user context) +- `posts/get_quoted_posts.js` - Get posts that quote a post +- `posts/get_reposted_by.js` - Get users who reposted a post +- `posts/search_all.js` - Full archive search +- `posts/search_recent.js` - Recent search (last 7 days) ### Users -| File | Description | Auth | -|------|-------------|------| -| `users/lookup.js` | Look up users by username | Bearer | -| `users/followers.js` | Get user's followers | Bearer | +- `users/get_users_by_usernames.js` - Look up users by username (bearer token) +- `users/get_users_by_usernames_user_context.js` - Look up users by username (user context) +- `users/get_users_me.js` - Get authenticated user (me) + +#### User Actions - Block +- `users/block/get_blocking.js` - Get users blocked by a user + +#### User Actions - Bookmark +- `users/bookmark/create_bookmark.js` - Create a bookmark +- `users/bookmark/delete_bookmark.js` - Delete a bookmark +- `users/bookmark/get_bookmarks.js` - Get user's bookmarks + +#### User Actions - Follow +- `users/follow/get_followers.js` - Get user's followers +- `users/follow/get_followers_paginated.js` - Get user's followers (paginated) +- `users/follow/get_following_paginated.js` - Get users a user is following (paginated) + +#### User Actions - Like +- `users/like/get_liked_posts.js` - Get posts liked by a user +- `users/like/like_post.js` - Like a post +- `users/like/unlike_post.js` - Unlike a post + +#### User Actions - Lists +- `users/lists/follow_list.js` - Follow a list +- `users/lists/get_followed_lists.js` - Get lists followed by a user +- `users/lists/get_list_memberships.js` - Get list memberships +- `users/lists/get_owned_lists.js` - Get lists owned by a user +- `users/lists/pin_list.js` - Pin a list +- `users/lists/unfollow_list.js` - Unfollow a list +- `users/lists/unpin_list.js` - Unpin a list + +#### User Actions - Mute +- `users/mute/get_muting.js` - Get users muted by a user +- `users/mute/mute_user.js` - Mute a user +- `users/mute/unmute_user.js` - Unmute a user + +#### User Actions - Repost +- `users/repost/repost_post.js` - Repost a post +- `users/repost/unrepost_post.js` - Undo a repost + +#### User Actions - Timeline +- `users/timeline/get_home_timeline.js` - Get home timeline +- `users/timeline/get_mentions.js` - Get user mentions timeline +- `users/timeline/get_posts.js` - Get user's posts timeline +- `users/timeline/get_posts_paginated.js` - Get user's posts timeline (paginated) ### Timelines -| File | Description | Auth | -|------|-------------|------| -| `timelines/user_posts.js` | User's posts timeline | Bearer | +- See `users/timeline/` directory for timeline examples ### Streams -| File | Description | Auth | -|------|-------------|------| -| `streams/filtered_stream.js` | Filtered stream with rules | Bearer | +- `streams/stream_posts_filtered.js` - Filtered stream with rules +- `streams/stream_posts_sample.js` - Sampled stream ### Lists -| File | Description | Auth | -|------|-------------|------| -| `lists/lookup.js` | Look up a list | Bearer | +- `lists/add_member.js` - Add member to a list +- `lists/create_list.js` - Create a new list +- `lists/delete_list.js` - Delete a list +- `lists/get_list_by_id.js` - Get list by ID +- `lists/get_list_followers.js` - Get list followers +- `lists/get_list_members.js` - Get list members +- `lists/get_list_posts.js` - Get posts from a list +- `lists/remove_member.js` - Remove member from a list +- `lists/update_list.js` - Update a list ### Spaces -| File | Description | Auth | -|------|-------------|------| -| `spaces/lookup.js` | Look up Spaces | Bearer | +- `spaces/get_spaces_by_ids.js` - Look up Spaces by ID +- `spaces/search_spaces.js` - Search for Spaces + +### Compliance +- `compliance/create_jobs.js` - Create compliance job +- `compliance/download_results.js` - Download compliance results +- `compliance/get_jobs_by_id.js` - Get compliance job by ID +- `compliance/get_jobs.js` - Get compliance jobs +- `compliance/upload_ids.js` - Upload IDs for compliance + +### Usage +- `usage/get_usage.js` - Get API usage information ## Running Examples diff --git a/python/README.md b/python/README.md index b3b4b0b..a4431a1 100644 --- a/python/README.md +++ b/python/README.md @@ -5,9 +5,12 @@ Working Python examples for the X API v2. ## Setup ```bash -pip install requests requests-oauthlib +pip install -r requirements.txt ``` +The `requirements.txt` file includes: +- `xdk>=0.4.5` - X Developer Kit for Python + ## Environment Variables ```bash @@ -19,95 +22,112 @@ export CONSUMER_SECRET='your_consumer_secret' ## Examples ### Posts -- posts/create_post.py -- posts/delete_post.py -- posts/get_liked_posts.py -- posts/get_liking_users.py -- posts/get_post_counts_all.py -- posts/get_post_counts_recent.py -- posts/get_posts_by_ids.py -- posts/get_quoted_posts.py -- posts/like_post.py -- posts/repost_post.py -- posts/retweeted_by.py -- posts/search_all.py -- posts/search_recent.py -- posts/undo_a_retweet.py -- posts/unlike_a_tweet.py +- `posts/create_post.py` - Create a new post +- `posts/delete_post.py` - Delete a post +- `posts/get_liking_users.py` - Get users who liked a post +- `posts/get_post_counts_all.py` - Get post counts (full archive) +- `posts/get_post_counts_recent.py` - Get post counts (recent) +- `posts/get_posts_by_ids.py` - Look up posts by ID +- `posts/get_quoted_posts.py` - Get posts that quote a post +- `posts/get_reposted_by.py` - Get users who reposted a post +- `posts/search_all.py` - Full archive search +- `posts/search_recent.py` - Recent search (last 7 days) ### Users -- users/following_lookup.py -- users/get_followers.py -- users/get_users_me_user_context.py -- users/get_users_with_bearer_token.py -- users/get_users_with_user_context.py -- users/lookup_blocks.py -- users/lookup_mutes.py -- users/lookup.py -- users/me.py -- users/mute_a_user.py -- users/muted.py -- users/unblock_a_user.py -- users/unmute_a_user.py - -### Timelines -- timelines/get_mentions.py -- timelines/get_posts.py -- timelines/get_timeline.py -- timelines/reverse-chron-home-timeline.py +- `users/get_users_by_usernames.py` - Look up users by username (bearer token) +- `users/get_users_by_usernames_user_context.py` - Look up user by username (user context) +- `users/get_users_me.py` - Get authenticated user (me) + +#### User Actions - Block +- `users/block/get_blocking.py` - Get users blocked by a user + +#### User Actions - Bookmark +- `users/bookmark/get_bookmarks.py` - Get user's bookmarks +- `users/bookmark/create_bookmark.py` - Create a bookmark +- `users/bookmark/delete_bookmark.py` - Delete a bookmark + +#### User Actions - Follow +- `users/follow/get_followers.py` - Get user's followers +- `users/follow/get_following.py` - Get users a user is following + +#### User Actions - Like +- `users/like/get_liked_posts.py` - Get posts liked by a user +- `users/like/like_post.py` - Like a post +- `users/like/unlike_post.py` - Unlike a post + +#### User Actions - Lists +- `users/lists/follow_list.py` - Follow a list +- `users/lists/get_followed_lists.py` - Get lists followed by a user +- `users/lists/get_list_memberships.py` - Get list memberships +- `users/lists/get_owned_lists.py` - Get lists owned by a user +- `users/lists/get_pinned_lists.py` - Get pinned lists +- `users/lists/pin_list.py` - Pin a list +- `users/lists/unfollow_list.py` - Unfollow a list +- `users/lists/unpin_list.py` - Unpin a list + +#### User Actions - Mute +- `users/mute/get_muting.py` - Get users muted by a user +- `users/mute/mute_user.py` - Mute a user +- `users/mute/unmute_user.py` - Unmute a user + +#### User Actions - Repost +- `users/repost/repost_post.py` - Repost a post +- `users/repost/unrepost_post.py` - Undo a repost + +#### User Actions - Timeline +- `users/timeline/get_mentions.py` - Get user mentions timeline +- `users/timeline/get_posts.py` - Get user's posts timeline +- `users/timeline/get_home_timeline.py` - Get home timeline ### Streams -- streams/filtered_stream.py -- streams/sampled_stream.py +- `streams/stream_posts_filtered.py` - Filtered stream with rules +- `streams/stream_posts_sample.py` - Sampled stream ### Lists -- lists/add_list_member.py -- lists/create_list.py -- lists/delete_list.py -- lists/follow_list.py -- lists/get_list_by_id.py -- lists/get_list_followers.py -- lists/get_list_members.py -- lists/get_list_posts.py -- lists/pin_list.py -- lists/Pinned-List.py -- lists/remove_member.py -- lists/unfollow_list.py -- lists/unpin_list.py -- lists/update_a_list.py -- lists/user-list-followed.py -- lists/user-list-memberships.py -- lists/user-owned-list-lookup.py - -### Bookmarks -- bookmarks/bookmarks_lookup.py -- bookmarks/create_bookmark.py -- bookmarks/delete_bookmark.py +- `lists/add_member.py` - Add member to a list +- `lists/create_list.py` - Create a new list +- `lists/delete_list.py` - Delete a list +- `lists/get_list_by_id.py` - Get list by ID +- `lists/get_list_followers.py` - Get list followers +- `lists/get_list_members.py` - Get list members +- `lists/get_list_posts.py` - Get posts from a list +- `lists/remove_member.py` - Remove member from a list +- `lists/update_list.py` - Update a list ### Spaces -- spaces/search_spaces.py -- spaces/spaces_lookup.py +- `spaces/get_spaces_by_ids.py` - Look up Spaces by ID +- `spaces/search_spaces.py` - Search for Spaces ### Direct Messages -- direct_messages/get_events_by_conversation.py -- direct_messages/get_one_to_one_conversation_events.py -- direct_messages/get_user_conversation_events.py -- direct_messages/post_dm_to_conversation.py -- direct_messages/post_group_conversation_dm.py -- direct_messages/post_one_to_one_dm.py +- `direct_messages/get_events_by_conversation.py` - Get DM events by conversation +- `direct_messages/get_one_to_one_conversation_events.py` - Get one-to-one conversation events +- `direct_messages/get_user_conversation_events.py` - Get user conversation events +- `direct_messages/post_dm_to_conversation.py` - Post DM to conversation +- `direct_messages/post_group_conversation_dm.py` - Post group conversation DM +- `direct_messages/post_one_to_one_dm.py` - Post one-to-one DM ### Media -- media/media_upload_v2.py -- media/upload.py +- `media/media_upload_v2.py` - Media upload v2 +- `media/upload.py` - Media upload ### Compliance -- compliance/create_compliance_job.py -- compliance/download_compliance_results.py -- compliance/get_compliance_job_information_by_id.py -- compliance/get_jobs.py -- compliance/get_list_of_compliance_jobs.py -- compliance/upload_ids.py +- `compliance/create_jobs.py` - Create compliance job +- `compliance/download_results.py` - Download compliance results +- `compliance/get_jobs_by_id.py` - Get compliance job by ID +- `compliance/get_jobs.py` - Get compliance jobs +- `compliance/upload_ids.py` - Upload IDs for compliance ### Usage -- usage/get_usage.py +- `usage/get_usage.py` - Get API usage information + +## Running Examples + +```bash +# Make sure environment variables are set +python posts/search_recent.py +``` + +## More Information +- [X API Documentation](https://developer.x.com/en/docs/twitter-api) +- [X Developer Portal](https://developer.x.com/en/portal/dashboard) diff --git a/python/compliance/create_compliance_job.py b/python/compliance/create_jobs.py similarity index 100% rename from python/compliance/create_compliance_job.py rename to python/compliance/create_jobs.py diff --git a/python/compliance/download_compliance_results.py b/python/compliance/download_results.py similarity index 100% rename from python/compliance/download_compliance_results.py rename to python/compliance/download_results.py diff --git a/python/compliance/get_compliance_job_information_by_id.py b/python/compliance/get_jobs_by_id.py similarity index 100% rename from python/compliance/get_compliance_job_information_by_id.py rename to python/compliance/get_jobs_by_id.py diff --git a/python/compliance/get_list_of_compliance_jobs.py b/python/compliance/get_list_of_compliance_jobs.py deleted file mode 100644 index 84ddf59..0000000 --- a/python/compliance/get_list_of_compliance_jobs.py +++ /dev/null @@ -1,27 +0,0 @@ -""" -Get Compliance Jobs - X API v2 -============================== -Endpoint: GET https://api.x.com/2/compliance/jobs -Docs: https://developer.x.com/en/docs/twitter-api/compliance/batch-compliance/api-reference/get-compliance-jobs - -Authentication: Bearer Token (App-only) -Required env vars: BEARER_TOKEN -""" - -import os -import json -from xdk import Client - -bearer_token = os.environ.get("BEARER_TOKEN") -client = Client(bearer_token=bearer_token) - -# For User Compliance job, replace the value for type with users -job_type = "tweets" - -def main(): - response = client.compliance.get_jobs(type=job_type) - - print(json.dumps(response.data, indent=4, sort_keys=True)) - -if __name__ == "__main__": - main() diff --git a/python/lists/add_list_member.py b/python/lists/add_member.py similarity index 100% rename from python/lists/add_list_member.py rename to python/lists/add_member.py diff --git a/python/lists/remove_list_member.py b/python/lists/remove_member.py similarity index 100% rename from python/lists/remove_list_member.py rename to python/lists/remove_member.py diff --git a/python/posts/get_resposted_by.py b/python/posts/get_reposted_by.py similarity index 100% rename from python/posts/get_resposted_by.py rename to python/posts/get_reposted_by.py diff --git a/python/spaces/get_by_ids.py b/python/spaces/get_spaces_by_ids.py similarity index 100% rename from python/spaces/get_by_ids.py rename to python/spaces/get_spaces_by_ids.py diff --git a/python/users/get_blocking.py b/python/users/block/get_blocking.py similarity index 100% rename from python/users/get_blocking.py rename to python/users/block/get_blocking.py diff --git a/python/bookmarks/create_bookmark.py b/python/users/bookmark/create_bookmark.py similarity index 100% rename from python/bookmarks/create_bookmark.py rename to python/users/bookmark/create_bookmark.py diff --git a/python/bookmarks/delete_bookmark.py b/python/users/bookmark/delete_bookmark.py similarity index 100% rename from python/bookmarks/delete_bookmark.py rename to python/users/bookmark/delete_bookmark.py diff --git a/python/bookmarks/bookmarks_lookup.py b/python/users/bookmark/get_bookmarks.py similarity index 100% rename from python/bookmarks/bookmarks_lookup.py rename to python/users/bookmark/get_bookmarks.py diff --git a/python/users/get_followers.py b/python/users/follow/get_followers.py similarity index 100% rename from python/users/get_followers.py rename to python/users/follow/get_followers.py diff --git a/python/users/get_following.py b/python/users/follow/get_following.py similarity index 100% rename from python/users/get_following.py rename to python/users/follow/get_following.py diff --git a/python/users/get_users_by_usernames_bearer_token.py b/python/users/get_users_by_usernames.py similarity index 100% rename from python/users/get_users_by_usernames_bearer_token.py rename to python/users/get_users_by_usernames.py diff --git a/python/users/get_user_by_usernams_user_context.py b/python/users/get_users_by_usernames_user_context.py similarity index 100% rename from python/users/get_user_by_usernams_user_context.py rename to python/users/get_users_by_usernames_user_context.py diff --git a/python/users/get_users_me_user_context.py b/python/users/get_users_me.py similarity index 100% rename from python/users/get_users_me_user_context.py rename to python/users/get_users_me.py diff --git a/python/posts/get_liked_posts.py b/python/users/like/get_liked_posts.py similarity index 100% rename from python/posts/get_liked_posts.py rename to python/users/like/get_liked_posts.py diff --git a/python/posts/like_post.py b/python/users/like/like_post.py similarity index 100% rename from python/posts/like_post.py rename to python/users/like/like_post.py diff --git a/python/posts/unlike_post.py b/python/users/like/unlike_post.py similarity index 100% rename from python/posts/unlike_post.py rename to python/users/like/unlike_post.py diff --git a/python/lists/follow_list.py b/python/users/lists/follow_list.py similarity index 100% rename from python/lists/follow_list.py rename to python/users/lists/follow_list.py diff --git a/python/lists/get_followed_lists.py b/python/users/lists/get_followed_lists.py similarity index 100% rename from python/lists/get_followed_lists.py rename to python/users/lists/get_followed_lists.py diff --git a/python/lists/get_list_memberships.py b/python/users/lists/get_list_memberships.py similarity index 100% rename from python/lists/get_list_memberships.py rename to python/users/lists/get_list_memberships.py diff --git a/python/lists/get_owned_lists.py b/python/users/lists/get_owned_lists.py similarity index 100% rename from python/lists/get_owned_lists.py rename to python/users/lists/get_owned_lists.py diff --git a/python/lists/get_pinned_lists.py b/python/users/lists/get_pinned_lists.py similarity index 100% rename from python/lists/get_pinned_lists.py rename to python/users/lists/get_pinned_lists.py diff --git a/python/lists/pin_list.py b/python/users/lists/pin_list.py similarity index 100% rename from python/lists/pin_list.py rename to python/users/lists/pin_list.py diff --git a/python/lists/unfollow_list.py b/python/users/lists/unfollow_list.py similarity index 100% rename from python/lists/unfollow_list.py rename to python/users/lists/unfollow_list.py diff --git a/python/lists/unpin_list.py b/python/users/lists/unpin_list.py similarity index 100% rename from python/lists/unpin_list.py rename to python/users/lists/unpin_list.py diff --git a/python/users/get_muting.py b/python/users/mute/get_muting.py similarity index 100% rename from python/users/get_muting.py rename to python/users/mute/get_muting.py diff --git a/python/users/mute_user.py b/python/users/mute/mute_user.py similarity index 100% rename from python/users/mute_user.py rename to python/users/mute/mute_user.py diff --git a/python/users/unmute_user.py b/python/users/mute/unmute_user.py similarity index 100% rename from python/users/unmute_user.py rename to python/users/mute/unmute_user.py diff --git a/python/posts/repost_post.py b/python/users/repost/repost_post.py similarity index 100% rename from python/posts/repost_post.py rename to python/users/repost/repost_post.py diff --git a/python/posts/unrepost_post.py b/python/users/repost/unrepost_post.py similarity index 100% rename from python/posts/unrepost_post.py rename to python/users/repost/unrepost_post.py diff --git a/python/timelines/get_timeline.py b/python/users/timeline/get_home_timeline.py similarity index 100% rename from python/timelines/get_timeline.py rename to python/users/timeline/get_home_timeline.py diff --git a/python/timelines/get_mentions.py b/python/users/timeline/get_mentions.py similarity index 100% rename from python/timelines/get_mentions.py rename to python/users/timeline/get_mentions.py diff --git a/python/timelines/get_posts.py b/python/users/timeline/get_posts.py similarity index 100% rename from python/timelines/get_posts.py rename to python/users/timeline/get_posts.py diff --git a/ruby/README.md b/ruby/README.md index c8cb19c..6c890cf 100644 --- a/ruby/README.md +++ b/ruby/README.md @@ -12,11 +12,20 @@ ruby --version ### 2. Install dependencies +```bash +bundle install +``` + +Or manually: ```bash gem install typhoeus gem install oauth # For OAuth 1.0a examples ``` +The `Gemfile` includes: +- `typhoeus` (~> 1.4) - HTTP client library +- `oauth` (~> 1.1) - OAuth 1.0a library + ### 3. Set environment variables For **Bearer Token** authentication (app-only): @@ -33,26 +42,76 @@ export CONSUMER_SECRET='your_consumer_secret' ## Examples by Category ### Posts -| File | Description | Auth | -|------|-------------|------| -| `posts/search_recent.rb` | Search recent posts (7 days) | Bearer | -| `posts/lookup.rb` | Look up posts by ID | Bearer | +- `posts/create_tweet.rb` - Create a new post +- `posts/delete_tweet.rb` - Delete a post +- `posts/full_archive_tweet_counts.rb` - Full archive tweet counts +- `posts/full-archive-search.rb` - Full archive search +- `posts/get_tweets_with_bearer_token.rb` - Look up posts by ID (bearer token) +- `posts/get_tweets_with_user_context.rb` - Look up posts by ID (user context) +- `posts/like_a_tweet.rb` - Like a post +- `posts/liked_tweets.rb` - Get posts liked by a user +- `posts/liking_users.rb` - Get users who liked a post +- `posts/lookup.rb` - Look up posts by ID +- `posts/quote_tweets.rb` - Get posts that quote a post +- `posts/recent_search.rb` - Recent search (last 7 days) +- `posts/recent_tweet_counts.rb` - Recent tweet counts +- `posts/retweet_a_tweet.rb` - Repost a post +- `posts/retweeted_by.rb` - Get users who reposted a post +- `posts/search_recent.rb` - Recent search (alternative) +- `posts/undo_a_retweet.rb` - Undo a repost +- `posts/unlike_a_tweet.rb` - Unlike a post ### Users -| File | Description | Auth | -|------|-------------|------| -| `users/lookup.rb` | Look up users by username | Bearer | -| `users/followers.rb` | Get user's followers | Bearer | +- `users/block_a_user.rb` - Block a user +- `users/followers-lookup.rb` - Get user's followers (lookup) +- `users/followers.rb` - Get user's followers +- `users/following-lookup.rb` - Get users a user is following +- `users/get_users_me_with_user_context.rb` - Get authenticated user (me) +- `users/get_users_with_bearer_token.rb` - Look up users by username (bearer token) +- `users/get_users_with_user_context.rb` - Look up users by username (user context) +- `users/lookup_blocks.rb` - Get users blocked by a user +- `users/lookup_mutes.rb` - Get users muted by a user +- `users/lookup.rb` - Look up users by username +- `users/mute_a_user.rb` - Mute a user +- `users/unblock_a_user.rb` - Unblock a user +- `users/unmute_a_user.rb` - Unmute a user ### Timelines -| File | Description | Auth | -|------|-------------|------| -| `timelines/user_posts.rb` | User's posts timeline | Bearer | +- `timelines/reverse-chron-home-timeline.rb` - Get home timeline (reverse chronological) +- `timelines/user_posts.rb` - Get user's posts timeline +- `timelines/user-mentions.rb` - Get user mentions timeline +- `timelines/user-tweets.rb` - Get user's tweets timeline + +### Streams +- `streams/filtered_stream.rb` - Filtered stream with rules +- `streams/sampled_stream.rb` - Sampled stream ### Lists -| File | Description | Auth | -|------|-------------|------| -| `lists/lookup.rb` | Look up a list | Bearer | +- `lists/add_member.rb` - Add member to a list +- `lists/create_a_list.rb` - Create a new list +- `lists/delete_a_list.rb` - Delete a list +- `lists/follow_list.rb` - Follow a list +- `lists/list-followers-lookup.rb` - Get list followers +- `lists/list-lookup-by-id.rb` - Get list by ID +- `lists/list-member-lookup.rb` - Get list members +- `lists/List-Tweets.rb` - Get posts from a list +- `lists/lookup.rb` - Look up a list +- `lists/pin_list.rb` - Pin a list +- `lists/Pinned-List.rb` - Get pinned lists +- `lists/remove_member.rb` - Remove member from a list +- `lists/unfollow_list.rb` - Unfollow a list +- `lists/unpin_list.rb` - Unpin a list +- `lists/update_a_list.rb` - Update a list +- `lists/user-list-followed.rb` - Get lists followed by a user +- `lists/user-list-memberships.rb` - Get list memberships +- `lists/user-owned-list-lookup.rb` - Get lists owned by a user + +### Bookmarks +- `bookmarks/bookmarks_lookup.rb` - Get user's bookmarks + +### Spaces +- `spaces/search_spaces.rb` - Search for Spaces +- `spaces/spaces_lookup.rb` - Look up Spaces by ID ## Running Examples From 3356e061a18dc13598dce3ac506fd30e2e50b68e Mon Sep 17 00:00:00 2001 From: Taylor Caldwell Date: Sat, 6 Dec 2025 14:18:47 -0800 Subject: [PATCH 2/3] update env variables for oatuh2 --- README.md | 15 ++++++-- java/README.md | 10 +++++- javascript/README.md | 66 +++++++++++++++++++----------------- python/README.md | 81 ++++++++++++++++++++++++-------------------- ruby/README.md | 12 +++++-- 5 files changed, 110 insertions(+), 74 deletions(-) diff --git a/README.md b/README.md index 9ae445d..78417ef 100644 --- a/README.md +++ b/README.md @@ -24,8 +24,19 @@ Sign up at the [X Developer Portal](https://developer.x.com/en/portal/dashboard) ### 2. Set Environment Variables +**For read-only operations (search, lookup):** ```bash export BEARER_TOKEN='your_bearer_token' +``` + +**For user actions (post, like, repost, bookmark, etc.):** +```bash +export CLIENT_ID='your_client_id' +export CLIENT_SECRET='your_client_secret' +``` + +**For OAuth 1.0a (legacy endpoints):** +```bash export CONSUMER_KEY='your_consumer_key' export CONSUMER_SECRET='your_consumer_secret' ``` @@ -72,8 +83,8 @@ java -cp ".:lib/*" RecentSearchDemo | Type | Use Case | Env Vars | |------|----------|----------| | Bearer Token | Read-only (search, lookup) | `BEARER_TOKEN` | -| OAuth 1.0a | User actions (post, like) | `CONSUMER_KEY`, `CONSUMER_SECRET` | -| OAuth 2.0 PKCE | Bookmarks, newer endpoints | OAuth flow | +| OAuth 2.0 PKCE | User actions (post, like, repost, bookmark, mute, etc.) | `CLIENT_ID`, `CLIENT_SECRET` | +| OAuth 1.0a | Legacy endpoints (if applicable) | `CONSUMER_KEY`, `CONSUMER_SECRET` | ## 🤖 For LLMs diff --git a/java/README.md b/java/README.md index e4b76aa..1c1d5eb 100644 --- a/java/README.md +++ b/java/README.md @@ -31,11 +31,19 @@ Using Maven, add to your `pom.xml`: ### 3. Set environment variables -For **Bearer Token** authentication (app-only): +**For read-only operations (search, lookup):** ```bash export BEARER_TOKEN='your_bearer_token' ``` +**For user actions (post, like, repost, bookmark, etc.):** +```bash +export CLIENT_ID='your_client_id' +export CLIENT_SECRET='your_client_secret' +``` + +**Note:** Most user action examples require OAuth 2.0 authentication with `CLIENT_ID` and `CLIENT_SECRET`. Read-only examples (search, lookup) only require `BEARER_TOKEN`. + ## Examples by Category ### Posts diff --git a/javascript/README.md b/javascript/README.md index ae5f4b0..5ec0b1b 100644 --- a/javascript/README.md +++ b/javascript/README.md @@ -16,27 +16,29 @@ No package.json is required. Examples use Node.js built-in modules or standard l ### 3. Set environment variables -For **Bearer Token** authentication (app-only): +**For read-only operations (search, lookup):** ```bash export BEARER_TOKEN='your_bearer_token' ``` -For **OAuth 1.0a** authentication (user context): +**For user actions (post, like, repost, bookmark, mute, etc.):** ```bash -export CONSUMER_KEY='your_consumer_key' -export CONSUMER_SECRET='your_consumer_secret' +export CLIENT_ID='your_client_id' +export CLIENT_SECRET='your_client_secret' ``` +**Note:** Most user action examples (create post, like, repost, bookmark, mute, block, etc.) require OAuth 2.0 authentication with `CLIENT_ID` and `CLIENT_SECRET`. Read-only examples (search, lookup) only require `BEARER_TOKEN`. + ## Examples by Category ### Posts -- `posts/create_post.js` - Create a new post -- `posts/delete_post.js` - Delete a post -- `posts/get_liking_users.js` - Get users who liked a post +- `posts/create_post.js` - Create a new post (requires `CLIENT_ID`, `CLIENT_SECRET`) +- `posts/delete_post.js` - Delete a post (requires `CLIENT_ID`, `CLIENT_SECRET`) +- `posts/get_liking_users.js` - Get users who liked a post (requires `CLIENT_ID`, `CLIENT_SECRET` for user context) - `posts/get_post_counts_all.js` - Get post counts (full archive) - `posts/get_post_counts_recent.js` - Get post counts (recent) - `posts/get_posts_by_ids.js` - Look up posts by ID (bearer token) -- `posts/get_posts_by_ids_user_context.js` - Look up posts by ID (user context) +- `posts/get_posts_by_ids_user_context.js` - Look up posts by ID (user context) (requires `CLIENT_ID`, `CLIENT_SECRET`) - `posts/get_quoted_posts.js` - Get posts that quote a post - `posts/get_reposted_by.js` - Get users who reposted a post - `posts/search_all.js` - Full archive search @@ -44,16 +46,16 @@ export CONSUMER_SECRET='your_consumer_secret' ### Users - `users/get_users_by_usernames.js` - Look up users by username (bearer token) -- `users/get_users_by_usernames_user_context.js` - Look up users by username (user context) -- `users/get_users_me.js` - Get authenticated user (me) +- `users/get_users_by_usernames_user_context.js` - Look up users by username (user context) (requires `CLIENT_ID`, `CLIENT_SECRET`) +- `users/get_users_me.js` - Get authenticated user (me) (requires `CLIENT_ID`, `CLIENT_SECRET`) #### User Actions - Block -- `users/block/get_blocking.js` - Get users blocked by a user +- `users/block/get_blocking.js` - Get users blocked by a user (requires `CLIENT_ID`, `CLIENT_SECRET`) #### User Actions - Bookmark -- `users/bookmark/create_bookmark.js` - Create a bookmark -- `users/bookmark/delete_bookmark.js` - Delete a bookmark -- `users/bookmark/get_bookmarks.js` - Get user's bookmarks +- `users/bookmark/create_bookmark.js` - Create a bookmark (requires `CLIENT_ID`, `CLIENT_SECRET`) +- `users/bookmark/delete_bookmark.js` - Delete a bookmark (requires `CLIENT_ID`, `CLIENT_SECRET`) +- `users/bookmark/get_bookmarks.js` - Get user's bookmarks (requires `CLIENT_ID`, `CLIENT_SECRET`) #### User Actions - Follow - `users/follow/get_followers.js` - Get user's followers @@ -61,30 +63,30 @@ export CONSUMER_SECRET='your_consumer_secret' - `users/follow/get_following_paginated.js` - Get users a user is following (paginated) #### User Actions - Like -- `users/like/get_liked_posts.js` - Get posts liked by a user -- `users/like/like_post.js` - Like a post -- `users/like/unlike_post.js` - Unlike a post +- `users/like/get_liked_posts.js` - Get posts liked by a user (requires `CLIENT_ID`, `CLIENT_SECRET`) +- `users/like/like_post.js` - Like a post (requires `CLIENT_ID`, `CLIENT_SECRET`) +- `users/like/unlike_post.js` - Unlike a post (requires `CLIENT_ID`, `CLIENT_SECRET`) #### User Actions - Lists -- `users/lists/follow_list.js` - Follow a list +- `users/lists/follow_list.js` - Follow a list (requires `CLIENT_ID`, `CLIENT_SECRET`) - `users/lists/get_followed_lists.js` - Get lists followed by a user - `users/lists/get_list_memberships.js` - Get list memberships - `users/lists/get_owned_lists.js` - Get lists owned by a user -- `users/lists/pin_list.js` - Pin a list -- `users/lists/unfollow_list.js` - Unfollow a list -- `users/lists/unpin_list.js` - Unpin a list +- `users/lists/pin_list.js` - Pin a list (requires `CLIENT_ID`, `CLIENT_SECRET`) +- `users/lists/unfollow_list.js` - Unfollow a list (requires `CLIENT_ID`, `CLIENT_SECRET`) +- `users/lists/unpin_list.js` - Unpin a list (requires `CLIENT_ID`, `CLIENT_SECRET`) #### User Actions - Mute -- `users/mute/get_muting.js` - Get users muted by a user -- `users/mute/mute_user.js` - Mute a user -- `users/mute/unmute_user.js` - Unmute a user +- `users/mute/get_muting.js` - Get users muted by a user (requires `CLIENT_ID`, `CLIENT_SECRET`) +- `users/mute/mute_user.js` - Mute a user (requires `CLIENT_ID`, `CLIENT_SECRET`) +- `users/mute/unmute_user.js` - Unmute a user (requires `CLIENT_ID`, `CLIENT_SECRET`) #### User Actions - Repost -- `users/repost/repost_post.js` - Repost a post -- `users/repost/unrepost_post.js` - Undo a repost +- `users/repost/repost_post.js` - Repost a post (requires `CLIENT_ID`, `CLIENT_SECRET`) +- `users/repost/unrepost_post.js` - Undo a repost (requires `CLIENT_ID`, `CLIENT_SECRET`) #### User Actions - Timeline -- `users/timeline/get_home_timeline.js` - Get home timeline +- `users/timeline/get_home_timeline.js` - Get home timeline (requires `CLIENT_ID`, `CLIENT_SECRET`) - `users/timeline/get_mentions.js` - Get user mentions timeline - `users/timeline/get_posts.js` - Get user's posts timeline - `users/timeline/get_posts_paginated.js` - Get user's posts timeline (paginated) @@ -97,15 +99,15 @@ export CONSUMER_SECRET='your_consumer_secret' - `streams/stream_posts_sample.js` - Sampled stream ### Lists -- `lists/add_member.js` - Add member to a list -- `lists/create_list.js` - Create a new list -- `lists/delete_list.js` - Delete a list +- `lists/add_member.js` - Add member to a list (requires `CLIENT_ID`, `CLIENT_SECRET`) +- `lists/create_list.js` - Create a new list (requires `CLIENT_ID`, `CLIENT_SECRET`) +- `lists/delete_list.js` - Delete a list (requires `CLIENT_ID`, `CLIENT_SECRET`) - `lists/get_list_by_id.js` - Get list by ID - `lists/get_list_followers.js` - Get list followers - `lists/get_list_members.js` - Get list members - `lists/get_list_posts.js` - Get posts from a list -- `lists/remove_member.js` - Remove member from a list -- `lists/update_list.js` - Update a list +- `lists/remove_member.js` - Remove member from a list (requires `CLIENT_ID`, `CLIENT_SECRET`) +- `lists/update_list.js` - Update a list (requires `CLIENT_ID`, `CLIENT_SECRET`) ### Spaces - `spaces/get_spaces_by_ids.js` - Look up Spaces by ID diff --git a/python/README.md b/python/README.md index a4431a1..a8ddc7d 100644 --- a/python/README.md +++ b/python/README.md @@ -13,17 +13,24 @@ The `requirements.txt` file includes: ## Environment Variables +**For read-only operations (search, lookup):** ```bash export BEARER_TOKEN='your_bearer_token' -export CONSUMER_KEY='your_consumer_key' -export CONSUMER_SECRET='your_consumer_secret' ``` +**For user actions (post, like, repost, bookmark, mute, etc.):** +```bash +export CLIENT_ID='your_client_id' +export CLIENT_SECRET='your_client_secret' +``` + +**Note:** Most user action examples (create post, like, repost, bookmark, mute, block, etc.) require OAuth 2.0 authentication with `CLIENT_ID` and `CLIENT_SECRET`. Read-only examples (search, lookup) only require `BEARER_TOKEN`. + ## Examples ### Posts -- `posts/create_post.py` - Create a new post -- `posts/delete_post.py` - Delete a post +- `posts/create_post.py` - Create a new post (requires `CLIENT_ID`, `CLIENT_SECRET`) +- `posts/delete_post.py` - Delete a post (requires `CLIENT_ID`, `CLIENT_SECRET`) - `posts/get_liking_users.py` - Get users who liked a post - `posts/get_post_counts_all.py` - Get post counts (full archive) - `posts/get_post_counts_recent.py` - Get post counts (recent) @@ -35,80 +42,80 @@ export CONSUMER_SECRET='your_consumer_secret' ### Users - `users/get_users_by_usernames.py` - Look up users by username (bearer token) -- `users/get_users_by_usernames_user_context.py` - Look up user by username (user context) -- `users/get_users_me.py` - Get authenticated user (me) +- `users/get_users_by_usernames_user_context.py` - Look up user by username (user context) (requires `CLIENT_ID`, `CLIENT_SECRET`) +- `users/get_users_me.py` - Get authenticated user (me) (requires `CLIENT_ID`, `CLIENT_SECRET`) #### User Actions - Block -- `users/block/get_blocking.py` - Get users blocked by a user +- `users/block/get_blocking.py` - Get users blocked by a user (requires `CLIENT_ID`, `CLIENT_SECRET`) #### User Actions - Bookmark -- `users/bookmark/get_bookmarks.py` - Get user's bookmarks -- `users/bookmark/create_bookmark.py` - Create a bookmark -- `users/bookmark/delete_bookmark.py` - Delete a bookmark +- `users/bookmark/get_bookmarks.py` - Get user's bookmarks (requires `CLIENT_ID`, `CLIENT_SECRET`) +- `users/bookmark/create_bookmark.py` - Create a bookmark (requires `CLIENT_ID`, `CLIENT_SECRET`) +- `users/bookmark/delete_bookmark.py` - Delete a bookmark (requires `CLIENT_ID`, `CLIENT_SECRET`) #### User Actions - Follow - `users/follow/get_followers.py` - Get user's followers - `users/follow/get_following.py` - Get users a user is following #### User Actions - Like -- `users/like/get_liked_posts.py` - Get posts liked by a user -- `users/like/like_post.py` - Like a post -- `users/like/unlike_post.py` - Unlike a post +- `users/like/get_liked_posts.py` - Get posts liked by a user (requires `CLIENT_ID`, `CLIENT_SECRET`) +- `users/like/like_post.py` - Like a post (requires `CLIENT_ID`, `CLIENT_SECRET`) +- `users/like/unlike_post.py` - Unlike a post (requires `CLIENT_ID`, `CLIENT_SECRET`) #### User Actions - Lists -- `users/lists/follow_list.py` - Follow a list +- `users/lists/follow_list.py` - Follow a list (requires `CLIENT_ID`, `CLIENT_SECRET`) - `users/lists/get_followed_lists.py` - Get lists followed by a user - `users/lists/get_list_memberships.py` - Get list memberships - `users/lists/get_owned_lists.py` - Get lists owned by a user -- `users/lists/get_pinned_lists.py` - Get pinned lists -- `users/lists/pin_list.py` - Pin a list -- `users/lists/unfollow_list.py` - Unfollow a list -- `users/lists/unpin_list.py` - Unpin a list +- `users/lists/get_pinned_lists.py` - Get pinned lists (requires `CLIENT_ID`, `CLIENT_SECRET`) +- `users/lists/pin_list.py` - Pin a list (requires `CLIENT_ID`, `CLIENT_SECRET`) +- `users/lists/unfollow_list.py` - Unfollow a list (requires `CLIENT_ID`, `CLIENT_SECRET`) +- `users/lists/unpin_list.py` - Unpin a list (requires `CLIENT_ID`, `CLIENT_SECRET`) #### User Actions - Mute -- `users/mute/get_muting.py` - Get users muted by a user -- `users/mute/mute_user.py` - Mute a user -- `users/mute/unmute_user.py` - Unmute a user +- `users/mute/get_muting.py` - Get users muted by a user (requires `CLIENT_ID`, `CLIENT_SECRET`) +- `users/mute/mute_user.py` - Mute a user (requires `CLIENT_ID`, `CLIENT_SECRET`) +- `users/mute/unmute_user.py` - Unmute a user (requires `CLIENT_ID`, `CLIENT_SECRET`) #### User Actions - Repost -- `users/repost/repost_post.py` - Repost a post -- `users/repost/unrepost_post.py` - Undo a repost +- `users/repost/repost_post.py` - Repost a post (requires `CLIENT_ID`, `CLIENT_SECRET`) +- `users/repost/unrepost_post.py` - Undo a repost (requires `CLIENT_ID`, `CLIENT_SECRET`) #### User Actions - Timeline - `users/timeline/get_mentions.py` - Get user mentions timeline - `users/timeline/get_posts.py` - Get user's posts timeline -- `users/timeline/get_home_timeline.py` - Get home timeline +- `users/timeline/get_home_timeline.py` - Get home timeline (requires `CLIENT_ID`, `CLIENT_SECRET`) ### Streams - `streams/stream_posts_filtered.py` - Filtered stream with rules - `streams/stream_posts_sample.py` - Sampled stream ### Lists -- `lists/add_member.py` - Add member to a list -- `lists/create_list.py` - Create a new list -- `lists/delete_list.py` - Delete a list +- `lists/add_member.py` - Add member to a list (requires `CLIENT_ID`, `CLIENT_SECRET`) +- `lists/create_list.py` - Create a new list (requires `CLIENT_ID`, `CLIENT_SECRET`) +- `lists/delete_list.py` - Delete a list (requires `CLIENT_ID`, `CLIENT_SECRET`) - `lists/get_list_by_id.py` - Get list by ID - `lists/get_list_followers.py` - Get list followers - `lists/get_list_members.py` - Get list members - `lists/get_list_posts.py` - Get posts from a list -- `lists/remove_member.py` - Remove member from a list -- `lists/update_list.py` - Update a list +- `lists/remove_member.py` - Remove member from a list (requires `CLIENT_ID`, `CLIENT_SECRET`) +- `lists/update_list.py` - Update a list (requires `CLIENT_ID`, `CLIENT_SECRET`) ### Spaces - `spaces/get_spaces_by_ids.py` - Look up Spaces by ID - `spaces/search_spaces.py` - Search for Spaces ### Direct Messages -- `direct_messages/get_events_by_conversation.py` - Get DM events by conversation -- `direct_messages/get_one_to_one_conversation_events.py` - Get one-to-one conversation events -- `direct_messages/get_user_conversation_events.py` - Get user conversation events -- `direct_messages/post_dm_to_conversation.py` - Post DM to conversation -- `direct_messages/post_group_conversation_dm.py` - Post group conversation DM -- `direct_messages/post_one_to_one_dm.py` - Post one-to-one DM +- `direct_messages/get_events_by_conversation.py` - Get DM events by conversation (requires `CLIENT_ID`, `CLIENT_SECRET`) +- `direct_messages/get_one_to_one_conversation_events.py` - Get one-to-one conversation events (requires `CLIENT_ID`, `CLIENT_SECRET`) +- `direct_messages/get_user_conversation_events.py` - Get user conversation events (requires `CLIENT_ID`, `CLIENT_SECRET`) +- `direct_messages/post_dm_to_conversation.py` - Post DM to conversation (requires `CLIENT_ID`, `CLIENT_SECRET`) +- `direct_messages/post_group_conversation_dm.py` - Post group conversation DM (requires `CLIENT_ID`, `CLIENT_SECRET`) +- `direct_messages/post_one_to_one_dm.py` - Post one-to-one DM (requires `CLIENT_ID`, `CLIENT_SECRET`) ### Media -- `media/media_upload_v2.py` - Media upload v2 -- `media/upload.py` - Media upload +- `media/media_upload_v2.py` - Media upload v2 (requires `CLIENT_ID`, `CLIENT_SECRET`) +- `media/upload.py` - Media upload (requires `CLIENT_ID`, `CLIENT_SECRET`) ### Compliance - `compliance/create_jobs.py` - Create compliance job diff --git a/ruby/README.md b/ruby/README.md index 6c890cf..0777ae8 100644 --- a/ruby/README.md +++ b/ruby/README.md @@ -28,17 +28,25 @@ The `Gemfile` includes: ### 3. Set environment variables -For **Bearer Token** authentication (app-only): +**For read-only operations (search, lookup):** ```bash export BEARER_TOKEN='your_bearer_token' ``` -For **OAuth 1.0a** authentication (user context): +**For user actions (post, like, repost, bookmark, etc.):** +```bash +export CLIENT_ID='your_client_id' +export CLIENT_SECRET='your_client_secret' +``` + +**For OAuth 1.0a (legacy endpoints):** ```bash export CONSUMER_KEY='your_consumer_key' export CONSUMER_SECRET='your_consumer_secret' ``` +**Note:** Most user action examples require OAuth 2.0 authentication with `CLIENT_ID` and `CLIENT_SECRET`. Read-only examples (search, lookup) only require `BEARER_TOKEN`. + ## Examples by Category ### Posts From 5471b09a530f60cbaaa059a27ef932be0ab326be Mon Sep 17 00:00:00 2001 From: Vardhan Agnihotri Date: Thu, 19 Mar 2026 15:08:52 -0700 Subject: [PATCH 3/3] missing scripts for subscriptions, webhooks, posts, follows --- python/activity/create_subscription.py | 63 +++++++++++++++ python/activity/delete_subscription.py | 34 ++++++++ python/activity/list_subscriptions.py | 33 ++++++++ python/activity/stream_events.py | 39 ++++++++++ python/activity/update_subscription.py | 43 +++++++++++ python/posts/get_post_by_id.py | 37 +++++++++ python/posts/hide_reply.py | 69 +++++++++++++++++ python/requirements.txt | 2 + python/users/follow/follow_user.py | 70 +++++++++++++++++ python/users/follow/unfollow_user.py | 69 +++++++++++++++++ python/users/repost/get_reposts_of_me.py | 78 +++++++++++++++++++ python/webhooks/delete_webhook.py | 33 ++++++++ python/webhooks/list_webhooks.py | 28 +++++++ python/webhooks/register_webhook.py | 36 +++++++++ python/webhooks/validate_webhook.py | 35 +++++++++ python/webhooks/webhook_server.py | 98 ++++++++++++++++++++++++ 16 files changed, 767 insertions(+) create mode 100644 python/activity/create_subscription.py create mode 100644 python/activity/delete_subscription.py create mode 100644 python/activity/list_subscriptions.py create mode 100644 python/activity/stream_events.py create mode 100644 python/activity/update_subscription.py create mode 100644 python/posts/get_post_by_id.py create mode 100644 python/posts/hide_reply.py create mode 100644 python/users/follow/follow_user.py create mode 100644 python/users/follow/unfollow_user.py create mode 100644 python/users/repost/get_reposts_of_me.py create mode 100644 python/webhooks/delete_webhook.py create mode 100644 python/webhooks/list_webhooks.py create mode 100644 python/webhooks/register_webhook.py create mode 100644 python/webhooks/validate_webhook.py create mode 100644 python/webhooks/webhook_server.py diff --git a/python/activity/create_subscription.py b/python/activity/create_subscription.py new file mode 100644 index 0000000..84ac379 --- /dev/null +++ b/python/activity/create_subscription.py @@ -0,0 +1,63 @@ +""" +Create Activity Subscription - X API v2 +======================================== +Endpoint: POST https://api.x.com/2/activity/subscriptions +Docs: https://docs.x.com/x-api/activity/introduction + +Creates a subscription to receive real-time activity events for a specified +event type and filter. Once created, matching events will be delivered to the +activity stream (see stream_events.py) and optionally to a registered webhook. + +Supported public event types include: + - profile.update.bio + - profile.update.picture + - profile.update.banner + - profile.update.location + - profile.update.url + - profile.update.username + +Authentication: Bearer Token (App-only) +Required env vars: BEARER_TOKEN +""" + +import os +import json +from xdk import Client + +bearer_token = os.environ.get("BEARER_TOKEN") +client = Client(bearer_token=bearer_token) + +# Replace with the user ID you want to monitor for activity events +user_id = "2244994945" + +# Replace with the event type you want to subscribe to. +# See the supported event types listed in the docstring above. +event_type = "profile.update.bio" + +# Optional: replace with a registered webhook ID to also receive events via webhook delivery. +# If omitted, events are only available on the activity stream. +webhook_id = None + +def main(): + payload = { + "event_type": event_type, + "filter": { + "user_id": user_id + } + } + + # Attach a label to help identify this subscription in the stream + payload["tag"] = f"{event_type} for user {user_id}" + + # Optionally route events to a registered webhook in addition to the stream + if webhook_id: + payload["webhook_id"] = webhook_id + + response = client.activity.create_subscription(body=payload) + + print("Response code: 201") + print(json.dumps(response.data, indent=4, sort_keys=True)) + + +if __name__ == "__main__": + main() diff --git a/python/activity/delete_subscription.py b/python/activity/delete_subscription.py new file mode 100644 index 0000000..53159e1 --- /dev/null +++ b/python/activity/delete_subscription.py @@ -0,0 +1,34 @@ +""" +Delete Activity Subscription - X API v2 +======================================== +Endpoint: DELETE https://api.x.com/2/activity/subscriptions/:id +Docs: https://docs.x.com/x-api/activity/introduction + +Deletes an activity subscription. Once deleted, events matching that subscription +will no longer be delivered to the stream or associated webhook. Use +list_subscriptions.py to find the subscription_id you wish to remove. + +Authentication: Bearer Token (App-only) +Required env vars: BEARER_TOKEN +""" + +import os +import json +from xdk import Client + +bearer_token = os.environ.get("BEARER_TOKEN") +client = Client(bearer_token=bearer_token) + +# Replace with the subscription ID you wish to delete. +# You can find subscription IDs by running list_subscriptions.py +subscription_id = "your-subscription-id" + +def main(): + response = client.activity.delete_subscription(subscription_id) + + print("Response code: 200") + print(json.dumps(response.data, indent=4, sort_keys=True)) + + +if __name__ == "__main__": + main() diff --git a/python/activity/list_subscriptions.py b/python/activity/list_subscriptions.py new file mode 100644 index 0000000..c7e0f44 --- /dev/null +++ b/python/activity/list_subscriptions.py @@ -0,0 +1,33 @@ +""" +List Activity Subscriptions - X API v2 +======================================= +Endpoint: GET https://api.x.com/2/activity/subscriptions +Docs: https://docs.x.com/x-api/activity/introduction + +Returns all active activity subscriptions for your app. Use the subscription_id +from the response to update or delete individual subscriptions. + +Authentication: Bearer Token (App-only) +Required env vars: BEARER_TOKEN +""" + +import os +import json +from xdk import Client + +bearer_token = os.environ.get("BEARER_TOKEN") +client = Client(bearer_token=bearer_token) + +def main(): + response = client.activity.get_subscriptions() + + # Access data attribute safely + response_data = getattr(response, 'data', None) + if response_data: + print(json.dumps(response_data, indent=4, sort_keys=True)) + else: + print(json.dumps(response, indent=4, sort_keys=True)) + + +if __name__ == "__main__": + main() diff --git a/python/activity/stream_events.py b/python/activity/stream_events.py new file mode 100644 index 0000000..10ae6c9 --- /dev/null +++ b/python/activity/stream_events.py @@ -0,0 +1,39 @@ +""" +Activity Stream - X API v2 +========================== +Endpoint: GET https://api.x.com/2/activity/stream +Docs: https://docs.x.com/x-api/activity/introduction + +Opens a persistent HTTP connection and streams real-time activity events +matching your active subscriptions. Events are delivered as they occur on +the platform — no polling required. + +You must create at least one subscription (see create_subscription.py) before +events will be delivered to this stream. + +Authentication: Bearer Token (App-only) +Required env vars: BEARER_TOKEN +""" + +import os +import json +from xdk import Client + +bearer_token = os.environ.get("BEARER_TOKEN") +client = Client(bearer_token=bearer_token) + +def main(): + print("Connecting to activity stream... (press Ctrl+C to stop)") + + # The stream() method returns a generator that yields events as they arrive. + # The SDK manages reconnection with exponential backoff automatically. + for event in client.activity.stream(): + # Access data attribute (model uses extra='allow' so data should be available) + # Use getattr with fallback in case data field is missing from response + event_data = getattr(event, 'data', None) + if event_data: + print(json.dumps(event_data, indent=4, sort_keys=True)) + + +if __name__ == "__main__": + main() diff --git a/python/activity/update_subscription.py b/python/activity/update_subscription.py new file mode 100644 index 0000000..025431a --- /dev/null +++ b/python/activity/update_subscription.py @@ -0,0 +1,43 @@ +""" +Update Activity Subscription - X API v2 +======================================== +Endpoint: PUT https://api.x.com/2/activity/subscriptions/:id +Docs: https://docs.x.com/x-api/activity/introduction + +Updates an existing activity subscription. You can change the filter (e.g. target +a different user ID), the tag, or the associated webhook. Use list_subscriptions.py +to find the subscription_id you wish to update. + +Authentication: Bearer Token (App-only) +Required env vars: BEARER_TOKEN +""" + +import os +import json +from xdk import Client + +bearer_token = os.environ.get("BEARER_TOKEN") +client = Client(bearer_token=bearer_token) + +# Replace with the subscription ID you wish to update. +# You can find subscription IDs by running list_subscriptions.py +subscription_id = "your-subscription-id" + +# Replace with the updated user ID you want to monitor +updated_user_id = "2244994945" + +def main(): + payload = { + "filter": { + "user_id": updated_user_id + } + } + + response = client.activity.update_subscription(subscription_id, body=payload) + + print("Response code: 200") + print(json.dumps(response.data, indent=4, sort_keys=True)) + + +if __name__ == "__main__": + main() diff --git a/python/posts/get_post_by_id.py b/python/posts/get_post_by_id.py new file mode 100644 index 0000000..7acf07a --- /dev/null +++ b/python/posts/get_post_by_id.py @@ -0,0 +1,37 @@ +""" +Single Post Lookup - X API v2 +============================= +Endpoint: GET https://api.x.com/2/tweets/:id +Docs: https://developer.x.com/en/docs/twitter-api/tweets/lookup/api-reference/get-tweets-id + +Authentication: Bearer Token (App-only) or OAuth (User Context) +Required env vars: BEARER_TOKEN +""" + +import os +import json +from xdk import Client + +bearer_token = os.environ.get("BEARER_TOKEN") +client = Client(bearer_token=bearer_token) + +# Replace with the Post ID you want to look up +post_id = "post-id" + +def main(): + # Post fields are adjustable. Options include: + # attachments, author_id, context_annotations, conversation_id, + # created_at, entities, geo, id, in_reply_to_user_id, lang, + # non_public_metrics, organic_metrics, possibly_sensitive, + # promoted_metrics, public_metrics, referenced_tweets, + # source, text, and withheld + response = client.posts.get_by_id( + post_id, + tweet_fields=["created_at", "author_id", "lang", "source", "public_metrics", "entities"] + ) + + print(json.dumps(response.data, indent=4, sort_keys=True)) + + +if __name__ == "__main__": + main() diff --git a/python/posts/hide_reply.py b/python/posts/hide_reply.py new file mode 100644 index 0000000..a5f923d --- /dev/null +++ b/python/posts/hide_reply.py @@ -0,0 +1,69 @@ +""" +Hide Reply - X API v2 +===================== +Endpoint: PUT https://api.x.com/2/tweets/:id/hidden +Docs: https://developer.x.com/en/docs/twitter-api/tweets/hide-replies/api-reference/put-tweets-id-hidden + +Authentication: OAuth 2.0 (User Context) +Required env vars: CLIENT_ID, CLIENT_SECRET + +Note: You can only hide or unhide replies to conversations you authored. +Pass hidden=True to hide a reply, or hidden=False to unhide one. +""" + +import os +import json +from xdk import Client +from xdk.oauth2_auth import OAuth2PKCEAuth + +# The code below sets the client ID and client secret from your environment variables +# To set environment variables on macOS or Linux, run the export commands below from the terminal: +# export CLIENT_ID='YOUR-CLIENT-ID' +# export CLIENT_SECRET='YOUR-CLIENT-SECRET' +client_id = os.environ.get("CLIENT_ID") +client_secret = os.environ.get("CLIENT_SECRET") + +# Replace the following URL with your callback URL, which can be obtained from your App's auth settings. +redirect_uri = "https://example.com" + +# Set the scopes +scopes = ["tweet.read", "tweet.write", "users.read", "offline.access"] + +# Replace with the ID of the reply you wish to hide. +# You can only hide replies to conversations you authored. +tweet_id = "reply-tweet-id-to-hide" + +def main(): + # Step 1: Create PKCE instance + auth = OAuth2PKCEAuth( + client_id=client_id, + client_secret=client_secret, + redirect_uri=redirect_uri, + scope=scopes + ) + + # Step 2: Get authorization URL + auth_url = auth.get_authorization_url() + print("Visit the following URL to authorize your App on behalf of your X handle in a browser:") + print(auth_url) + + # Step 3: Handle callback + callback_url = input("Paste the full callback URL here: ") + + # Step 4: Exchange code for tokens + tokens = auth.fetch_token(authorization_response=callback_url) + access_token = tokens["access_token"] + + # Step 5: Create client + client = Client(access_token=access_token) + + # Step 6: Hide the reply + # Set hidden=False to unhide a previously hidden reply + response = client.posts.hide_reply(tweet_id, hidden=True) + + print("Response code: 200") + print(json.dumps(response.data, indent=4, sort_keys=True)) + + +if __name__ == "__main__": + main() diff --git a/python/requirements.txt b/python/requirements.txt index ebc8553..2f878f3 100644 --- a/python/requirements.txt +++ b/python/requirements.txt @@ -1 +1,3 @@ xdk>=0.4.5 +flask>=3.0.0 +waitress>=3.0.0 diff --git a/python/users/follow/follow_user.py b/python/users/follow/follow_user.py new file mode 100644 index 0000000..3731c8d --- /dev/null +++ b/python/users/follow/follow_user.py @@ -0,0 +1,70 @@ +""" +Follow User - X API v2 +====================== +Endpoint: POST https://api.x.com/2/users/:id/following +Docs: https://developer.x.com/en/docs/twitter-api/users/follows/api-reference/post-users-id-following + +Authentication: OAuth 2.0 (User Context) +Required env vars: CLIENT_ID, CLIENT_SECRET +""" + +import os +import json +from xdk import Client +from xdk.oauth2_auth import OAuth2PKCEAuth + +# The code below sets the client ID and client secret from your environment variables +# To set environment variables on macOS or Linux, run the export commands below from the terminal: +# export CLIENT_ID='YOUR-CLIENT-ID' +# export CLIENT_SECRET='YOUR-CLIENT-SECRET' +client_id = os.environ.get("CLIENT_ID") +client_secret = os.environ.get("CLIENT_SECRET") + +# Replace the following URL with your callback URL, which can be obtained from your App's auth settings. +redirect_uri = "https://example.com" + +# Set the scopes +scopes = ["tweet.read", "users.read", "follows.write", "offline.access"] + +# Be sure to replace user-id-to-follow with the user id you wish to follow. +# You can find a user ID by using the user lookup endpoint +target_user_id = "user-id-to-follow" + +def main(): + # Step 1: Create PKCE instance + auth = OAuth2PKCEAuth( + client_id=client_id, + client_secret=client_secret, + redirect_uri=redirect_uri, + scope=scopes + ) + + # Step 2: Get authorization URL + auth_url = auth.get_authorization_url() + print("Visit the following URL to authorize your App on behalf of your X handle in a browser:") + print(auth_url) + + # Step 3: Handle callback + callback_url = input("Paste the full callback URL here: ") + + # Step 4: Exchange code for tokens + tokens = auth.fetch_token(authorization_response=callback_url) + access_token = tokens["access_token"] + + # Step 5: Create client + client = Client(access_token=access_token) + + # Step 6: Get the authenticated user's ID + me_response = client.users.get_me() + user_id = me_response.data["id"] + + # Step 7: Follow the user + payload = {"target_user_id": target_user_id} + response = client.users.follow_user(user_id, body=payload) + + print("Response code: 200") + print(json.dumps(response.data, indent=4, sort_keys=True)) + + +if __name__ == "__main__": + main() diff --git a/python/users/follow/unfollow_user.py b/python/users/follow/unfollow_user.py new file mode 100644 index 0000000..e640879 --- /dev/null +++ b/python/users/follow/unfollow_user.py @@ -0,0 +1,69 @@ +""" +Unfollow User - X API v2 +======================== +Endpoint: DELETE https://api.x.com/2/users/:source_user_id/following/:target_user_id +Docs: https://developer.x.com/en/docs/twitter-api/users/follows/api-reference/delete-users-source_user_id-following + +Authentication: OAuth 2.0 (User Context) +Required env vars: CLIENT_ID, CLIENT_SECRET +""" + +import os +import json +from xdk import Client +from xdk.oauth2_auth import OAuth2PKCEAuth + +# The code below sets the client ID and client secret from your environment variables +# To set environment variables on macOS or Linux, run the export commands below from the terminal: +# export CLIENT_ID='YOUR-CLIENT-ID' +# export CLIENT_SECRET='YOUR-CLIENT-SECRET' +client_id = os.environ.get("CLIENT_ID") +client_secret = os.environ.get("CLIENT_SECRET") + +# Replace the following URL with your callback URL, which can be obtained from your App's auth settings. +redirect_uri = "https://example.com" + +# Set the scopes +scopes = ["tweet.read", "users.read", "follows.write", "offline.access"] + +# Be sure to replace user-id-to-unfollow with the id of the user you wish to unfollow. +# You can find a user ID by using the user lookup endpoint +target_user_id = "user-id-to-unfollow" + +def main(): + # Step 1: Create PKCE instance + auth = OAuth2PKCEAuth( + client_id=client_id, + client_secret=client_secret, + redirect_uri=redirect_uri, + scope=scopes + ) + + # Step 2: Get authorization URL + auth_url = auth.get_authorization_url() + print("Visit the following URL to authorize your App on behalf of your X handle in a browser:") + print(auth_url) + + # Step 3: Handle callback + callback_url = input("Paste the full callback URL here: ") + + # Step 4: Exchange code for tokens + tokens = auth.fetch_token(authorization_response=callback_url) + access_token = tokens["access_token"] + + # Step 5: Create client + client = Client(access_token=access_token) + + # Step 6: Get the authenticated user's ID + me_response = client.users.get_me() + user_id = me_response.data["id"] + + # Step 7: Unfollow the user + response = client.users.unfollow_user(user_id, target_user_id) + + print("Response code: 200") + print(json.dumps(response.data, indent=4, sort_keys=True)) + + +if __name__ == "__main__": + main() diff --git a/python/users/repost/get_reposts_of_me.py b/python/users/repost/get_reposts_of_me.py new file mode 100644 index 0000000..977780f --- /dev/null +++ b/python/users/repost/get_reposts_of_me.py @@ -0,0 +1,78 @@ +""" +Reposts of Me - X API v2 +========================= +Endpoint: GET https://api.x.com/2/users/reposts_of_me +Docs: https://docs.x.com/x-api/users/get-reposts-of-me + +Authentication: OAuth 2.0 (User Context) +Required env vars: CLIENT_ID, CLIENT_SECRET + +Note: Returns posts from the authenticated user's timeline that have been reposted. +""" + +import os +import json +from xdk import Client +from xdk.oauth2_auth import OAuth2PKCEAuth + +# The code below sets the client ID and client secret from your environment variables +# To set environment variables on macOS or Linux, run the export commands below from the terminal: +# export CLIENT_ID='YOUR-CLIENT-ID' +# export CLIENT_SECRET='YOUR-CLIENT-SECRET' +client_id = os.environ.get("CLIENT_ID") +client_secret = os.environ.get("CLIENT_SECRET") + +# Replace the following URL with your callback URL, which can be obtained from your App's auth settings. +redirect_uri = "https://example.com" + +# Set the scopes +scopes = ["tweet.read", "users.read", "offline.access"] + +def main(): + # Step 1: Create PKCE instance + auth = OAuth2PKCEAuth( + client_id=client_id, + client_secret=client_secret, + redirect_uri=redirect_uri, + scope=scopes + ) + + # Step 2: Get authorization URL + auth_url = auth.get_authorization_url() + print("Visit the following URL to authorize your App on behalf of your X handle in a browser:") + print(auth_url) + + # Step 3: Handle callback + callback_url = input("Paste the full callback URL here: ") + + # Step 4: Exchange code for tokens + tokens = auth.fetch_token(authorization_response=callback_url) + access_token = tokens["access_token"] + + # Step 5: Create client + client = Client(access_token=access_token) + + # Step 6: Get the authenticated user's posts that have been reposted + # Post fields are adjustable. Options include: + # attachments, author_id, context_annotations, conversation_id, + # created_at, entities, geo, id, in_reply_to_user_id, lang, + # non_public_metrics, organic_metrics, possibly_sensitive, + # promoted_metrics, public_metrics, referenced_tweets, + # source, text, and withheld + all_posts = [] + for page in client.users.get_reposts_of_me( + max_results=100, + tweet_fields=["created_at", "public_metrics"] + ): + # Access data attribute (model uses extra='allow' so data should be available) + # Use getattr with fallback in case data field is missing from response + page_data = getattr(page, 'data', []) or [] + all_posts.extend(page_data) + print(f"Fetched {len(page_data)} posts (total: {len(all_posts)})") + + print(f"\nTotal Reposted Posts: {len(all_posts)}") + print(json.dumps({"data": all_posts[:5]}, indent=4, sort_keys=True)) # Print first 5 as example + + +if __name__ == "__main__": + main() diff --git a/python/webhooks/delete_webhook.py b/python/webhooks/delete_webhook.py new file mode 100644 index 0000000..341bd05 --- /dev/null +++ b/python/webhooks/delete_webhook.py @@ -0,0 +1,33 @@ +""" +Delete Webhook - X API v2 +========================= +Endpoint: DELETE https://api.x.com/2/webhooks/:webhook_id +Docs: https://docs.x.com/x-api/webhooks/introduction + +Deletes a registered webhook. After deletion, X will stop delivering events +to the associated URL. Use list_webhooks.py to find your webhook_id. + +Authentication: Bearer Token (App-only) +Required env vars: BEARER_TOKEN +""" + +import os +import json +from xdk import Client + +bearer_token = os.environ.get("BEARER_TOKEN") +client = Client(bearer_token=bearer_token) + +# Replace with the webhook ID you wish to delete. +# You can find your webhook IDs by running list_webhooks.py +webhook_id = "your-webhook-id" + +def main(): + response = client.webhooks.delete(webhook_id) + + print("Response code: 200") + print(json.dumps(response.data, indent=4, sort_keys=True)) + + +if __name__ == "__main__": + main() diff --git a/python/webhooks/list_webhooks.py b/python/webhooks/list_webhooks.py new file mode 100644 index 0000000..de1a19f --- /dev/null +++ b/python/webhooks/list_webhooks.py @@ -0,0 +1,28 @@ +""" +List Webhooks - X API v2 +======================== +Endpoint: GET https://api.x.com/2/webhooks +Docs: https://docs.x.com/x-api/webhooks/introduction + +Returns all registered webhooks for your app. Use the webhook_id from the +response when managing subscriptions or deleting a webhook. + +Authentication: Bearer Token (App-only) +Required env vars: BEARER_TOKEN +""" + +import os +import json +from xdk import Client + +bearer_token = os.environ.get("BEARER_TOKEN") +client = Client(bearer_token=bearer_token) + +def main(): + response = client.webhooks.get() + + print(json.dumps(response.data, indent=4, sort_keys=True)) + + +if __name__ == "__main__": + main() diff --git a/python/webhooks/register_webhook.py b/python/webhooks/register_webhook.py new file mode 100644 index 0000000..b1d0462 --- /dev/null +++ b/python/webhooks/register_webhook.py @@ -0,0 +1,36 @@ +""" +Register Webhook - X API v2 +=========================== +Endpoint: POST https://api.x.com/2/webhooks +Docs: https://docs.x.com/x-api/webhooks/introduction + +Registers a new webhook URL with X. When you make this request, X immediately +sends a CRC challenge GET request to your URL to verify ownership. Your server +must respond correctly before the webhook is saved — see webhook_server.py. + +Authentication: Bearer Token (App-only) +Required env vars: BEARER_TOKEN +""" + +import os +import json +from xdk import Client + +bearer_token = os.environ.get("BEARER_TOKEN") +client = Client(bearer_token=bearer_token) + +# Replace with your publicly accessible HTTPS webhook URL. +# The URL must be reachable by X at the time of registration so the CRC check can complete. +# For local development you can use a tool like ngrok to expose a local server. +webhook_url = "https://your-domain.com/webhooks" + +def main(): + payload = {"url": webhook_url} + response = client.webhooks.create(body=payload) + + print("Response code: 200") + print(json.dumps(response.data, indent=4, sort_keys=True)) + + +if __name__ == "__main__": + main() diff --git a/python/webhooks/validate_webhook.py b/python/webhooks/validate_webhook.py new file mode 100644 index 0000000..05269aa --- /dev/null +++ b/python/webhooks/validate_webhook.py @@ -0,0 +1,35 @@ +""" +Validate Webhook (Trigger CRC) - X API v2 +========================================== +Endpoint: PUT https://api.x.com/2/webhooks/:webhook_id +Docs: https://docs.x.com/x-api/webhooks/introduction + +Manually triggers a CRC (Challenge-Response Check) for the specified webhook. +Use this to re-validate your server's ownership or to re-enable a webhook that +was disabled due to failed CRC checks. Your webhook server must be running and +able to respond to the challenge before calling this endpoint. + +Authentication: Bearer Token (App-only) +Required env vars: BEARER_TOKEN +""" + +import os +import json +from xdk import Client + +bearer_token = os.environ.get("BEARER_TOKEN") +client = Client(bearer_token=bearer_token) + +# Replace with the webhook ID you wish to validate. +# You can find your webhook IDs by running list_webhooks.py +webhook_id = "your-webhook-id" + +def main(): + response = client.webhooks.validate(webhook_id) + + print("Response code: 200") + print(json.dumps(response.data, indent=4, sort_keys=True)) + + +if __name__ == "__main__": + main() diff --git a/python/webhooks/webhook_server.py b/python/webhooks/webhook_server.py new file mode 100644 index 0000000..b39fe77 --- /dev/null +++ b/python/webhooks/webhook_server.py @@ -0,0 +1,98 @@ +""" +Webhook Server - X API v2 +========================= +Docs: https://docs.x.com/x-api/webhooks/introduction + +This is a minimal webhook consumer server that handles two responsibilities: + 1. CRC (Challenge-Response Check) validation via GET — X sends a crc_token + and expects back an HMAC-SHA256 hash signed with your Consumer Secret. + This is required when registering a webhook and periodically thereafter + to confirm your server is still alive. + 2. Event delivery via POST — X sends account activity or filtered stream + events as JSON payloads to this endpoint in real time. + +To receive events you must: + 1. Run this server at a publicly accessible HTTPS URL (e.g. via ngrok). + 2. Register the URL with X: see register_webhook.py + 3. Subscribe user accounts or set stream rules to start receiving events. + +Authentication: Consumer Secret (HMAC-SHA256 for CRC validation) +Required env vars: CONSUMER_SECRET +Dependencies: flask, waitress (pip install flask waitress) +""" + +import base64 +import hashlib +import hmac +import json +import os +import sys + +from flask import Flask, jsonify, request +from waitress import serve + +app = Flask(__name__) + +# Your app's Consumer Secret — used to sign the CRC response. +# To set environment variables on macOS or Linux, run the export commands below from the terminal: +# export CONSUMER_SECRET='YOUR-CONSUMER-SECRET' +CONSUMER_SECRET = os.environ.get("CONSUMER_SECRET") +if CONSUMER_SECRET is None: + print("Missing consumer secret. Ensure CONSUMER_SECRET env var is set.") + sys.exit(1) + +HOST = "0.0.0.0" +PORT = 8080 + + +@app.route('/webhooks', methods=['GET', 'POST']) +def webhook_request(): + # Handle GET request (CRC challenge) + if request.method == 'GET': + crc_token = request.args.get('crc_token') + print(f"CRC Token received: {crc_token}") + + if crc_token is None: + print("Error: No crc_token found in the request.") + return jsonify({'error': 'No crc_token'}), 400 + + # Creates HMAC SHA-256 hash from the incoming token and your Consumer Secret + sha256_hash_digest = hmac.new( + CONSUMER_SECRET.encode('utf-8'), + msg=crc_token.encode('utf-8'), + digestmod=hashlib.sha256 + ).digest() + + # Construct response data with base64 encoded hash + response = { + 'response_token': 'sha256=' + base64.b64encode(sha256_hash_digest).decode('utf-8') + } + + # Returns properly formatted json response + return jsonify(response) + + # Handle POST request (webhook event delivery) + elif request.method == 'POST': + event_data = request.get_json() + if event_data: + print(json.dumps(event_data, indent=2)) + else: + # Log if the request body wasn't JSON or was empty + print(f"Body: {request.data.decode('utf-8')}") + + # Return 200 OK immediately to acknowledge receipt. + # X will retry delivery if it does not receive a 2xx response promptly. + return '', 200 + + return 'Method Not Allowed', 405 + + +def main(): + print("--- Starting Webhook Server ---") + print(f"Listening on {HOST}:{PORT}") + print("Expose this server publicly (e.g. via ngrok) then register the URL with register_webhook.py") + serve(app, host=HOST, port=PORT) + + +if __name__ == '__main__': + main()