Last active
November 15, 2021 02:46
-
-
Save porglezomp/34be1368089c721291d309d2d81fe560 to your computer and use it in GitHub Desktop.
Show your pinned lists in your twitter top bar.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ==UserScript== | |
// @name Twitter Pinned Lists | |
// @description Show your pinned lists in your twitter top bar. | |
// @version 1.1 | |
// @grant none | |
// @include https://twitter.com/* | |
// @include https://mobile.twitter.com/* | |
// ==/UserScript== | |
// USAGE: Visit your lists page for a few seconds, and this will learn your pinned lists. | |
// Then it should be able to insert the pinned lists into the top bar. | |
const PRIMARY_COLUMN = "main [data-testid='primaryColumn']"; | |
const LOCALSTORAGE_KEY = "pinnedLists#greaseMonkey@porglezomp"; | |
// Twitter is a rude SPA that doesn't load the header promptly... | |
// This will just keep trying to find an element until you see it! | |
function eventuallyGetElement(selector) { | |
return new Promise((resolve, reject) => { | |
function find() { | |
console.log(`Trying to find ${selector}`); | |
let element = document.querySelector(selector); | |
if (element) return resolve(element); | |
window.setTimeout(find, 1000); | |
} | |
find(); | |
}); | |
} | |
// We have the pinned lists saved in local storage, | |
// so we don't rely on the user going and looking at their lists page each session. | |
let pinnedLists = JSON.parse(localStorage.getItem(LOCALSTORAGE_KEY) || '[]'); | |
// Twitter broke the storage/API I was using to find the list names... | |
// So we'll discover them when you visit your lists page and see the | |
// pinned lists there. Save them to local storage when you find them. | |
setInterval(() => { | |
try { | |
// If we're not on a "lists" page, then we don't need to try to discover lists | |
if (!window.location.pathname.endsWith("/lists")) { | |
return; | |
} | |
const pinnedListHeader = | |
Array.from(document.querySelectorAll('h2')) | |
.find(x => x.textContent.startsWith("Pinned Lists")); | |
// If there is no "Pinned Lists" header then this might be another user's lists page | |
if (!pinnedListHeader) { | |
return; | |
} | |
// The actual pinned lists are a sibling of an ancestor... | |
const pinnedListsContainer = pinnedListHeader | |
.parentElement.parentElement.parentElement.nextSibling; | |
const pinnedListElements = pinnedListsContainer.querySelectorAll('li'); | |
// Get the info and save it into localStorage for later loads. | |
let pinned = []; | |
for (const list of pinnedListElements) { | |
const a = list.querySelector('a'); | |
pinned.push({ name: a.textContent, href: a.href }); | |
} | |
// Only overwrite for non-empty lists, | |
// because sometimes we get an empty list due to a partially loaded lists page. | |
console.log("Saving", pinned); | |
if (pinned.length) { | |
pinnedLists = pinned; | |
localStorage.setItem(LOCALSTORAGE_KEY, JSON.stringify(pinnedLists)); | |
} | |
} catch (err) { | |
console.log(err); | |
} | |
}, 2000); | |
// Insert the list links into the page header. | |
function injectLinks(header, headerLine, lists) { | |
if (headerLine.childElementCount != 1) return; | |
headerLine.style["flex-direction"] = "row"; | |
headerLine.style["gap"] = "15px"; | |
headerLine.style["align-items"] = "baseline"; | |
let i = 1; | |
for (const list of lists) { | |
let element = document.createElement("h2"); | |
element.classList = header.classList; | |
let link = document.createElement("a"); | |
element.appendChild(link); | |
link.href = list.href; | |
link.text = list.name; | |
link.style["color"] = getComputedStyle(header).color; | |
link.style["font-size"] = "0.9em"; | |
headerLine.appendChild(element); | |
i += 1; | |
} | |
} | |
// Every few seconds, try to inject the links into the header. | |
// We have to do this repeatedly, because they go away when users navigate pages. | |
async function findAndInject() { | |
try { | |
let primaryColumn = await eventuallyGetElement(PRIMARY_COLUMN); | |
let titleContainer = primaryColumn.firstElementChild.firstElementChild; | |
let header = titleContainer.querySelector('h2'); | |
let headerLine = header.parentElement; | |
injectLinks(header, headerLine, pinnedLists); | |
} catch (err) { | |
console.log(err); | |
} | |
setTimeout(findAndInject, 1000); | |
} | |
findAndInject(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment