Skip to content

Instantly share code, notes, and snippets.

@baptx
Last active July 29, 2022 10:00
Show Gist options
  • Star 16 You must be signed in to star a gist
  • Fork 4 You must be signed in to fork a gist
  • Save baptx/1525f338d93fa01db4e0 to your computer and use it in GitHub Desktop.
Save baptx/1525f338d93fa01db4e0 to your computer and use it in GitHub Desktop.
Twitter API 1.1 tweets / favorites (likes) / following / followers backup in web browser
/* Twitter API 1.1 tweets / favorites (likes) / following / followers backup in web browser
* Get your access keys to use Twitter API 1.1: https://dev.twitter.com/docs/auth/tokens-devtwittercom
* You can change Twitter API URL and Twitter screen_name, then execute script from a trusted web page without CSP protection like about:blank in a web browser console (F12 or Ctrl+Shift+K shortcut)
* A textarea will appear so you can copy/paste to save data as a CSV file or search tweets / users in your web browser (Ctrl+F shortcut)
* You can then view your backup in a spreadsheet editor like LibreOffice Calc
* You can also compare the backup with another one to see who unfollowed you, who changed their Twitter username by looking at the user ID or which tweet you retweeted / favorited was deleted (e.g. with the Linux diff command)
*
* Note about the tweets backup:
* Usually you will search tweets that you retweeted using Twitter web version (https://twitter.com/search) with a search like "from:your_username filter:nativeretweets keyword"
* But it is limited to the retweets of the last 7 days, like for the free version of the search API (https://developer.twitter.com/en/docs/tweets/search/overview/standard)
* An alternative is to search tweets in your user timeline with this script but it is limited to your last 3200 tweets (including retweets and replies)
* This script can be combined with the Twitter feature to backup data, which is not limited to your last 3200 tweets but you can only request a backup every 30 days
* To find tweets that you retweeted or favorited / liked from a specific person, you can open the CSV file with LibreOffice Calc, click on the column you want to search and press Ctrl+H to search a username
*/
var url = "https://api.twitter.com/1.1/statuses/user_timeline.json"
//var url = "https://api.twitter.com/1.1/favorites/list.json";
//var url = "https://api.twitter.com/1.1/friends/list.json";
//var url = "https://api.twitter.com/1.1/followers/list.json";
var accessor = {
token: "XXX",
tokenSecret: "XXX",
consumerKey : "XXX",
consumerSecret: "XXX"
};
var message = {
action: url,
method: "GET",
parameters: {
screen_name: "jack",
count: 200,
callback: "getJSONP"
}
};
var out = [];
var length = -1;
var start = 0;
var next = -1;
var res;
function getJSONP(data)
{
res = data;
}
function loadAPI_tweets()
{
if (length != 1)
{
if (next != -1) {
message.parameters.max_id = next;
}
OAuth.completeRequest(message, accessor);
OAuth.SignatureMethod.sign(message, accessor);
var script3 = document.createElement("script");
script3.setAttribute("src", url + '?' + OAuth.formEncode(message.parameters));
document.body.appendChild(script3);
script3.addEventListener("load", function() {
length = res.length;
for (var i = 0; i < length; ++i) {
var text, name, id;
if (res[i].retweeted_status) {
text = res[i].retweeted_status.full_text;
name = res[i].retweeted_status.user.screen_name;
id = res[i].retweeted_status.user.id;
} else {
text = res[i].full_text;
name = res[i].user.screen_name;
id = res[i].user.id;
}
out[start + i] = res[i].created_at + '\t'
+ name + '\t"""'
+ id + '"""\t"' // backup user ID to track a user who deleted a tweet you retweeted / favorited (even if the user changed his username)
+ text.replace(/"/g, '""') + '"\t"""'
+ res[i].id_str + '"""';
// CSV with tab separator: quote to allow multiline string; escape quote string delimiter with double quote; display large numbers correctly as string with triple quote
}
next = res[length - 1].id_str;
start += length - 1;
loadAPI_tweets();
});
}
else
{
displayData();
}
}
function loadAPI_users()
{
if (length == -1 || res.next_cursor_str != 0) {
message.parameters.cursor = next;
OAuth.completeRequest(message, accessor);
OAuth.SignatureMethod.sign(message, accessor);
var script3 = document.createElement("script");
script3.setAttribute("src", url + '?' + OAuth.formEncode(message.parameters));
document.body.appendChild(script3);
script3.addEventListener("load", function() {
length = res.users.length;
for (var i = 0; i < length; ++i) {
out[start + i] = res.users[i].screen_name + '\t'
+ res.users[i].name.replace(/"/g, '""') + '\t"""'
+ res.users[i].id + '"""';
}
next = res.next_cursor_str;
start += length;
loadAPI_users();
});
}
else {
displayData();
}
}
function displayData()
{
var box = document.createElement("textarea");
box.value = out.join('\n');
document.body.appendChild(box);
}
var script = document.createElement("script");
script.setAttribute("src", "https://pastebin.com/raw/HFjqYLdG"); // http://oauth.googlecode.com/svn/code/javascript/oauth.js (down)
document.body.appendChild(script);
script.addEventListener("load", function() {
var script2 = document.createElement("script");
script2.setAttribute("src", "https://pastebin.com/raw/M0N8JKwf"); // http://pajhome.org.uk/crypt/md5/sha1.js
document.head.appendChild(script2);
script2.addEventListener("load", function() {
if (url == "https://api.twitter.com/1.1/statuses/user_timeline.json" || url == "https://api.twitter.com/1.1/favorites/list.json") {
message.parameters.include_rts = true; // include retweets
message.parameters.tweet_mode = "extended"; // needed to get full tweet after API change
loadAPI_tweets();
}
else {
loadAPI_users();
}
});
});
@devmbm
Copy link

devmbm commented Nov 14, 2020

Hi. non-programmer here so I stumbled with this but cannot make it work, both Firefox and Chrome, when running this on "about:blank", won't load the script because of "https://pastebin.com/raw/HFjqYLdG". Thanks for sharing! I used this months ago and it was able to made it work, this time I am not being able to replicate it..

capt_api_tw_backup

@baptx
Copy link
Author

baptx commented Nov 18, 2020

Hi @devmbm, I got this error too. This seems to be due to a recent change of Pastebin, because of the HTTP response header X-Content-Type-Options: nosniff.
A way to make the script work would be host oauth.js and sha1.js dependencies somewhere else, on your own server for example if you have one (there would be a security advantage also if you don't trust a third-party like Pastebin).
Or you can just keep using the script as is and bypass the header with an open source browser addon like "Header Editor" (compatible with Firefox and Chrome / Chromium: https://github.com/FirefoxBar/HeaderEditor).

Here is how to configure the removal of the header when you create a new rule:

  1. For "Rule type", select Modify response header
  2. For "Match type", select Domain with the "Match rules" value pastebin.com
  3. For "Header name", enter x-content-type-options

You can keep everything else to the default value. For the name at the top, you can write something like remove X-Content-Type-Options and for the group name, something like Pastebin MIME nosniff bypass.
Keep in mind to disable the rule or the addon when you don't use the script since it is a security header that can be useful in some cases.

@devmbm
Copy link

devmbm commented Nov 21, 2020

That is spot on 👌 thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment