Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 20 additions & 57 deletions python/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,45 +19,25 @@ export CONSUMER_SECRET='your_consumer_secret'
## Examples

### Posts
- posts/counts_full_archive.py
- posts/counts_recent.py
- posts/create_post.py
- posts/create_tweet.py
- posts/delete_post.py
- posts/delete_tweet.py
- posts/full_archive_tweet_counts.py
- posts/full-archive-search.py
- posts/get_tweets_with_bearer_token.py
- posts/get_tweets_with_user_context.py
- posts/like_a_tweet.py
- posts/like.py
- posts/liked_posts.py
- posts/liked_tweets.py
- posts/liking_users.py
- posts/lookup.py
- posts/quote_posts.py
- posts/quote_tweets.py
- posts/recent_search.py
- posts/recent_tweet_counts.py
- posts/repost.py
- posts/reposted_by.py
- posts/retweet_a_tweet.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_full_archive.py
- posts/search_all.py
- posts/search_recent.py
- posts/undo_a_retweet.py
- posts/undo_repost.py
- posts/unlike_a_tweet.py
- posts/unlike.py

### Users
- users/block_a_user.py
- users/block.py
- users/blocked.py
- users/followers_lookup.py
- users/followers.py
- users/following_lookup.py
- users/following.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
Expand All @@ -66,37 +46,29 @@ export CONSUMER_SECRET='your_consumer_secret'
- users/lookup.py
- users/me.py
- users/mute_a_user.py
- users/mute.py
- users/muted.py
- users/unblock_a_user.py
- users/unblock.py
- users/unmute_a_user.py
- users/unmute.py

### Timelines
- timelines/home_timeline.py
- timelines/get_mentions.py
- timelines/get_posts.py
- timelines/get_timeline.py
- timelines/reverse-chron-home-timeline.py
- timelines/user_mentions.py
- timelines/user_posts.py
- timelines/user_tweets.py

### Streams
- streams/filtered_stream.py
- streams/sampled_stream.py
- streams/sampled-stream.py

### Lists
- lists/add_member.py
- lists/create_a_list.py
- lists/create.py
- lists/delete_a_list.py
- lists/delete.py
- lists/add_list_member.py
- lists/create_list.py
- lists/delete_list.py
- lists/follow_list.py
- lists/list-followers-lookup.py
- lists/list-lookup-by-id.py
- lists/list-member-lookup.py
- lists/List-Tweets.py
- lists/lookup.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
Expand All @@ -110,41 +82,32 @@ export CONSUMER_SECRET='your_consumer_secret'
### Bookmarks
- bookmarks/bookmarks_lookup.py
- bookmarks/create_bookmark.py
- bookmarks/create.py
- bookmarks/delete_bookmark.py
- bookmarks/delete.py
- bookmarks/lookup.py

### Spaces
- spaces/lookup.py
- spaces/search_spaces.py
- spaces/search.py
- spaces/spaces_lookup.py

### 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/lookup.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/send.py

### Media
- media/media_upload_v2.py
- media/upload.py

### Compliance
- compliance/create_compliance_job.py
- compliance/create_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

### Usage
- usage/get_usage_tweets.py
- usage/get_usage.py

152 changes: 60 additions & 92 deletions python/bookmarks/bookmarks_lookup.py
Original file line number Diff line number Diff line change
@@ -1,103 +1,71 @@
import base64
import hashlib
"""
Bookmarks Lookup - X API v2
===========================
Endpoint: GET https://api.x.com/2/users/:id/bookmarks
Docs: https://developer.x.com/en/docs/twitter-api/bookmarks/api-reference/get-users-id-bookmarks

Authentication: OAuth 2.0 (User Context)
Required env vars: CLIENT_ID, CLIENT_SECRET
"""

import os
import re
import json
import requests
from requests.auth import AuthBase, HTTPBasicAuth
from requests_oauthlib import OAuth2Session
from xdk import Client
from xdk.oauth2_auth import OAuth2PKCEAuth

# First, you will need to enable OAuth 2.0 in your App’s auth settings in the Developer Portal to get your client ID.
# Inside your terminal you will need to set an enviornment variable
# export CLIENT_ID='your-client-id'
# 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")

# If you have selected a type of App that is a confidential client you will need to set a client secret.
# Confidential Clients securely authenticate with the authorization server.

# Inside your terminal you will need to set an enviornment variable
# export CLIENT_SECRET='your-client-secret'

# Remove the comment on the following line if you are using a confidential client
# client_secret = os.environ.get("CLIENT_SECRET")
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://www.example.com"
redirect_uri = "https://example.com"

# Set the scopes
scopes = ["bookmark.read", "tweet.read", "users.read", "offline.access"]

# Create a code verifier
code_verifier = base64.urlsafe_b64encode(os.urandom(30)).decode("utf-8")
code_verifier = re.sub("[^a-zA-Z0-9]+", "", code_verifier)

# Create a code challenge
code_challenge = hashlib.sha256(code_verifier.encode("utf-8")).digest()
code_challenge = base64.urlsafe_b64encode(code_challenge).decode("utf-8")
code_challenge = code_challenge.replace("=", "")

# Start an OAuth 2.0 session
oauth = OAuth2Session(client_id, redirect_uri=redirect_uri, scope=scopes)

# Create an authorize URL
auth_url = "https://twitter.com/i/oauth2/authorize"
authorization_url, state = oauth.authorization_url(
auth_url, code_challenge=code_challenge, code_challenge_method="S256"
)

# Visit the URL to authorize your App to make requests on behalf of a user
print(
"Visit the following URL to authorize your App on behalf of your Twitter handle in a browser:"
)
print(authorization_url)

# Paste in your authorize URL to complete the request
authorization_response = input(
"Paste in the full URL after you've authorized your App:\n"
)

# Fetch your access token
token_url = "https://api.x.com/2/oauth2/token"

# The following line of code will only work if you are using a type of App that is a public client
auth = False

# If you are using a confidential client you will need to pass in basic encoding of your client ID and client secret.

# Please remove the comment on the following line if you are using a type of App that is a confidential client
# auth = HTTPBasicAuth(client_id, client_secret)

token = oauth.fetch_token(
token_url=token_url,
authorization_response=authorization_response,
auth=auth,
client_id=client_id,
include_client_id=True,
code_verifier=code_verifier,
)

# Your access token
access = token["access_token"]

# Make a request to the users/me endpoint to get your user ID
user_me = requests.request(
"GET",
"https://api.x.com/2/users/me",
headers={"Authorization": "Bearer {}".format(access)},
).json()
user_id = user_me["data"]["id"]

# Make a request to the bookmarks url
url = "https://api.x.com/2/users/{}/bookmarks".format(user_id)
headers = {
"Authorization": "Bearer {}".format(access),
"User-Agent": "BookmarksSampleCode",
}
response = requests.request("GET", url, headers=headers)
if response.status_code != 200:
raise Exception(
"Request returned an error: {} {}".format(response.status_code, response.text)
def main():
# Step 1: Create PKCE instance
auth = OAuth2PKCEAuth(
client_id=client_id,
client_secret=client_secret,
redirect_uri=redirect_uri,
scope=scopes
)
print("Response code: {}".format(response.status_code))
json_response = response.json()
print(json.dumps(json_response, indent=4, sort_keys=True))

# 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 authenticated user ID
user_me = client.users.get_me()
user_id = user_me.data["id"]

# Step 7: Get bookmarks with automatic pagination
all_bookmarks = []
for page in client.users.get_bookmarks(
user_id,
max_results=100,
tweetfields=["created_at"]
):
all_bookmarks.extend(page.data)
print(f"Fetched {len(page.data)} bookmarks (total: {len(all_bookmarks)})")

print(f"\nTotal Bookmarks: {len(all_bookmarks)}")
print(json.dumps({"data": all_bookmarks[:5]}, indent=4, sort_keys=True)) # Print first 5 as example

if __name__ == "__main__":
main()
54 changes: 0 additions & 54 deletions python/bookmarks/create.py

This file was deleted.

Loading
Loading