Skip to content

Instantly share code, notes, and snippets.

@marysaka
Created November 15, 2024 15:20
Show Gist options
  • Save marysaka/bc6037e4715eba961a3c440e82703c28 to your computer and use it in GitHub Desktop.
Save marysaka/bc6037e4715eba961a3c440e82703c28 to your computer and use it in GitHub Desktop.
Some Python script to delete all your tweets (or until a certain cutoff date) based on Twitter Archive (tweet.js)
import requests
import json
import sys
import time
from requests.models import Response
# Common Twitter WebApp Bearer
USER_AUTH_TOKEN = "AAAAAAAAAAAAAAAAAAAAANRILgAAAAAAnNwIzUejRCOuH5E6I8xnZz4puTs%3D1Zv7ttfk8LF81IUq16cHjhLTvJu4FA33AGWWjCpTnA"
# Credentials
# Copy of the cookie passed in requests (usually start with "night_mode=")
USER_COOKIE = "<secret>"
# Copy of the header value of "x-csrf-token"
USER_CSRF = "<secret>"
RETRY_BASE_SECONDS = 5
DELETE_GRAPHQL_METHOD_ID = "VaenaVgh5q5ih7kvyVjgtg"
DELETE_ENDPOINT_URL = (
f"https://x.com/i/api/graphql/{DELETE_GRAPHQL_METHOD_ID}/DeleteTweet"
)
def delete_tweet_by_id(
tweet_id: str, auth_token: str, cookie: str, csrf: str
) -> Response:
if "ct0=" not in cookie:
cookie = cookie + "; ct0=" + csrf
headers = {
"User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:132.0) Gecko/20100101 Firefox/132.0",
"Content-Type": "application/json",
"x-twitter-auth-type": "OAuth2Session",
"x-twitter-client-language": "en-GB",
"x-twitter-active-user": "yes",
"Origin": "https://x.com",
"Referer": "https://x.com",
"Cookie": cookie,
"x-csrf-token": csrf,
"authorization": "Bearer " + auth_token,
}
data = {
"variables": json.dumps({"tweet_id": tweet_id, "dark_request": False}),
"queryId": DELETE_GRAPHQL_METHOD_ID,
}
return requests.post(DELETE_ENDPOINT_URL, headers=headers, json=data)
if len(sys.argv) < 2:
print("Usage <tweet.js> [cutoff_date]")
print("example cutoff_date: Mon Oct 20 01:44:42 +0000 2025")
sys.exit(1)
raw_tweets = open(sys.argv[1], "rb").read().decode("utf-8")
cutoff_date = None
if len(sys.argv) >= 3:
cutoff_date = time.strptime(sys.argv[2], "%a %b %d %H:%M:%S %z %Y")
json_start_index = raw_tweets.find("[")
if json_start_index == -1:
print("Start of tweet.js data not found (malformed file?s)")
sys.exit(1)
tweets = json.loads(raw_tweets[json_start_index:])
for data in tweets:
tweet: str = data["tweet"]
created_at_date = time.strptime(tweet["created_at"], "%a %b %d %H:%M:%S %z %Y")
if not cutoff_date or created_at_date < cutoff_date:
should_retry = True
retry_seconds = RETRY_BASE_SECONDS
while should_retry:
res = delete_tweet_by_id(
tweet["id_str"],
auth_token=USER_AUTH_TOKEN,
cookie=USER_COOKIE,
csrf=USER_CSRF,
)
print(f"{tweet['id_str']} {res.text}")
should_retry = res.status_code == 429
if should_retry:
error_message = res.json()["errors"][0]["message"]
# Twitter GraphQL API will tell you you are rate limited to 500 but it will actually perform deletion
# This behaviour seems to change when you hit the limit 1000 marker resulting in global account rate limiting.
# XXX: November 2024 update: Rate limits seem completely gone on Twitter now?!
if (
error_message
== "Rate limit exceeded for feature graphql_operation_mutation with limit 500"
):
break
print(f"{tweet['id_str']} RETRYING IN {retry_seconds} seconds")
time.sleep(retry_seconds)
retry_seconds += RETRY_BASE_SECONDS
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment