Misskey support

closes #63
This commit is contained in:
Austin Huang 2023-03-18 19:19:01 -04:00
parent b2f52a3365
commit 4a82fe0348
No known key found for this signature in database
GPG key ID: 84C23AA04587A91F
3 changed files with 233 additions and 2 deletions

View file

@ -11,6 +11,7 @@ import * as MastodonDetect from "./Detect/Mastodon.js";
import * as GnuSocialDetect from "./Detect/GnuSocial.js";
import * as PleromaDetect from "./Detect/Pleroma.js";
import * as FriendicaDetect from "./Detect/Friendica.js";
import * as MisskeyDetect from "./Detect/Misskey.js";
import * as NetworkTools from "/common/modules/NetworkTools.js";
import * as MastodonRedirect from "./MastodonRedirect.js";
@ -24,13 +25,15 @@ const FEDIVERSE_TYPE = Object.freeze({
MASTODON: Symbol("Mastodon"),
GNU_SOCIAL: Symbol("GNU Social"),
PLEROMA: Symbol("Pleroma"),
FRIENDICA: Symbol("Friendica")
FRIENDICA: Symbol("Friendica"),
MISSKEY: Symbol("Misskey"),
});
const FEDIVERSE_MODULE = Object.freeze({
[FEDIVERSE_TYPE.MASTODON]: MastodonDetect,
[FEDIVERSE_TYPE.GNU_SOCIAL]: GnuSocialDetect,
[FEDIVERSE_TYPE.PLEROMA]: PleromaDetect,
[FEDIVERSE_TYPE.FRIENDICA]: FriendicaDetect
[FEDIVERSE_TYPE.FRIENDICA]: FriendicaDetect,
[FEDIVERSE_TYPE.MISSKEY]: MisskeyDetect
});
/**
@ -158,6 +161,9 @@ async function onTabUpdate(tabId, changeInfo) {
browser.tabs.executeScript(tabId, {
file: "/content_script/mastodonInject.js",
});
browser.tabs.executeScript(tabId, {
file: "/content_script/misskeyInject.js",
});
}
}

View file

@ -0,0 +1,77 @@
/**
* Module, that detects a Misskey instance and returns the required values.
*
* @module Detect/Misskey
*/
import {INTERACTION_TYPE} from "../data/INTERACTION_TYPE.js";
const REMOTE_INTERACT_REGEX = /\/notes\/(\w|\d)+\/?#interact$/;
/** The URLs to intercept and pass to this module. */
export const CATCH_URLS = new Map();
CATCH_URLS.set(REMOTE_INTERACT_REGEX, INTERACTION_TYPE.TOOT_INTERACT);
/**
* Whether the OAUTH site finished loading.
*
* @private
* @type {boolean}
*/
let redirectSiteFinishedLoading = false;
/**
* Determinates whether the redirect should replace the site before or not.
*
* @public
* @returns {boolean}
*/
export function shouldLoadReplace() {
// if site finished loading, replace it
return redirectSiteFinishedLoading;
}
/**
* Determinates which tab should be redirected.
*
* @public
* @param {Object} requestDetails
* @returns {int}
*/
export function getTabToModify(requestDetails) {
return requestDetails.tabId;
}
/**
* Find the status URL.
*
* @public
* @param {URL} url
* @returns {Promise}
*/
export function getTootUrl(url) {
return new Promise((resolve, reject) => {
resolve(`https://${url.host}${url.pathname}`);
});
}
/**
* Returns the username.
*
* @public
* @returns {string|undefined}
*/
export function getUsername() {
throw new NotSupportedError("getUsername() is not supported");
}
/**
* Returns the server from the required URL.
*
* @function
* @param {URL} url
* @returns {string|undefined}
*/
export function getServer(url) {
return url.host;
}

View file

@ -0,0 +1,148 @@
"use strict";
/**
* Replacement onClick handler for interaction buttons.
*
* @param {Event} event
* @returns {void}
*/
function onClickInteract(event) {
event.stopImmediatePropagation();
event.stopPropagation();
event.preventDefault();
const articleElement = event.target.closest("article");
let headerElement = (
articleElement === null
? null
: articleElement.querySelector("header > div > a") // misskey
);
headerElement = (
(articleElement !== null && headerElement === null)
? articleElement.querySelector(".created-at") // calckey
: headerElement
);
const tootId = (
headerElement === null
? window.location.pathname.toString()
: headerElement.getAttribute("href")
);
// activate AutoRemoteFollow
window.open(`${tootId}#interact`, "_blank");
}
/**
* Wait for element to appear.
*
* @param {string} selector
* @param {boolean} [multiple=false]
* @param {number} [timeoutDuration=200000]
* @see {@link https://github.com/storybookjs/test-runner/blob/6d41927154e8dd1e4c9e7493122e24e2739a7a0f/src/setup-page.ts#L134}
* from which this was adapted
* @returns {Promise}
*/
function waitForElement(selector, multiple = false, timeoutDuration = 200000) {
return new Promise((resolve, reject) => {
const getElement = () => (
multiple
? document.querySelectorAll(selector)
: document.querySelector(selector)
);
const isElementFound = (el) => (!multiple && el) || (multiple && el.length > 0);
const timeout = window.setTimeout(() => {
reject(new Error("waitForElement timed out"));
}, timeoutDuration);
const element = getElement();
if (isElementFound(element)) {
window.clearTimeout(timeout);
return resolve(element);
}
const observer = new MutationObserver(() => {
const element = getElement();
if (isElementFound(element)) {
window.clearTimeout(timeout);
resolve(element);
observer.disconnect();
}
});
observer.observe(document.body, {
childList: true,
subtree: true
});
return null;
});
}
/**
* Inject replacement onClick handler for Interaction buttons.
*
* @returns {void}
*/
async function injectInteractionButtons() {
const INJECTED_REPLY_CLASS = "mastodon-simplified-federation-injected-interaction";
const SELECTOR = ".mk-app > .main > .contents article footer > button:not(:last-child)";
try {
const replyButtons = await waitForElement(SELECTOR, true);
replyButtons.forEach((button) => {
try {
if (!button.classList.contains(INJECTED_REPLY_CLASS)) {
button.classList.add(INJECTED_REPLY_CLASS);
button.addEventListener("click", onClickInteract);
button.removeEventListener("mousedown");
}
} catch (error) {
// Failed to inject interaction buttons
}
});
} catch (error) {
// Interaction buttons failed to appear
}
}
/**
* Initialise injection for all remote Misskey buttons.
*
* @returns {void}
*/
function initInjections() {
injectInteractionButtons().catch(console.error);
}
/**
* Initialise script and re-run if there are changes.
*
* @returns {void}
*/
async function init() {
if (typeof MISSKEY_INJECTED_CLASS === "undefined"){
// eslint-disable-next-line vars-on-top, no-var
var MISSKEY_INJECTED_CLASS = true;
} else {
// init has already run
return;
}
initInjections();
const observer = new MutationObserver(() => {
initInjections();
});
// monitor only the main column in the Mastodon UI
const mainColumn = await waitForElement(
".mk-app > .main",
false,
);
observer.observe(mainColumn, {
childList: true,
subtree: true,
});
}
init().catch(console.error);