Created
August 6, 2024 05:19
-
-
Save rakslice/ca850aba27394260125bfd1dd9db182d to your computer and use it in GitHub Desktop.
Userscript to redirect mastodon remote links matching the usual pattern to the local instance
This file contains 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 make mastodon links internal | |
// @namespace http://rakslice.net/userscripts/mastodon_internal_links | |
// @version 2024-08-05 | |
// @description within the mastodon advanced UI, make links to other posts on external sites instead link to the post within the UI | |
// @author You | |
// @match https://mastodon.social/deck/* | |
// @icon https://www.google.com/s2/favicons?sz=64&domain=mastodon.social | |
// @grant none | |
// @require http://code.jquery.com/jquery-3.4.1.min.js | |
// ==/UserScript== | |
(function() { | |
'use strict'; | |
var ourJQuery = jQuery; | |
jQuery.noConflict(); | |
var $ = jQuery; | |
var marker = "mmliprocessed"; | |
// e.g. https://techhub.social/@Techmeme/112910951270387920 => https://mastodon.social/deck/@[email protected]/112910951333258455 | |
var fedilinkre = /https:\/\/([^/]+)\/(@[^/]+)\/([0-9]+)$/; | |
var hash_post_url = null; | |
function getScroller(article) { | |
var cur = article; | |
while (cur.getAttribute("class") != "scrollable") { | |
cur = cur.parentNode; | |
if (cur == null) break; | |
} | |
return cur; | |
} | |
function processArticle(article) { | |
if(window.location.hash) { | |
// let props = getReactProps(menuBtn.parentElement, menuBtn); | |
let status = $("div.status", article)[0]; | |
var action_bar = $("div.status__action-bar", status)[0]; | |
var more_button = $("button", action_bar)[4]; | |
var props = getReactProps(action_bar, more_button); | |
//console.log("props", props); | |
for (let i = 0; i < props.items.length; i++) { | |
let item = props.items[i]; | |
//console.log(item); | |
if (item && item.hasOwnProperty("href")) { | |
var href = item.href; | |
//console.log("href", href); | |
if (href == hash_post_url) { | |
console.log("Found post", href); | |
function scroll_later(article, scroller) { | |
console.log("scroller", scroller); | |
setTimeout(function() { | |
console.log("SCROLL TO ARTICLE NOW"); | |
scroller.scrollTo(0,article.offsetTop); | |
}, 1000); | |
} | |
scroll_later(article, getScroller(article)); | |
} | |
break; | |
} | |
} | |
} | |
if (article.hasAttribute(marker)) return; | |
//console.log("article", article); | |
$("a", article).each(function(i, link) { | |
var href=link.getAttribute("href"); | |
if (!href.startsWith("https://")) return; | |
var found = href.match(fedilinkre); | |
if (found != null) { | |
console.log("link", href); | |
var hostname = found[1]; | |
var at_user = found[2]; | |
var post_num = found[3]; | |
//var new_href = "/deck/" + at_user + "@" + hostname + "/" + post_num; | |
var new_href = "/deck/" + at_user + "@" + hostname + "/#" + post_num ; | |
console.log("to ", new_href); | |
link.setAttribute("href", new_href); | |
} | |
}); | |
article.setAttribute(marker, "1"); | |
} | |
function feedChange(mutations) { | |
mutations.forEach(function(mutation) { | |
for (var i = 0; i < mutation.addedNodes.length; i++) { | |
var node = mutation.addedNodes[i]; | |
if (node.nodeType == 1 /* element */ && node.nodeName == "ARTICLE") { | |
processArticle(node); | |
} | |
} | |
}) | |
} | |
function processFeed(feed) { | |
//console.log("processFeed", feed); | |
// hookup listening for future post items added to the feed | |
var observer = new MutationObserver(feedChange); | |
observer.observe(feed, { childList: true }); | |
// process any post items already in the feed | |
for (var i = 0; i < feed.children.length; i++) { | |
//console.log(i); | |
var article = feed.children[i]; | |
if (article.nodeType == 1 /* element */ && article.nodeName == "ARTICLE") { | |
//console.log(article.nodeName); | |
processArticle(article); | |
} | |
} | |
} | |
function waitForSelector(selector, baseElement) { | |
/* | |
Async. Find the first element matching the selector on the given baseElement (i.e. like baseElement.querySelector(selector)) but if it doesn't match anything right away watch the element for changes until it does. | |
Based on https://stackoverflow.com/a/61511955/60422 | |
Usage: | |
waitForSelector('.some-class', base).then((elm) => { | |
console.log('Got element', elm); | |
}); | |
*/ | |
// FIXME cancellation? | |
return new Promise(resolve => { | |
var match = baseElement.querySelector(selector); | |
if (match) { | |
return resolve(match); | |
} | |
const observer = new MutationObserver(mutations => { | |
var match = baseElement.querySelector(selector); | |
if (match) { | |
observer.disconnect(); | |
resolve(match); | |
} | |
}); | |
observer.observe(baseElement, { | |
childList: true, | |
subtree: true | |
}); | |
}); | |
} | |
function getReactProps(parent/*: Element*/, target /*: Element*/)/*: any*/ { | |
// from https://stackoverflow.com/questions/70507318/how-to-get-react-element-props-from-html-element-with-javascript/74240138#74240138 | |
// got there from https://stackoverflow.com/questions/58369378/acessing-reactjs-props-states-via-greasemonkey | |
const keyof_ReactProps = Object.keys(parent).find(k => k.startsWith("__reactProps$")); | |
const symof_ReactFragment = Symbol.for("react.fragment"); | |
//console.log("reactfragment", symof_ReactFragment); | |
//Find the path from target to parent | |
let path = []; | |
let elem = target; | |
while (elem !== parent) { | |
let index = 0; | |
for (let sibling = elem; sibling != null;) { | |
if (sibling[keyof_ReactProps]) index++; | |
sibling = sibling.previousElementSibling; | |
} | |
path.push({ child: elem, index }); | |
elem = elem.parentElement; | |
} | |
//Walk down the path to find the react state props | |
let state = elem[keyof_ReactProps]; | |
//console.log("getReactProps() state", state); | |
for (let i = path.length - 1; i >= 0 && state != null; i--) { | |
//Find the target child state index | |
let childStateIndex = 0, childElemIndex = 0; | |
while (childStateIndex < state.children.length) { | |
let childState = state.children[childStateIndex]; | |
//console.log("childState", childStateIndex, childState); | |
if (childState instanceof Object) { | |
//Fragment children are inlined in the parent DOM element | |
let isFragment = childState.type === symof_ReactFragment && childState.props.children.length; | |
childElemIndex += isFragment ? childState.props.children.length : 1; | |
if (childElemIndex === path[i].index) break; | |
} | |
childStateIndex++; | |
} | |
let childState = state.children[childStateIndex] ?? (childStateIndex === 0 ? state.children : null); | |
state = childState?.props; | |
//console.log("substate", state); | |
elem = path[i].child; | |
} | |
return state; | |
} | |
function colChange(mutations) { | |
mutations.forEach(function(mutation) { | |
for (var i = 0; i < mutation.addedNodes.length; i++) { | |
var node = mutation.addedNodes[i]; | |
if (node.nodeType == 1 /* element */ && node.nodeName == "DIV") { | |
if (node.getAttribute("class") == "column" && node.getAttribute("role") == "region") { | |
console.log("added column", node); | |
waitForSelector("div.item-list[role=\"feed\"]", node).then( function(feed) { | |
//var feeds = $("div.item-list[role=\"feed\"]", node); | |
//console.log("feeds", feeds); | |
//var feed = feeds[0]; | |
//console.log("feed is", feed); | |
processFeed(feed); | |
}); | |
} | |
} | |
} | |
}) | |
} | |
$(function() { | |
console.log("make mastodon links internal"); | |
if (window.location.hash) { | |
let parts = window.location.pathname.split("/"); | |
let user = parts[2]; | |
let userparts = user.split("@"); | |
let user_proper = userparts[1]; | |
let host = userparts[2]; | |
hash_post_url = "https://" + host + "/@" + user_proper + "/" + window.location.hash.substring(1); | |
console.log("based on hash", window.location.hash, "we are looking for post", hash_post_url); | |
} | |
var feeds = $("div.item-list[role=\"feed\"]"); | |
//console.log(feeds); | |
feeds.each(function(i, feed) { | |
//console.log(feed); | |
processFeed(feed); | |
}); | |
var colsObserver = new MutationObserver(colChange); | |
colsObserver.observe($("div.columns-area")[0], { childList: true }); | |
}); | |
})(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment