293 lines
11 KiB
JavaScript
293 lines
11 KiB
JavaScript
var LANGS = ["DE", "EN", "ES", "FI", "FR", "HU", "IT", "JA", "LZH", "KO", "PL", "PT", "RU", "TH", "TOK", "UK", "ZH"];
|
|
var BASE64REGEX = /^([0-9a-zA-Z+/]{4})*(([0-9a-zA-Z+/]{2}==)|([0-9a-zA-Z+/]{3}=))?$/;
|
|
const extensionAPI = typeof browser === "undefined" ? chrome : browser;
|
|
|
|
function b64decode(str) {
|
|
const binary_string = atob(str);
|
|
const len = binary_string.length;
|
|
const bytes = new Uint8Array(new ArrayBuffer(len));
|
|
for (let i = 0; i < len; i++) {
|
|
bytes[i] = binary_string.charCodeAt(i);
|
|
}
|
|
return bytes;
|
|
}
|
|
|
|
async function commonFunctionDecompressJSON(value) {
|
|
// Check if value is base64 encoded:
|
|
if (BASE64REGEX.test(value)) {
|
|
// Decode into blob
|
|
const stream = new Blob([b64decode(value)], {
|
|
type: "application/json",
|
|
}).stream();
|
|
|
|
// Decompress value
|
|
const decompressedReadableStream = stream.pipeThrough(
|
|
new DecompressionStream("gzip")
|
|
);
|
|
|
|
const resp = new Response(decompressedReadableStream);
|
|
const blob = await resp.blob();
|
|
const blobText = JSON.parse(await blob.text());
|
|
return blobText;
|
|
} else {
|
|
return value;
|
|
}
|
|
}
|
|
|
|
async function commonFunctionCompressJSON(value) {
|
|
const stream = new Blob([JSON.stringify(value)], {
|
|
type: 'application/json',
|
|
}).stream();
|
|
|
|
// Compress stream with gzip
|
|
const compressedReadableStream = stream.pipeThrough(
|
|
new CompressionStream("gzip")
|
|
);
|
|
const compressedResponse = new Response(compressedReadableStream);
|
|
|
|
// Convert response to blob and buffer
|
|
const blob = await compressedResponse.blob();
|
|
const buffer = await blob.arrayBuffer();
|
|
|
|
// Encode and return string
|
|
return btoa(
|
|
String.fromCharCode(
|
|
...new Uint8Array(buffer)
|
|
)
|
|
);
|
|
}
|
|
|
|
// Load wiki data objects, with each destination having its own object
|
|
async function commonFunctionGetSiteDataByDestination() {
|
|
var sites = [];
|
|
let promises = [];
|
|
for (let i = 0; i < LANGS.length; i++) {
|
|
promises.push(fetch(extensionAPI.runtime.getURL('data/sites' + LANGS[i] + '.json'))
|
|
.then((resp) => resp.json())
|
|
.then((jsonData) => {
|
|
jsonData.forEach((site) => site.language = LANGS[i]);
|
|
sites = sites.concat(jsonData);
|
|
}));
|
|
}
|
|
await Promise.all(promises);
|
|
return sites;
|
|
}
|
|
|
|
async function populateSiteDataByOrigin() {
|
|
// Populate with the site data
|
|
let sites = [];
|
|
let promises = [];
|
|
for (let i = 0; i < LANGS.length; i++) {
|
|
promises.push(fetch(extensionAPI.runtime.getURL('data/sites' + LANGS[i] + '.json'))
|
|
.then((resp) => resp.json())
|
|
.then((jsonData) => {
|
|
jsonData.forEach((site) => {
|
|
site.origins.forEach((origin) => {
|
|
sites.push({
|
|
"id": site.id,
|
|
"origin": origin.origin,
|
|
"origin_base_url": origin.origin_base_url,
|
|
"origin_content_path": origin.origin_content_path,
|
|
"origin_main_page": origin.origin_main_page,
|
|
"destination": site.destination,
|
|
"destination_base_url": site.destination_base_url,
|
|
"destination_search_path": site.destination_search_path,
|
|
"destination_content_prefix": origin.destination_content_prefix || site.destination_content_prefix || "",
|
|
// /w/index.php?title= is the default path for a new MediaWiki install, change as accordingly in config JSON files
|
|
"destination_content_path": site.destination_content_path || "/w/index.php?title=",
|
|
"destination_content_suffix": origin.destination_content_suffix || site.destination_content_suffix || "",
|
|
"destination_platform": site.destination_platform,
|
|
"destination_icon": site.destination_icon,
|
|
"destination_main_page": site.destination_main_page,
|
|
"destination_host": site.destination_host,
|
|
"tags": site.tags || [],
|
|
"language": LANGS[i]
|
|
})
|
|
})
|
|
});
|
|
}));
|
|
}
|
|
|
|
await Promise.all(promises);
|
|
|
|
if (typeof window !== 'undefined') {
|
|
window.iwb_siteDataByOrigin = sites;
|
|
}
|
|
|
|
return sites;
|
|
}
|
|
|
|
// Load wiki data objects, with each origin having its own object
|
|
async function commonFunctionGetSiteDataByOrigin() {
|
|
if (typeof window === 'undefined' || !window.iwb_siteDataByOrigin || window.iwb_siteDataByOrigin.length === 0) {
|
|
let sites = await populateSiteDataByOrigin();
|
|
return sites;
|
|
} else {
|
|
return window.iwb_siteDataByOrigin;
|
|
}
|
|
}
|
|
|
|
// Given a URL, find closest match in our dataset
|
|
async function commonFunctionFindMatchingSite(site, crossLanguageSetting, dest = false) {
|
|
let base_url_key = dest ? 'destination_base_url' : 'origin_base_url';
|
|
|
|
let matchingSite = commonFunctionGetSiteDataByOrigin().then(sites => {
|
|
let matchingSites = [];
|
|
if (crossLanguageSetting === 'on') {
|
|
matchingSites = sites.filter(el => site.replace(/.*https?:\/\//, '').startsWith(el[base_url_key]));
|
|
} else {
|
|
matchingSites = sites.filter(el =>
|
|
site.replace(/.*https?:\/\//, '').startsWith(dest ? el[base_url_key] : (el.origin_base_url + el.origin_content_path))
|
|
|| site.replace(/.*https?:\/\//, '').replace(/\/$/, '') === el[base_url_key]
|
|
);
|
|
}
|
|
|
|
if (matchingSites.length > 0) {
|
|
// Select match with longest base URL
|
|
let closestMatch = '';
|
|
matchingSites.forEach(site => {
|
|
if (site[base_url_key].length > closestMatch.length) {
|
|
closestMatch = site[base_url_key];
|
|
}
|
|
});
|
|
return matchingSites.find(site => site[base_url_key] === closestMatch);
|
|
} else {
|
|
return null;
|
|
}
|
|
});
|
|
|
|
return matchingSite;
|
|
}
|
|
|
|
function commonFunctionGetOriginArticle(originURL, matchingSite) {
|
|
let url = new URL('https://' + originURL.replace(/.*https?:\/\//, ''));
|
|
let article = String(url.pathname).split(matchingSite['origin_content_path'])[1] || '';
|
|
|
|
// If a Fextralife wiki, replace plus signs with spaces
|
|
// When there are multiple plus signs together, this regex will only replace only the first
|
|
if (originURL.includes('.wiki.fextralife.com')) {
|
|
article = article.replace(/(?<!\+)\+/g, ' ');
|
|
}
|
|
|
|
return article;
|
|
}
|
|
|
|
function commonFunctionGetDestinationArticle(matchingSite, article) {
|
|
return matchingSite['destination_content_prefix'] + article + matchingSite['destination_content_suffix'];
|
|
}
|
|
|
|
function encodeArticleTitle(articleTitle) {
|
|
// We decode + encode to ensure we don't double-encode,
|
|
// in the event a string is already encoded.
|
|
// We wrap in a try-catch as decoding can sometimes fail if destination article
|
|
// does have special characters (e.g. %) in the title.
|
|
try {
|
|
return encodeURIComponent(decodeURIComponent(articleTitle));
|
|
} catch {
|
|
return encodeURIComponent(articleTitle);
|
|
}
|
|
}
|
|
|
|
function commonFunctionGetNewURL(originURL, matchingSite) {
|
|
// Get article name from the end of the URL;
|
|
// We can't just take the last part of the path due to subpages;
|
|
// Instead, we take everything after the wiki's base URL + content path
|
|
let originArticle = commonFunctionGetOriginArticle(originURL, matchingSite);
|
|
let destinationArticle = commonFunctionGetDestinationArticle(matchingSite, originArticle);
|
|
|
|
// Set up URL to redirect user to based on wiki platform
|
|
let newURL = '';
|
|
|
|
// If the article is the main page (or missing), redirect to the indie wiki's main page
|
|
if ((!originArticle) || (decodeURIComponent(originArticle) === matchingSite['origin_main_page'])) {
|
|
const mainPageArticle = encodeArticleTitle(matchingSite['destination_main_page']);
|
|
newURL = 'https://' + matchingSite["destination_base_url"] + matchingSite["destination_content_path"] + mainPageArticle + matchingSite['destination_content_suffix'];
|
|
return newURL;
|
|
}
|
|
|
|
// Replace underscores with spaces as that performs better in search
|
|
const encodedDestinationArticle = encodeArticleTitle(destinationArticle.replaceAll('_', ' '));
|
|
|
|
let searchParams = '';
|
|
switch (matchingSite['destination_platform']) {
|
|
case 'mediawiki':
|
|
searchParams = `?title=Special:Search&search=${encodedDestinationArticle}`;
|
|
break;
|
|
case 'dokuwiki':
|
|
searchParams = `?do=search&q=${encodedDestinationArticle}`;
|
|
break;
|
|
// Otherwise, assume the full search path is defined on "destination_search_path"
|
|
default:
|
|
searchParams = encodedDestinationArticle;
|
|
break;
|
|
}
|
|
newURL = 'https://' + matchingSite["destination_base_url"] + matchingSite["destination_search_path"] + searchParams;
|
|
|
|
return newURL;
|
|
}
|
|
|
|
// Temporary function to migrate user data to IWB version 3.0+
|
|
async function commonFunctionMigrateToV3() {
|
|
await extensionAPI.storage.sync.get(async (storage) => {
|
|
if (!storage.v3migration) {
|
|
let defaultWikiAction = storage.defaultWikiAction || 'alert';
|
|
let defaultSearchAction = storage.defaultSearchAction || 'replace';
|
|
|
|
// Set new default action settings:
|
|
if (!storage.defaultWikiAction) {
|
|
if (storage.defaultActionSettings && storage.defaultActionSettings['EN']) {
|
|
defaultWikiAction = storage.defaultActionSettings['EN'];
|
|
}
|
|
extensionAPI.storage.sync.set({ 'defaultWikiAction': defaultWikiAction });
|
|
}
|
|
if (!storage.defaultSearchAction) {
|
|
if (storage.defaultSearchFilterSettings && storage.defaultSearchFilterSettings['EN']) {
|
|
if (storage.defaultSearchFilterSettings['EN'] === 'false') {
|
|
defaultSearchAction = 'disabled';
|
|
} else {
|
|
defaultSearchAction = 'replace';
|
|
}
|
|
}
|
|
extensionAPI.storage.sync.set({ 'defaultSearchAction': defaultSearchAction });
|
|
}
|
|
|
|
// Remove old objects:
|
|
extensionAPI.storage.sync.remove('defaultActionSettings');
|
|
extensionAPI.storage.sync.remove('defaultSearchFilterSettings');
|
|
|
|
// Migrate wiki settings to new searchEngineSettings and wikiSettings objects
|
|
sites = await commonFunctionGetSiteDataByOrigin();
|
|
let siteSettings = storage.siteSettings || {};
|
|
let searchEngineSettings = await commonFunctionDecompressJSON(storage.searchEngineSettings || {});
|
|
let wikiSettings = await commonFunctionDecompressJSON(storage.wikiSettings) || {};
|
|
|
|
sites.forEach((site) => {
|
|
if (!searchEngineSettings[site.id]) {
|
|
if (siteSettings[site.id] && siteSettings[site.id].searchFilter) {
|
|
if (siteSettings[site.id].searchFilter === 'false') {
|
|
searchEngineSettings[site.id] = 'disabled';
|
|
} else {
|
|
searchEngineSettings[site.id] = 'replace';
|
|
}
|
|
} else {
|
|
searchEngineSettings[site.id] = defaultSearchAction;
|
|
}
|
|
}
|
|
|
|
if (!wikiSettings[site.id]) {
|
|
wikiSettings[site.id] = siteSettings[site.id]?.action || defaultWikiAction;
|
|
}
|
|
});
|
|
|
|
extensionAPI.storage.sync.set({ 'searchEngineSettings': await commonFunctionCompressJSON(searchEngineSettings) });
|
|
extensionAPI.storage.sync.set({ 'wikiSettings': await commonFunctionCompressJSON(wikiSettings) });
|
|
|
|
// Remove old object:
|
|
extensionAPI.storage.sync.remove('siteSettings');
|
|
|
|
// Mark v3 migration as complete:
|
|
extensionAPI.storage.sync.set({ 'v3migration': 'done' });
|
|
}
|
|
});
|
|
}
|