Skip to content

Instantly share code, notes, and snippets.

@tiffany352
Last active December 27, 2023 05:22
Show Gist options
  • Star 55 You must be signed in to star a gist
  • Fork 11 You must be signed in to fork a gist
  • 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>
@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