Skip to content

Instantly share code, notes, and snippets.

@westonruter
Last active June 30, 2023 15:29
Show Gist options
  • Save westonruter/5333030 to your computer and use it in GitHub Desktop.
Save westonruter/5333030 to your computer and use it in GitHub Desktop.
User script to add a “Group by User”, “Sort by Date”, and “Load until…” buttons to Twitter.

This user script adds “Group by User”, “Sort by Date”, and “Load until...” buttons to the Twitter web app. I'm so tired of Twitter (and Facebook and Google+) not remembering the read state for my posts. My solution in the past was GTweet which allowed me to import my tweets into Google Reader, and Google Reader would happily keep track of all of the tweets I hadn't read yet. I also wrote a user script that added buttons to Reader which allowed me to group/sort the tweets by user to allow me to much more quickly read through them; inpiration for this came from Steve Souders:

Wrote bookmarklet to sort Twitter stream by account. Love it for my early morning massive catchup. #MyWebMyWay

— souders (@souders) November 21, 2012

Well, since Google Reader is going away (R.I.P.), I needed to come up with another solution to manage keeping up with the twitter firehouse. This time I wrote a user script that hacked the Twitter web app itself to add the functionality I need. The user script will save a bookmark for when you last read your tweets, and then when you come back later and it can load up all tweets via the infinite scroll until that bookmark timestamp is reached. Then you can click the button to group the loaded tweets by user so that you can rapidly catch up:

Screenshot

// ==UserScript==
// @name Add a "Group by User", "Sort by Date", and "Load until..." buttons to Twitter
// @description I'm so tired of Twitter not remembering the read state for my Tweet. So this user script will save a bookmark for when you last read your tweets, and then come back later and load up all tweets via infinite scroll until that bookmark timestamp is reached. Also group tweets by user to more quickly read them. Inpiration comes in part from @souders: https://twitter.com/souders/statuses/271308381535494145
// @match *://twitter.com/*
// @include *://twitter.com/*
// @url https://gist.github.com/westonruter/5333030
// ==/UserScript==
(function () {
function add_style() {
var style = document.createElement('style');
style.type = 'text/css';
var css = '.timeline-buttons button { cursor: pointer; margin: 2px; } .timeline-buttons button:hover { background: black; color: white; } ';
if (style.styleSheet){
style.styleSheet.cssText = css;
}
else {
style.appendChild(document.createTextNode(css));
}
document.querySelector('head').appendChild(style);
}
function sort_stream(sorter) {
var stream_container = document.querySelector('#stream-items-id');
if (!stream_container) {
return;
}
var items = Array.prototype.slice.call(stream_container.querySelectorAll('.stream-item'));
items.sort(sorter);
while (items.length) {
var item = items.pop();
stream_container.insertBefore(item, stream_container.firstChild);
}
}
function get_oldest_tweet_timestamp(options) {
options = options || {
include_retweets: false,
include_promoted: false
};
var selector = '#stream-items-id .stream-item > .tweet';
if (!options.include_retweets) {
selector += ':not([data-retweet-id])';
}
if (!options.include_promoted) {
selector += ':not(.promoted-tweet)';
}
selector += ' .time *[data-time]';
var timestmaps = Array.prototype.map.call(document.querySelectorAll(selector), function (el) {
return +el.dataset.time;
});
return Math.min.apply(null, timestmaps);
}
function add_timeline_buttons() {
var title = document.querySelector('.js-timeline-title');
if (!title) {
return;
}
var buttons_container = title.querySelector('.timeline-buttons');
if (buttons_container) {
return;
}
buttons_container = document.createElement('span');
buttons_container.className = 'timeline-buttons';
buttons_container.style.float = 'right';
/**
* Sort by User descending, Date Descending
*/
var group_by_user_button = document.createElement('button');
group_by_user_button.className = 'sort-by-user';
group_by_user_button.appendChild(document.createTextNode('Group by User'));
group_by_user_button.addEventListener('click', function () { //group_by_user_button.setAttribute('onclick', '(' + (function () {
sort_stream(function(a, b){
var tweet_a = a.querySelector('.tweet');
var tweet_b = b.querySelector('.tweet');
if ( ! tweet_a || ! tweet_b ) {
return 0;
}
var sorted = null;
if (tweet_a.dataset.screenName === tweet_b.dataset.screenName) {
if (tweet_a.dataset.tweetId === tweet_b.dataset.tweetId) {
sorted = 0;
}
else {
sorted = +tweet_a.dataset.tweetId > +tweet_b.dataset.tweetId ? -1 : 1;
}
}
else {
sorted = tweet_a.dataset.screenName.toLowerCase() < tweet_b.dataset.screenName.toLowerCase() ? -1 : 1;
}
return sorted;
});
}); //}).toString() + ')()');
buttons_container.appendChild(group_by_user_button);
/**
* Load Until buton
*/
var timeout_id = null;
var load_until_button = document.createElement('button');
load_until_button.className = 'load-until';
load_until_button.appendChild(document.createTextNode('Load until…'));
load_until_button.addEventListener('click', function () { //load_until_button.setAttribute('onclick', '(' + (function () {
var bookmarked_timestamp = localStorage.getItem('bookmarkedTweetTimestamp');
if (bookmarked_timestamp) {
bookmarked_timestamp = new Date(parseInt(bookmarked_timestamp.toString() + '000', 10));
}
else {
bookmarked_timestamp = new Date(new Date().valueOf() - 24 * 60 * 60 * 1000);
}
bookmarked_timestamp = prompt('Load tweets until: ', bookmarked_timestamp.toISOString());
if (bookmarked_timestamp) {
bookmarked_timestamp = Math.floor(Date.parse(bookmarked_timestamp) / 1000);
var new_bookmarked_timestamp = prompt(
'New bookmark timestamp for next time (cancel to preserve old one):',
new Date().toISOString()
);
if (new_bookmarked_timestamp) {
localStorage.setItem('bookmarkedTweetTimestamp', Math.floor(Date.parse(new_bookmarked_timestamp) / 1000));
}
var scroll_infinite_to_find;
scroll_infinite_to_find = function () {
timeout_id = null;
if (get_oldest_tweet_timestamp() < bookmarked_timestamp) {
console.info('Passed oldest tweet');
return;
}
// No more tweets to load
if (document.querySelector('.stream-footer .timeline-end:not(.has-more-items)')) {
console.info('Aborting because fell off the bottom');
return;
}
document.querySelector('.stream-footer').scrollIntoView();
timeout_id = setTimeout(function () {
scroll_infinite_to_find();
}, 1000);
};
scroll_infinite_to_find();
}
}); //}).toString() + ')()');
buttons_container.appendChild(load_until_button);
title.appendChild(buttons_container);
}
add_style();
add_timeline_buttons();
}());
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment