Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Twitter archive browser
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Twitter Archive Browser</title>
<script src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/babel-standalone@6.15.0/babel.min.js"></script>
<style>
* {
font-family: sans-serif;
}
.tweet {
border: 1px solid grey;
border-radius: 4px;
display: block;
padding: 8px;
margin: 4px;
max-width: 600px;
}
.date {
font-size: 0.8em;
color: grey;
}
</style>
</head>
<body>
<div id="root"></div>
<script type="text/javascript">
window.YTD = {
tweet: {},
account: {},
}
</script>
<script type="text/javascript" src="./tweet.js"></script>
<script type="text/javascript" src="./account.js"></script>
<script type="text/babel">
const accountName = window.YTD.account.part0[0].account.username;
const createdStr = window.YTD.account.part0[0].account.createdAt;
const createdDate = new Date(/^[0-9]+$/.test(createdStr) ? parseInt(createdStr) : createdStr);
function Media(props) {
const data = props.data;
if (data.type == 'photo') {
return <img src={data.media_url_https} />;
}
else {
return <p>Unknown media attachment.</p>;
}
}
function Tweet(props) {
const data = props.data.tweet ? props.data.tweet : props.data;
const url = "https://twitter.com/" + accountName + "/status/" + data.id_str;
var media = [];
if (data.extended_entities) {
media = data.extended_entities.media.map((media, index) => {
return <Media data={media} key={index} />;
});
}
return (
<div className="tweet">
<p>{data.full_text}</p>
<div>{media}</div>
<a target="_blank" href={url} className="date">{data.created_at}</a>
</div>
);
}
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
year: createdDate.getFullYear(),
month: 'Jan',
searchTerm: "",
};
}
render() {
const years = [];
const now = new Date();
for (var i = createdDate.getFullYear(); i <= now.getFullYear(); i++) {
const year = i;
const onClick = () => {
this.setState((prevState) => {
return {
...prevState,
year,
};
});
};
years.push(<button key={i} onClick={onClick}>{i}</button>);
}
const months = [
'All',
'Jan',
'Feb',
'Mar',
'Apr',
'May',
'Jun',
'Jul',
'Aug',
'Sep',
'Oct',
'Nov',
'Dec',
].map((name) => {
const onClick = () => {
this.setState((prevState) => {
return {
...prevState,
month: name,
}
})
}
return <button key={name} onClick={onClick}>{name}</button>;
});
const month = this.state.month;
const year = this.state.year;
const searchTerm = this.state.searchTerm;
const tweets = window.YTD.tweet.part0;
const filteredTweets = tweets.filter((tweet) => {
if (tweet.tweet) {
tweet = tweet.tweet
}
if (searchTerm != "") {
const haystack = tweet.full_text.toLowerCase();
const needle = searchTerm.toLowerCase();
return haystack.indexOf(needle) != -1;
}
else {
return tweet.created_at.endsWith(year.toString()) &&
(month == 'All' || tweet.created_at.indexOf(month) != -1)
}
});
const maxTweets = 1000;
var elements = [];
for (var i = 0; i < Math.min(maxTweets, filteredTweets.length); i++) {
const data = filteredTweets[i];
elements.push(<Tweet key={data.id_str} data={data} />);
}
if (maxTweets < filteredTweets.length) {
elements.push(<div key="toomany">
<p>Too many results, only showing {maxTweets}</p>
</div>)
}
if (filteredTweets.length == 0) {
elements.push(<div key="none">
<p>No tweets found!</p>
</div>)
}
var title;
if (searchTerm != "") {
title = "Search: " + searchTerm;
}
else {
title = month + " " + year;
}
title += " (" + filteredTweets.length + " results)";
const handleChange = (event) => {
const newText = event.target.value;
this.setState((prevState) => {
return {
...prevState,
searchTerm: newText,
}
});
};
return (
<div>
<nav>
{years}
</nav>
<nav>
{months}
</nav>
<input type="text" onChange={handleChange} value={searchTerm} />
<h2>{title}</h2>
<div>
{elements}
</div>
</div>
);
}
}
ReactDOM.render(
<App />,
document.getElementById('root')
);
</script>
</body>
</html>
@superwills

This comment has been minimized.

Copy link

@superwills superwills commented Oct 7, 2019

Nice. I think you are loading images from the web, instead of the downloaded media, I didn't do anything about this ;)

In my fork I reduced the size of all images to 100x100 for browsing purposes.

Also added video support

@tiffany352

This comment has been minimized.

Copy link
Owner Author

@tiffany352 tiffany352 commented Oct 7, 2019

Nice. I think you are loading images from the web, instead of the downloaded media, I didn't do anything about this ;)

In my fork I reduced the size of all images to 100x100 for browsing purposes.

Also added video support

At the time I wrote this tool (mid 2018), there was seemingly no way of actually mapping the CDN URLs to the images in the dump. The ones in the dump appeared to be a content hash or something. At some point since then, they changed the archive format again. Now, they use a different naming scheme for media, and all of the media are stored inside of zip files nested inside of the archive zip. Using the new naming scheme, it's possible to map CDN URLs to media in the dump thankfully.

I'm currently working on a much more fully featured and updated version of this tool and hopefully it will be ready soon.

@vicentory

This comment has been minimized.

Copy link

@vicentory vicentory commented Oct 10, 2019

thank you, this tool is very helpful. if you can update this tool so it can view like the official type, it would be very nice.

@tiffany352

This comment has been minimized.

Copy link
Owner Author

@tiffany352 tiffany352 commented Oct 10, 2019

@vicentory @superwills

The new version I'm making is here: https://github.com/tiffany352/twitter-archive-browser

It's not a script you drop into your archive, it's instead a desktop application you install, but it has many more features and looks more like the actual Twitter website.

@Downs86

This comment has been minimized.

Copy link

@Downs86 Downs86 commented Nov 21, 2019

would you please email me one of these with the years set to 2014, onward? Downs.86@gmail.com ChrisDownsBooks.com

@EnigmaticZee

This comment has been minimized.

Copy link

@EnigmaticZee EnigmaticZee commented Jan 9, 2020

@vicentory @superwills

The new version I'm making is here: https://github.com/tiffany352/twitter-archive-browser

It's not a script you drop into your archive, it's instead a desktop application you install, but it has many more features and looks more like the actual Twitter website.

Hi, there is no mac version? Any suggestions for a mac user?

@Downs86

This comment has been minimized.

Copy link

@Downs86 Downs86 commented Jan 13, 2020

@Downs86

This comment has been minimized.

Copy link

@Downs86 Downs86 commented Jan 13, 2020

@Downs86

This comment has been minimized.

Copy link

@Downs86 Downs86 commented Jan 14, 2020

Tiffany, I don’t know if you noticed, but this page no longer works with the most recent twitter archives tweet.js . Would you please create a new version able to be a suitable replacement for the index.html file many of us need. Thank you,
Chris

@vossviola

This comment has been minimized.

Copy link

@vossviola vossviola commented Jan 17, 2020

Same problem here: This helpful solution doesn't work with an archive I dowloaded today. :(

@Downs86

This comment has been minimized.

Copy link

@Downs86 Downs86 commented Jan 19, 2020

Apparently the failure to load tweet data might have something to do with oAuth and the Twitter API but i haven't been into coding that much to know where to start on that.

@mmogithub

This comment has been minimized.

Copy link

@mmogithub mmogithub commented Jan 20, 2020

Same here. Twitter archive created today and Index.html no longer works :(
Page is blank. It seems no tweet.fuction is working.
First error is shown in "TypeError: tweet.created_at is undefined".

01
02

Blame Twitter 'cause for sure they changed something.
I hope you would be able to find a solution. Your file IS extremelly usefull.
Thanks in advance.

@Nonesuch13

This comment has been minimized.

Copy link

@Nonesuch13 Nonesuch13 commented Jan 21, 2020

This was so valuable to me as well. I hope the original contributor sees how much people used and appreciated. Not sure what Twitter had to go and change. Makes no sense they would make it so hard to use their archive files.

@Downs86

This comment has been minimized.

Copy link

@Downs86 Downs86 commented Jan 24, 2020

@tiffany352

This comment has been minimized.

Copy link
Owner Author

@tiffany352 tiffany352 commented Jan 25, 2020

@Downs86 @Nonesuch13 @mmogithub @vossviola

Sorry for the delay. I just put up an updated version that has the createdAt issue fixed and also has the issue where it's showing dates from 1969 as well.

The updated script should be compatible with both these new changes Twitter made as well as the archives you have from last year etc.

@tiffany352

This comment has been minimized.

Copy link
Owner Author

@tiffany352 tiffany352 commented Jan 25, 2020

@EnigmaticZee I just published a new version of the archive browser app that includes a Mac build. I don't have a way to test it since I don't have a Mac of my own, but you could try it and see if it works. I hope that helps!

@Downs86

This comment has been minimized.

Copy link

@Downs86 Downs86 commented Jan 25, 2020

@Nonesuch13

This comment has been minimized.

Copy link

@Nonesuch13 Nonesuch13 commented Jan 27, 2020

Thank you so much!

@mmogithub

This comment has been minimized.

Copy link

@mmogithub mmogithub commented Jan 28, 2020

Thank you so much.
It works like a charm.
You're the best, amazing work :)

@Downs86

This comment has been minimized.

Copy link

@Downs86 Downs86 commented Jan 28, 2020

@Downs86

This comment has been minimized.

Copy link

@Downs86 Downs86 commented Feb 16, 2020

@Downs86

This comment has been minimized.

Copy link

@Downs86 Downs86 commented Feb 19, 2020

@euan-gwd

This comment has been minimized.

Copy link

@euan-gwd euan-gwd commented Feb 20, 2020

@tiffany352 Thanks for doing this, good piece of code, i was going to do it myself then found this, great job. I like your implementation.

@0987e02

This comment has been minimized.

Copy link

@0987e02 0987e02 commented Jun 20, 2020

Hi! Where do I insert this? And if you can give me a free executor it would be helpful, Ive been searching the whole web for one.

@Downs86

This comment has been minimized.

Copy link

@Downs86 Downs86 commented Jul 16, 2020

@Downs86

This comment has been minimized.

Copy link

@Downs86 Downs86 commented Jul 16, 2020

@vossviola

This comment has been minimized.

Copy link

@vossviola vossviola commented Dec 31, 2020

In my archive downloaded today there is the usual tweet.js file, but also one called a tweet-part1.js which seems to contain the next bunch of tweets.
Tiffany's index file only gives me the tweets in the tweet.js; when I change line 36 to tweet-part1.js to also get the tweets from that file I get a blank page.
What could be done about that?

@vossviola

This comment has been minimized.

Copy link

@vossviola vossviola commented Feb 13, 2021

I have found a workaround: copy the content of tweet-part1.js at in tweet.js at the end of the file; then the index file works fine for the complete archive. :)

@Downs86

This comment has been minimized.

Copy link

@Downs86 Downs86 commented Mar 19, 2021

@vossviola

This comment has been minimized.

Copy link

@vossviola vossviola commented Apr 21, 2021

@owns86 hm, this index file also only works with the tweets in the "tweet.js" file, but not with those in the "tweet-part1.js" file?

@Downs86

This comment has been minimized.

Copy link

@Downs86 Downs86 commented Apr 21, 2021

@vossviola

This comment has been minimized.

Copy link

@vossviola vossviola commented Apr 21, 2021

yes, with that copy&paste workaround it works.
it seems this "scattering" happens when you pass a certain amount of tweets.

@Downs86

This comment has been minimized.

Copy link

@Downs86 Downs86 commented Apr 21, 2021

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