Skip to content

Instantly share code, notes, and snippets.

@tiffany352
Last active May 1, 2024 11:36
Show Gist options
  • Save tiffany352/9ee7e0d4fd7e08ede9d0314df9eab672 to your computer and use it in GitHub Desktop.
Save tiffany352/9ee7e0d4fd7e08ede9d0314df9eab672 to your computer and use it in GitHub Desktop.
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>
@mmogithub
Copy link

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
Copy link

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
Copy link

Downs86 commented Jan 24, 2020 via email

@tiffany352
Copy link
Author

@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
Copy link
Author

@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
Copy link

Downs86 commented Jan 25, 2020 via email

@Nonesuch13
Copy link

Thank you so much!

@mmogithub
Copy link

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

@Downs86
Copy link

Downs86 commented Jan 28, 2020 via email

@Downs86
Copy link

Downs86 commented Feb 16, 2020 via email

@Downs86
Copy link

Downs86 commented Feb 19, 2020 via email

@euan-gwd
Copy link

@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
Copy link

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
Copy link

Downs86 commented Jul 16, 2020 via email

@Downs86
Copy link

Downs86 commented Jul 16, 2020 via email

@vossviola
Copy link

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
Copy link

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
Copy link

Downs86 commented Mar 19, 2021 via email

@vossviola
Copy link

@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
Copy link

Downs86 commented Apr 21, 2021 via email

@vossviola
Copy link

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

@Downs86
Copy link

Downs86 commented Apr 21, 2021 via email

@vossviola
Copy link

twitter has changed the structure of the archive again, and so this helpful file is not working anymore. sigh

@Downs86
Copy link

Downs86 commented Dec 21, 2022

@vossviola It’s The Difference Of The Former tweet.js File and The New One Which Contains The PArt WHeir A TWēEt Is SupposEd To Bē EditAble.

So the quickest rout I saw is selecting the first example of the addition/difference, and using a Keyboard Command/Control To Select All Similar BrackEts And THeir Contents, and then simply deleting them so that the formatting of The New tweet.js File be as The Former One, YEt: Still Contain All TWēEt Data.

The Other (More PreferABLE Option) is Adapting The index.html File of The Twitter Archive Browser To CompensATE For The Addition of “Edit TWēEt” Section. (But I couldn’t EVEn Select all within similar BrackEts Using MY BeLovEd Atom Web Developer App (Attacked Devices..).

@Downs86
Copy link

Downs86 commented Dec 21, 2022

Thanks!

@vossviola
Copy link

Thank you for pointing to the difference!
I gave it a short try to delete those sections -- which wasn't very successful. 😎
So I am hoping that somebody will be able and willing to update the index.html file. 🤞

@vossviola
Copy link

I have found out that Excel can now import json files.
there is only a little change needed to the twitter js files.
see https://www.zdnet.com/paid-content/article/want-to-analyse-your-tweets-how-to-import-twitter-json-data-exports-into-excel/

@sHall0w
Copy link

sHall0w commented May 28, 2023

Hello! I'm very unexperienced in this, but could someone please tell me how do i use this? It would mean a lot to me! (I mostly need it to browse through my DMs.)
I have downloaded the file, put it in the same folder where my Twitter data is (already extracted), but I'm not sure what to do next, as it does nothing. I would really appreciate the help <3

@vossviola
Copy link

As far as I can see it this script does not work any more because of the new structure of the Twitter archive files. :(

@sHall0w
Copy link

sHall0w commented May 29, 2023

ohh i understand...:( thank you for the reply!<3

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