mirror of https://github.com/GooseMod/GooseMod
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
335 lines
14 KiB
335 lines
14 KiB
import { sha512 } from '../util/hash';
|
|
|
|
import * as JSCache from './jsCache';
|
|
import * as IDCache from './idCache';
|
|
|
|
let goosemodScope = {};
|
|
|
|
export default {
|
|
setThisScope: (scope) => {
|
|
goosemodScope = scope;
|
|
|
|
JSCache.setThisScope(scope);
|
|
IDCache.setThisScope(scope);
|
|
},
|
|
|
|
modules: [],
|
|
repos: [],
|
|
|
|
apiBaseURL: 'https://api.goosemod.com',
|
|
storeApiBaseURL: 'https://store.goosemod.com',
|
|
|
|
jsCache: JSCache,
|
|
idCache: IDCache,
|
|
|
|
getSettingItemName: (moduleInfo) => {
|
|
let item = goosemodScope.i18n.goosemodStrings.settings.itemNames.plugins;
|
|
|
|
if (moduleInfo.tags.includes('theme')) item = goosemodScope.i18n.goosemodStrings.settings.itemNames.themes;
|
|
|
|
return item;
|
|
},
|
|
|
|
hotupdate: async (shouldHandleLoadingText = false) => { // Update repos, hotreload any updated modules (compare hashes to check if updated)
|
|
if (shouldHandleLoadingText) goosemodScope.updateLoadingScreen(`Getting modules from repos...`);
|
|
|
|
await goosemodScope.moduleStoreAPI.updateModules();
|
|
|
|
await goosemodScope.moduleStoreAPI.updateStoreSetting();
|
|
|
|
if (shouldHandleLoadingText) goosemodScope.updateLoadingScreen(`Updating modules...`);
|
|
|
|
const updatePromises = [];
|
|
|
|
for (const m in goosemodScope.modules) {
|
|
const msHash = goosemodScope.moduleStoreAPI.modules.find((x) => x.name === m)?.hash;
|
|
|
|
const cacheHash = goosemodScope.moduleStoreAPI.jsCache.getCache()[m]?.hash;
|
|
|
|
if (msHash === undefined || cacheHash === undefined || msHash === cacheHash) continue;
|
|
|
|
// New update for it, cached JS != repo JS hashes
|
|
if (shouldHandleLoadingText) goosemodScope.updateLoadingScreen(`Updating modules...\n${m}`);
|
|
|
|
updatePromises.push(goosemodScope.moduleStoreAPI.importModule(m, goosemodScope.moduleSettingsStore.checkDisabled(m)).then(async () => {
|
|
goosemodScope.showToast(`Updated ${m}`, { timeout: 5000, type: 'success', subtext: 'GooseMod Store' })
|
|
}));
|
|
}
|
|
|
|
await Promise.all(updatePromises);
|
|
},
|
|
|
|
initRepos: async () => {
|
|
const getFirstMeta = async (url) => (await (await fetch(`${url}?_=${Date.now()}`)).json()).meta;
|
|
const getFirstObj = async (url) => ({
|
|
url,
|
|
enabled: true,
|
|
meta: await getFirstMeta(url)
|
|
});
|
|
|
|
goosemodScope.moduleStoreAPI.repos = JSON.parse(goosemod.storage.get('goosemodRepos')) || [
|
|
await getFirstObj(`https://store.goosemod.com/goosemod.json`),
|
|
await getFirstObj(`https://store.goosemod.com/ms2porter.json`),
|
|
await getFirstObj(`https://store.goosemod.com/bdthemes.json`),
|
|
await getFirstObj(`https://store.goosemod.com/pcthemes.json`),
|
|
await getFirstObj(`https://store.goosemod.com/pcplugins.json`),
|
|
];
|
|
},
|
|
|
|
updateModules: async () => {
|
|
let newModules = [];
|
|
|
|
goosemodScope.moduleStoreAPI.repos = (await Promise.all(goosemodScope.moduleStoreAPI.repos.map(async (repo) => {
|
|
if (!repo.enabled) {
|
|
return repo;
|
|
}
|
|
|
|
try {
|
|
const resp = (await (await fetch(`${repo.url}?_=${Date.now()}`)).json());
|
|
|
|
newModules = newModules.concat(resp.modules.map((x) => {
|
|
x.repo = repo.url;
|
|
return x;
|
|
})).sort((a, b) => a.name.localeCompare(b.name));
|
|
|
|
return {
|
|
...repo,
|
|
meta: resp.meta // Update meta
|
|
};
|
|
} catch (e) {
|
|
goosemodScope.showToast(`Failed to get repo: ${repo.url}`, { timeout: 5000, type: 'error', subtext: 'GooseMod Store' }); // Show error toast to user so they know
|
|
console.error(e);
|
|
}
|
|
|
|
return repo;
|
|
}))).sort((a, b) => goosemodScope.moduleStoreAPI.repos.indexOf(a.url) - goosemodScope.moduleStoreAPI.repos.indexOf(b.url));
|
|
|
|
goosemodScope.moduleStoreAPI.modules = newModules;
|
|
|
|
goosemod.storage.set('goosemodRepos', JSON.stringify(goosemodScope.moduleStoreAPI.repos));
|
|
goosemod.storage.set('goosemodCachedModules', JSON.stringify(goosemodScope.moduleStoreAPI.modules));
|
|
},
|
|
|
|
importModule: async (moduleName, disabled = false) => {
|
|
try {
|
|
const moduleInfo = goosemodScope.moduleStoreAPI.modules.find((x) => x.name === moduleName);
|
|
|
|
const jsCode = await goosemodScope.moduleStoreAPI.jsCache.getJSForModule(moduleName);
|
|
|
|
const calculatedHash = await sha512(jsCode);
|
|
if (calculatedHash !== moduleInfo.hash) {
|
|
goosemodScope.showToast(`Cancelled importing of ${moduleName} due to hash mismatch`, { timeout: 2000, type: 'danger', subtext: 'GooseMod Store' });
|
|
|
|
console.warn('Hash mismatch', calculatedHash, moduleInfo.hash);
|
|
return;
|
|
}
|
|
|
|
await goosemodScope.importModule({
|
|
name: moduleName,
|
|
data: jsCode,
|
|
metadata: moduleInfo
|
|
}, disabled);
|
|
|
|
if (!disabled) {
|
|
if (goosemodScope.modules[moduleName].goosemodHandlers.onLoadingFinished !== undefined) {
|
|
await goosemodScope.modules[moduleName].goosemodHandlers.onLoadingFinished();
|
|
}
|
|
|
|
await goosemodScope.moduleSettingsStore.loadSavedModuleSetting(moduleName);
|
|
}
|
|
|
|
try {
|
|
const item = goosemodScope.settings.items.find((x) => x[1] === goosemodScope.moduleStoreAPI.getSettingItemName(moduleInfo))[2].find((x) => x.subtext === moduleInfo.description);
|
|
|
|
item.buttonType = 'danger';
|
|
item.buttonText = goosemodScope.i18n.discordStrings.REMOVE;
|
|
item.showToggle = true;
|
|
} catch (e) {
|
|
goosemodScope.logger.debug('import', 'Failed to change setting during MS importModule (likely during initial imports so okay)');
|
|
}
|
|
|
|
// If themes / plugins open
|
|
if (document.querySelector(`#gm-settings-inject`)) {
|
|
const cardEls = [...document.querySelectorAll(`.title-31JmR4 + .colorStandard-2KCXvj`)].filter((x) => x.textContent === moduleInfo.description).map((x) => x.parentElement);
|
|
|
|
if (cardEls.length === 0) return;
|
|
|
|
for (const cardEl of cardEls) {
|
|
const buttonEl = cardEl.querySelector(`.colorBrand-3pXr91`);
|
|
|
|
buttonEl.className = buttonEl.className.replace('lookFilled-1Gx00P colorBrand-3pXr91', 'lookOutlined-3sRXeN colorRed-1TFJan');
|
|
buttonEl.textContent = goosemodScope.i18n.discordStrings.REMOVE;
|
|
|
|
const toggleEl = cardEl.querySelector(`.container-3auIfb`);
|
|
toggleEl.classList.remove('hide-toggle');
|
|
}
|
|
}
|
|
} catch (e) {
|
|
goosemodScope.showToast(`Failed to import module ${moduleName}`, { timeout: 2000, type: 'error', subtext: 'GooseMod Store' });
|
|
console.error(e);
|
|
}
|
|
},
|
|
|
|
moduleRemoved: (m) => {
|
|
let item = goosemodScope.settings.items.find((x) => x[1] === goosemodScope.moduleStoreAPI.getSettingItemName(m))[2].find((x) => x.subtext === m.description);
|
|
|
|
if (item === undefined) return;
|
|
|
|
item.buttonType = 'brand';
|
|
item.buttonText = goosemodScope.i18n.goosemodStrings.moduleStore.card.button.import;
|
|
item.showToggle = false;
|
|
|
|
// If themes / plugins open
|
|
if (document.querySelector(`#gm-settings-inject`)) {
|
|
const cardEls = [...document.querySelectorAll(`.title-31JmR4 + .colorStandard-2KCXvj`)].filter((x) => x.textContent === m.description).map((x) => x.parentElement);
|
|
|
|
if (cardEls.length === 0) return;
|
|
|
|
for (const cardEl of cardEls) {
|
|
const buttonEl = cardEl.querySelector(`.colorRed-1TFJan`);
|
|
|
|
buttonEl.className = buttonEl.className.replace('lookOutlined-3sRXeN colorRed-1TFJan', 'lookFilled-1Gx00P colorBrand-3pXr91');
|
|
buttonEl.textContent = goosemodScope.i18n.goosemodStrings.moduleStore.card.button.import;
|
|
|
|
const toggleEl = cardEl.querySelector(`.container-3auIfb`);
|
|
toggleEl.classList.add('hide-toggle');
|
|
}
|
|
}
|
|
},
|
|
|
|
parseAuthors: async (a) => {
|
|
let authors = [];
|
|
|
|
if (typeof a === "string") {
|
|
authors = a.split(', ');
|
|
} else if (Array.isArray(a)) {
|
|
authors = a;
|
|
};
|
|
|
|
return (await Promise.all(authors.map(async (x, i) => {
|
|
if (typeof x === 'object') { // User object
|
|
const pfp = `<img style="display: inline; border-radius: 50%; margin-right: 5px; vertical-align: bottom;" src="https://cdn.discordapp.com/avatars/${x.i}/${x.a}.png?size=32">`;
|
|
const name = `<span class="author" style="cursor: pointer; line-height: 32px;" onmouseover="this.style.color = '#ccc'" onmouseout="this.style.color = '#fff'" onclick="try { window.goosemod.webpackModules.findByProps('open', 'fetchMutualFriends').open('${x.i}') } catch (e) { }">${x.n}</span>`; //<span class="description-3_Ncsb">#${result.discriminator}</span></span>`;
|
|
|
|
return i > 1 ? pfp : pfp + name;
|
|
}
|
|
|
|
if (x.match(/^[0-9]{17,18}$/)) { // "<id>"
|
|
const result = await IDCache.getDataForID(x);
|
|
|
|
const pfp = `<img style="display: inline; border-radius: 50%; margin-right: 5px; vertical-align: bottom;" src="https://cdn.discordapp.com/avatars/${result.id}/${result.avatar}.png?size=32">`;
|
|
const name = `<span class="author" style="cursor: pointer; line-height: 32px;" onmouseover="this.style.color = '#ccc'" onmouseout="this.style.color = '#fff'" onclick="try { window.goosemod.webpackModules.findByProps('open', 'fetchMutualFriends').open('${result.id}') } catch (e) { }">${result.username}</span>`; //<span class="description-3_Ncsb">#${result.discriminator}</span></span>`;
|
|
|
|
return i > 1 ? pfp : pfp + name;
|
|
}
|
|
|
|
let idMatch = x.match(/(.*) \(([0-9]{17,18})\)/); // "<name> (<id>)"
|
|
if (idMatch === null) return `<span class="author">${x}</span>`; // "<name>"
|
|
|
|
return `<span class="author" style="cursor: pointer;" onmouseover="this.style.color = '#ccc'" onmouseout="this.style.color = '#fff'" onclick="try { window.goosemod.webpackModules.findByProps('open', 'fetchMutualFriends').open('${idMatch[2]}') } catch (e) { }">${idMatch[1]}</span>`; // todo
|
|
}))).join('<span class="description-3_Ncsb">,</span> ');
|
|
},
|
|
|
|
updateStoreSetting: async () => {
|
|
let allItems = goosemodScope.settings.items.filter((x) => x[1] === goosemodScope.i18n.goosemodStrings.settings.itemNames.plugins || x[1] === goosemodScope.i18n.goosemodStrings.settings.itemNames.themes);
|
|
|
|
for (const i of allItems) {
|
|
i[2] = i[2].filter((x) => x.type !== 'card');
|
|
}
|
|
|
|
for (const m of goosemodScope.moduleStoreAPI.modules) {
|
|
const itemName = goosemodScope.moduleStoreAPI.getSettingItemName(m);
|
|
const item = allItems.find((x) => x[1] === itemName);
|
|
|
|
const type = m.tags.includes('theme') ? 'themes' : 'plugins';
|
|
|
|
item[2].push({
|
|
type: 'card',
|
|
|
|
tags: m.tags,
|
|
github: m.github,
|
|
images: m.images?.map((x) => {
|
|
if (x.startsWith('/')) {
|
|
const baseUrl = m.repo.split('/').slice(0, -1).join('/');
|
|
x = baseUrl + x;
|
|
}
|
|
|
|
return x;
|
|
}),
|
|
lastUpdated: m.lastUpdated,
|
|
|
|
buttonType: goosemodScope.modules[m.name] || goosemodScope.disabledModules[m.name] ? 'danger' : 'brand',
|
|
showToggle: goosemodScope.modules[m.name] || goosemodScope.disabledModules[m.name],
|
|
|
|
name: m.name,
|
|
author: await goosemodScope.moduleStoreAPI.parseAuthors(m.authors),
|
|
|
|
subtext: m.description,
|
|
subtext2: m.version === '0' || m.version.toLowerCase().includes('auto') ? '' : `v${m.version}`,
|
|
|
|
buttonText: goosemodScope.modules[m.name] || goosemodScope.disabledModules[m.name] ? goosemodScope.i18n.discordStrings.REMOVE : goosemodScope.i18n.goosemodStrings.moduleStore.card.button.import,
|
|
onclick: async () => {
|
|
goosemodScope.settings[`regen${type}`] = true;
|
|
|
|
if (goosemodScope.modules[m.name] || goosemodScope.disabledModules[m.name]) {
|
|
// el.textContent = goosemodScope.i18n.goosemodStrings.moduleStore.card.button.removing;
|
|
|
|
goosemodScope.settings.removeModuleUI(m.name, itemName);
|
|
|
|
return;
|
|
}
|
|
|
|
// el.textContent = goosemodScope.i18n.goosemodStrings.moduleStore.card.button.importing;
|
|
|
|
if (m.dependencies && m.dependencies.length > 0) { // If it's the initial (on import) import that means it has been imported before
|
|
const mainWord = m.dependencies.length === 1 ? 'dependency' : 'dependencies';
|
|
|
|
const toContinue = await goosemod.confirmDialog('Continue',
|
|
`${m.name} has ${m.dependencies.length === 1 ? 'a ' : ''}${mainWord}`,
|
|
`**${m.name}** has **${m.dependencies.length}** ${mainWord}:
|
|
${m.dependencies.map((x) => ` - **${x}**\n`)}
|
|
To continue importing this module the dependencies need to be imported.`,
|
|
undefined,
|
|
'brand');
|
|
|
|
if (!toContinue) return;
|
|
|
|
for (const d of m.dependencies) {
|
|
await goosemodScope.moduleStoreAPI.importModule(d);
|
|
}
|
|
}
|
|
|
|
await goosemodScope.moduleStoreAPI.importModule(m.name);
|
|
},
|
|
isToggled: () => goosemodScope.disabledModules[m.name] === undefined,
|
|
onToggle: async (checked) => {
|
|
goosemodScope.settings[`regen${type}`] = true;
|
|
|
|
if (checked) {
|
|
goosemodScope.modules[m.name] = Object.assign({}, goosemodScope.disabledModules[m.name]);
|
|
delete goosemodScope.disabledModules[m.name];
|
|
|
|
await goosemodScope.modules[m.name].goosemodHandlers.onImport();
|
|
|
|
if (goosemodScope.modules[m.name].goosemodHandlers.onLoadingFinished !== undefined) {
|
|
await goosemodScope.modules[m.name].goosemodHandlers.onLoadingFinished();
|
|
}
|
|
|
|
await goosemodScope.moduleSettingsStore.loadSavedModuleSetting(m.name);
|
|
|
|
goosemodScope.moduleSettingsStore.enableModule(m.name);
|
|
} else {
|
|
goosemodScope.disabledModules[m.name] = Object.assign({}, goosemodScope.modules[m.name]);
|
|
|
|
await goosemodScope.modules[m.name].goosemodHandlers.onRemove();
|
|
|
|
delete goosemodScope.modules[m.name];
|
|
|
|
goosemodScope.moduleSettingsStore.disableModule(m.name);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}
|
|
}
|