diff --git a/src/_locales/en/messages.json b/src/_locales/en/messages.json index cb2c872..63a56c8 100644 --- a/src/_locales/en/messages.json +++ b/src/_locales/en/messages.json @@ -163,6 +163,10 @@ "message": "Prevents the use of an extra tab and instead redirects the action on the main page.", "description": "This is an option shown in the add-on settings." }, + "optionRedirectImmediately": { + "message": "Redirect immediately on page load, instead of on action.", + "description": "This is an option shown in the add-on settings." + }, "translatorCredit": { "message": "This add-on has been translated into English by $TRANSLATORS$.", "description": "The credit text for the translator. See https://github.com/TinyWebEx/common/blob/master/CONTRIBUTING.md#translator-credit-inside-of-add-on for how to translate this.", diff --git a/src/background/modules/ActivityPubRedirect.js b/src/background/modules/ActivityPubRedirect.js new file mode 100644 index 0000000..cd7e795 --- /dev/null +++ b/src/background/modules/ActivityPubRedirect.js @@ -0,0 +1,46 @@ +import * as NetworkTools from "/common/modules/NetworkTools.js"; +import * as AddonSettings from "/common/modules/AddonSettings/AddonSettings.js"; +import { resolveActivityPubId } from "/common/modules/MastodonApi.js"; + +/** + * Scrapes the ActivityPub destination from the HTML page, if needed and exists. + * + * @param {number} tabId + * @param {URL} url + * @returns {Promise} + */ +export async function redirectByActivityPubLink(tabId, url) { + if (!tabId || !url) { + throw new Error("Needs a tab id and a page URL"); + } + + const [objectId] = await browser.tabs.executeScript(tabId, { + code: `document.querySelector("link[rel=alternate][type='application/activity+json']")?.href`, + runAt: "document_end", + }); + if (!objectId) { + return; + } + + const ownMastodon = await AddonSettings.get("ownMastodon"); + if (ownMastodon.server === url.hostname) { + return; + } + + const body = await resolveActivityPubId(ownMastodon.server, objectId); + + const baseUrl = `https://${ownMastodon.server}`; + let homeUrl; + if (body.accounts[0]) { + homeUrl = new URL(`@${body.accounts[0].acct}`, baseUrl); + } else if (body.statuses[0]) { + homeUrl = new URL( + `@${body.statuses[0].account.acct}/${body.statuses[0].id}`, + baseUrl + ); + } else { + return; + } + + await NetworkTools.redirectToWebsite(homeUrl, tabId); +} diff --git a/src/background/modules/AutoRemoteFollow.js b/src/background/modules/AutoRemoteFollow.js index 73fd618..731b568 100644 --- a/src/background/modules/AutoRemoteFollow.js +++ b/src/background/modules/AutoRemoteFollow.js @@ -15,6 +15,7 @@ import * as MisskeyDetect from "./Detect/Misskey.js"; import * as NetworkTools from "/common/modules/NetworkTools.js"; import * as MastodonRedirect from "./MastodonRedirect.js"; +import { redirectByActivityPubLink } from "./ActivityPubRedirect.js"; import * as AddonSettings from "/common/modules/AddonSettings/AddonSettings.js"; import * as MastodonHandleCheck from "/common/modules/MastodonHandle/ConfigCheck.js"; @@ -149,12 +150,18 @@ function getInteractionType(url) { /** * Handles changes to the URL of a tab. - * + * * @param {string} tabId * @param {Object} changeInfo * @returns {void} */ async function onTabUpdate(tabId, changeInfo) { + // clear cache of settings + await AddonSettings.loadOptions(); + if (await AddonSettings.get("redirectImmediately")) { + redirectByActivityPubLink(tabId, changeInfo.url); + } + const ownMastodon = await AddonSettings.get("ownMastodon"); const currentURL = new URL(changeInfo.url); if (ownMastodon.server !== currentURL.hostname){ diff --git a/src/common/modules/MastodonApi.js b/src/common/modules/MastodonApi.js index b685c7d..6d599fd 100644 --- a/src/common/modules/MastodonApi.js +++ b/src/common/modules/MastodonApi.js @@ -99,3 +99,22 @@ export function getTootStatus(mastodonServer, localTootId) { return response.json(); }); } + +/** + * Resolves the ActivityPub object ID on a given server + * + * @param {string} mastodonServer + * @param {string} objectId + */ +export async function resolveActivityPubId(mastodonServer, objectId) { + const apiUrl = new URL("api/v2/search", `https://${mastodonServer}`); + apiUrl.searchParams.set("q", objectId); + apiUrl.searchParams.set("resolve", "true"); + + const response = await NetworkTools.fetch(apiUrl); + if (!response.ok) { + throw new MastodonApiError(mastodonServer, response, `Failed to resolve ${objectId}.`); + } + + return await response.json(); +} diff --git a/src/common/modules/data/DefaultSettings.js b/src/common/modules/data/DefaultSettings.js index 4aeb280..b774d8c 100644 --- a/src/common/modules/data/DefaultSettings.js +++ b/src/common/modules/data/DefaultSettings.js @@ -6,5 +6,6 @@ export const DEFAULT_SETTINGS = Object.freeze({ ownMastodon: null, - redirectInMainWindow: false + redirectInMainWindow: false, + redirectImmediately: false, }); diff --git a/src/options/options.html b/src/options/options.html index cffa210..1ca63a5 100644 --- a/src/options/options.html +++ b/src/options/options.html @@ -85,6 +85,10 @@ +