mirror of
https://github.com/zumbiepig/MineXLauncher.git
synced 2025-06-08 07:54:49 +00:00
step 2
This commit is contained in:
parent
bf89f3f29a
commit
76c28a5de1
5
.gitignore
vendored
5
.gitignore
vendored
@ -1,10 +1,7 @@
|
||||
package-lock.json
|
||||
desktop.ini
|
||||
|
||||
public/resources/data/assets.json
|
||||
public/resources/scripts/
|
||||
public/sw.js
|
||||
public/sw-full.js
|
||||
public/
|
||||
|
||||
# Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore
|
||||
|
||||
|
84
build.ts
84
build.ts
@ -1,8 +1,11 @@
|
||||
import { $, build } from 'bun';
|
||||
import { mkdirSync, readdirSync, statSync, writeFileSync, readFileSync } from 'fs';
|
||||
import { resolve, dirname } from 'path';
|
||||
import chalk from 'chalk';
|
||||
import { build } from 'bun';
|
||||
import { mkdirSync, readdirSync, statSync, writeFileSync, readFileSync, rmSync } from 'fs';
|
||||
import { resolve, dirname, basename } from 'path';
|
||||
import { minify } from 'html-minifier';
|
||||
import javascriptObfuscator from 'javascript-obfuscator';
|
||||
import chalk from 'chalk';
|
||||
|
||||
console.clear();
|
||||
|
||||
function getFiles(baseDir: string, dir?: string, filesArr?: string[]) {
|
||||
dir = dir ?? baseDir;
|
||||
@ -19,32 +22,31 @@ function getFiles(baseDir: string, dir?: string, filesArr?: string[]) {
|
||||
return filesArr;
|
||||
}
|
||||
|
||||
console.clear();
|
||||
|
||||
const isDev = process.env.NODE_ENV === 'development' ? true : false;
|
||||
const srcDir = resolve(import.meta.dir, 'src');
|
||||
const publicDir = resolve(import.meta.dir, 'public');
|
||||
const srcFilesArr = getFiles(resolve(import.meta.dir, 'src'));
|
||||
const srcFiles = getFiles(resolve(import.meta.dir, 'src'));
|
||||
const bundleFiles: string[] = [];
|
||||
const minifyFiles: string[] = [];
|
||||
const copyFiles: string[] = [];
|
||||
srcFiles.forEach((file) => {
|
||||
const strippedPath = file.replace(new RegExp(`^${srcDir}`), '');
|
||||
if (file.endsWith('.ts')) bundleFiles.push(file);
|
||||
else if (isDev) copyFiles.push(file);
|
||||
else if (/\.(html|css|js|json)$/.test(strippedPath)) {
|
||||
if (strippedPath.startsWith('/game/offline/') || basename(strippedPath) === 'classes.js') copyFiles.push(file);
|
||||
else minifyFiles.push(file);
|
||||
} else copyFiles.push(file);
|
||||
});
|
||||
|
||||
console.log(chalk.cyan('Linting code...\n'));
|
||||
//const lintOutput = await $`bunx eslint ./src/`.nothrow().text();
|
||||
//if (lintOutput) {
|
||||
// console.error(lintOutput);
|
||||
// process.exit(1);
|
||||
//}
|
||||
|
||||
console.log(chalk.cyan('Type-checking code...\n'));
|
||||
//const tscOutput = await $`bunx tsc`.nothrow().text();
|
||||
//if (tscOutput) {
|
||||
// console.error(tscOutput);
|
||||
// process.exit(1);
|
||||
//}
|
||||
|
||||
console.log(chalk.cyan('Removing old build artifacts...\n'));
|
||||
await $`rm -rf ./public/resources/scripts/ ./public/resources/data/assets.json ./public/sw.js ./public/sw-full.js`.quiet();
|
||||
if (!isDev) {
|
||||
console.log(chalk.cyan('Removing old build artifacts...\n'));
|
||||
rmSync(publicDir, { force: true, recursive: true });
|
||||
}
|
||||
|
||||
console.log(chalk.cyan('Bundling TypeScript and modules...\n'));
|
||||
await build({
|
||||
entrypoints: srcFilesArr.filter((file) => file.endsWith('.ts')),
|
||||
entrypoints: srcFiles.filter((file) => file.endsWith('.ts')),
|
||||
outdir: './public/',
|
||||
target: 'browser',
|
||||
root: './src/',
|
||||
@ -55,10 +57,9 @@ await build({
|
||||
},
|
||||
});
|
||||
|
||||
console.log(chalk.cyan('Minifying HTML and CSS...\n'));
|
||||
srcFilesArr
|
||||
.filter((file) => !file.endsWith('.ts'))
|
||||
.forEach((file) => {
|
||||
if (!isDev) {
|
||||
console.log(chalk.cyan('Minifying HTML, JS, CSS, and JSON...\n'));
|
||||
minifyFiles.forEach((file) => {
|
||||
const outputPath = file.replace(new RegExp(`^${srcDir}`), publicDir);
|
||||
mkdirSync(dirname(outputPath), { recursive: true });
|
||||
writeFileSync(
|
||||
@ -68,16 +69,37 @@ srcFilesArr
|
||||
removeComments: true,
|
||||
minifyCSS: true,
|
||||
minifyJS: true,
|
||||
continueOnParseError: true,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
console.log(chalk.cyan('Obfuscating JavaScript...\n'));
|
||||
await $`bunx javascript-obfuscator ./public/resources/scripts/ --output ./public/resources/scripts/ --options-preset high-obfuscation`.quiet();
|
||||
console.log(chalk.cyan('Obfuscating JavaScript...\n'));
|
||||
getFiles(resolve(publicDir, 'resources/scripts')).forEach((file) => {
|
||||
writeFileSync(
|
||||
file,
|
||||
javascriptObfuscator
|
||||
.obfuscate(readFileSync(file, 'utf-8'), {
|
||||
optionsPreset: 'high-obfuscation',
|
||||
target: 'browser',
|
||||
})
|
||||
.getObfuscatedCode()
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
console.log(chalk.cyan('Copying other files...\n'));
|
||||
copyFiles.forEach((file) => {
|
||||
const outputPath = file.replace(new RegExp(`^${srcDir}`), publicDir);
|
||||
mkdirSync(dirname(outputPath), { recursive: true });
|
||||
writeFileSync(outputPath, readFileSync(file));
|
||||
});
|
||||
|
||||
console.log(chalk.cyan('Generating assets list...\n'));
|
||||
const assetsJsonPath = resolve(publicDir, 'resources/data/assets.json');
|
||||
mkdirSync(dirname(assetsJsonPath), { recursive: true });
|
||||
writeFileSync(
|
||||
resolve(publicDir, 'resources/data/assets.json'),
|
||||
assetsJsonPath,
|
||||
JSON.stringify(
|
||||
getFiles(publicDir).map((asset) => {
|
||||
return asset.replace(new RegExp(`^${publicDir}`), '').replace(/\/index\.html$/, '/');
|
||||
|
@ -1,49 +0,0 @@
|
||||
import { $, build } from 'bun';
|
||||
import { readdirSync, statSync, writeFileSync } from 'fs';
|
||||
import { join } from 'path';
|
||||
import chalk from 'chalk';
|
||||
|
||||
function getFiles(baseDir: string, dir?: string, filesArr?: string[]) {
|
||||
dir = dir ?? baseDir;
|
||||
filesArr = filesArr ?? [];
|
||||
const files = readdirSync(dir);
|
||||
for (const file of files) {
|
||||
const name = join(dir, file);
|
||||
if (statSync(name).isDirectory()) {
|
||||
getFiles(baseDir, name, filesArr);
|
||||
} else {
|
||||
filesArr.push(name);
|
||||
}
|
||||
}
|
||||
return filesArr;
|
||||
}
|
||||
|
||||
console.log(chalk.cyan('Removing old build artifacts...\n'));
|
||||
await $`rm -rf ./public/resources/scripts/ ./public/resources/data/assets.json ./public/sw.js ./public/sw-full.js`.quiet();
|
||||
|
||||
console.log(chalk.cyan('Bundling TypeScript and modules...\n'));
|
||||
const srcFilesArr = getFiles(join(import.meta.dir, 'src'));
|
||||
await build({
|
||||
entrypoints: srcFilesArr,
|
||||
outdir: './public/',
|
||||
target: 'browser',
|
||||
root: './src/',
|
||||
minify: {
|
||||
syntax: true,
|
||||
whitespace: true,
|
||||
identifiers: false,
|
||||
},
|
||||
});
|
||||
|
||||
console.log(chalk.cyan('Generating assets list...\n'));
|
||||
const publicDir = join(import.meta.dir, 'public');
|
||||
writeFileSync(
|
||||
join(publicDir, 'resources/data/assets.json'),
|
||||
JSON.stringify(
|
||||
getFiles(publicDir).map((asset) => {
|
||||
return asset.replace(new RegExp(`^${publicDir}`), '').replace(/\/index\.html$/, '/');
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
console.log(chalk.green('Build complete!\n'));
|
@ -5,7 +5,6 @@ export default tseslint.config(
|
||||
eslint.configs.recommended,
|
||||
tseslint.configs.eslintRecommended,
|
||||
...tseslint.configs.strict,
|
||||
// ...tseslint.configs.strictTypeChecked,
|
||||
...tseslint.configs.stylisticTypeChecked,
|
||||
{
|
||||
languageOptions: {
|
||||
|
12
package.json
12
package.json
@ -3,12 +3,12 @@
|
||||
"name": "minexlauncher",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"start": "NODE_ENV=production bun run ./index.ts",
|
||||
"dev": "NODE_ENV=development DEBUG=app:* bun --hot run ./index.ts",
|
||||
"lint": "eslint ./src/",
|
||||
"lint:fix": "eslint --fix ./src/",
|
||||
"build": "bun run ./build.ts",
|
||||
"build:fast": "bun run ./build_fast.ts"
|
||||
"start": "NODE_ENV=production bun run ./server.ts",
|
||||
"dev": "NODE_ENV=development DEBUG=minexlauncher:* bun --hot run ./server.ts",
|
||||
"lint": "eslint ./src/ && tsc",
|
||||
"lint:fix": "eslint --fix ./src/ && tsc",
|
||||
"build": "NODE_ENV=production bun run ./build.ts",
|
||||
"build:fast": "NODE_ENV=development bun run ./build.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"chalk": "^5.3.0",
|
||||
|
File diff suppressed because one or more lines are too long
@ -82,7 +82,7 @@
|
||||
<img src="/resources/images/icons/clients/all.webp" />
|
||||
<span>1.5.2</span>
|
||||
</div>
|
||||
<div class="custom-option" onclick="game.select('/game/web/main/1.2.5/', '1.2.5')">
|
||||
<div class="custom-option" onclick="game.select('/game/offline/main/Eaglercraft_1.2.5.html', '1.2.5')">
|
||||
<img src="/resources/images/icons/clients/all.webp" />
|
||||
<span>1.2.5</span>
|
||||
</div>
|
||||
|
27
src/resources/scripts/eagler-launch/1.5.2/main.ts
Normal file
27
src/resources/scripts/eagler-launch/1.5.2/main.ts
Normal file
@ -0,0 +1,27 @@
|
||||
// @ts-nocheck
|
||||
window.addEventListener('load', function () {
|
||||
const relayId = Math.floor(Math.random() * 3);
|
||||
window.eaglercraftOpts = {
|
||||
container: 'game_frame',
|
||||
assetsURI: `${window.location.pathname}/assets.epk`,
|
||||
localesURI: `${window.location.pathname}/lang/`,
|
||||
serverWorkerURI: `${window.location.pathname}/worker_bootstrap.js`,
|
||||
worldsFolder: 'MAIN',
|
||||
servers: [{ serverName: 'BrickMC', serverAddress: 'wss://play.brickmc.net', hideAddress: false }],
|
||||
relays: [
|
||||
{ addr: 'wss://relay.deev.is/', name: 'lax1dude relay #1', primary: relayId === 0 },
|
||||
{ addr: 'wss://relay.lax1dude.net/', name: 'lax1dude relay #2', primary: relayId === 1 },
|
||||
{ addr: 'wss://relay.shhnowisnottheti.me/', name: 'ayunami relay #1', primary: relayId === 2 },
|
||||
],
|
||||
mainMenu: {
|
||||
splashes: ['Darviglet!', 'eaglerenophile!', 'You Eagler!', 'Yeeeeeee!', 'yeee', 'EEEEEEEEE!', 'You Darvig!', 'You Vigg!', ':>', '|>', 'You Yumpster!'],
|
||||
eaglerLogo: false,
|
||||
},
|
||||
};
|
||||
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
window.eaglercraftOpts.joinServer = urlParams.get('server') ?? undefined;
|
||||
|
||||
history.replaceState({}, '', '/play');
|
||||
main();
|
||||
});
|
45
src/resources/scripts/eagler-launch/1.8.8/eaglerforge.ts
Normal file
45
src/resources/scripts/eagler-launch/1.8.8/eaglerforge.ts
Normal file
@ -0,0 +1,45 @@
|
||||
// @ts-nocheck
|
||||
window.addEventListener('load', () => {
|
||||
const relayId = Math.floor(Math.random() * 3);
|
||||
window.eaglercraftXOpts = {
|
||||
container: 'game_frame',
|
||||
assetsURI: `${window.location.pathname}/assets.epk`,
|
||||
localesURI: `${window.location.pathname}/lang/`,
|
||||
servers: [
|
||||
{ addr: 'wss://mc.ricenetwork.xyz', name: 'Rice Network' },
|
||||
{ addr: 'wss://mc.lamplifesteal.xyz', name: 'LampLifesteal' },
|
||||
{ addr: 'wss://electronmc.club', name: 'Electron Network' },
|
||||
{ addr: 'wss://play.brickmc.net', name: 'BrickMC' },
|
||||
],
|
||||
relays: [
|
||||
{ addr: 'wss://relay.deev.is/', comment: 'lax1dude relay #1', primary: relayId === 0 },
|
||||
{ addr: 'wss://relay.lax1dude.net/', comment: 'lax1dude relay #2', primary: relayId === 1 },
|
||||
{ addr: 'wss://relay.shhnowisnottheti.me/', comment: 'ayunami relay #1', primary: relayId === 2 },
|
||||
],
|
||||
};
|
||||
|
||||
const storage = {
|
||||
local: {
|
||||
get: function (key: string) {
|
||||
const item = localStorage.getItem('minexlauncher');
|
||||
if (item !== null) {
|
||||
const json = JSON.parse(item);
|
||||
if (json[key] !== undefined) {
|
||||
return json[key];
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
window.eaglercraftXOpts.joinServer = urlParams.get('server') ?? undefined;
|
||||
window.eaglercraftXOpts.Mods = storage.local.get('mods') ?? [];
|
||||
|
||||
history.replaceState({}, '', '/play');
|
||||
main();
|
||||
});
|
688
src/resources/scripts/eagler-launch/1.8.8/eaglermobile-ef.ts
Normal file
688
src/resources/scripts/eagler-launch/1.8.8/eaglermobile-ef.ts
Normal file
File diff suppressed because one or more lines are too long
1156
src/resources/scripts/eagler-launch/1.8.8/eaglerpocketmobile.ts
Normal file
1156
src/resources/scripts/eagler-launch/1.8.8/eaglerpocketmobile.ts
Normal file
File diff suppressed because it is too large
Load Diff
26
src/resources/scripts/eagler-launch/1.8.8/main.ts
Normal file
26
src/resources/scripts/eagler-launch/1.8.8/main.ts
Normal file
@ -0,0 +1,26 @@
|
||||
// @ts-nocheck
|
||||
window.addEventListener('load', () => {
|
||||
const relayId = Math.floor(Math.random() * 3);
|
||||
window.eaglercraftXOpts = {
|
||||
container: 'game_frame',
|
||||
assetsURI: `${window.location.pathname}/assets.epk`,
|
||||
localesURI: `${window.location.pathname}/lang/`,
|
||||
servers: [
|
||||
{ addr: 'wss://mc.ricenetwork.xyz', name: 'Rice Network' },
|
||||
{ addr: 'wss://mc.lamplifesteal.xyz', name: 'LampLifesteal' },
|
||||
{ addr: 'wss://electronmc.club', name: 'Electron Network' },
|
||||
{ addr: 'wss://play.brickmc.net', name: 'BrickMC' },
|
||||
],
|
||||
relays: [
|
||||
{ addr: 'wss://relay.deev.is/', comment: 'lax1dude relay #1', primary: relayId === 0 },
|
||||
{ addr: 'wss://relay.lax1dude.net/', comment: 'lax1dude relay #2', primary: relayId === 1 },
|
||||
{ addr: 'wss://relay.shhnowisnottheti.me/', comment: 'ayunami relay #1', primary: relayId === 2 },
|
||||
],
|
||||
};
|
||||
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
window.eaglercraftXOpts.joinServer = urlParams.get('server') ?? undefined;
|
||||
|
||||
history.replaceState({}, '', '/play');
|
||||
main();
|
||||
});
|
21
src/resources/scripts/eagler-launch/1.9.4/main.ts
Normal file
21
src/resources/scripts/eagler-launch/1.9.4/main.ts
Normal file
@ -0,0 +1,21 @@
|
||||
// @ts-nocheck
|
||||
window.addEventListener('load', () => {
|
||||
const relayId = Math.floor(Math.random() * 3);
|
||||
window.eaglercraftXOpts = {
|
||||
container: 'game_frame',
|
||||
assetsURI: `${window.location.pathname}/assets.epk`,
|
||||
localesURI: `${window.location.pathname}/lang/`,
|
||||
servers: [{ addr: 'wss://eagler.xyz', name: 'TemuzX' }],
|
||||
relays: [
|
||||
{ addr: 'wss://relay.deev.is/', comment: 'lax1dude relay #1', primary: relayId === 0 },
|
||||
{ addr: 'wss://relay.lax1dude.net/', comment: 'lax1dude relay #2', primary: relayId === 1 },
|
||||
{ addr: 'wss://relay.shhnowisnottheti.me/', comment: 'ayunami relay #1', primary: relayId === 2 },
|
||||
],
|
||||
};
|
||||
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
window.eaglercraftXOpts.joinServer = urlParams.get('server') ?? undefined;
|
||||
|
||||
history.replaceState({}, '', '/play');
|
||||
main();
|
||||
});
|
10
src/resources/scripts/eagler-launch/b1.3/main.ts
Normal file
10
src/resources/scripts/eagler-launch/b1.3/main.ts
Normal file
@ -0,0 +1,10 @@
|
||||
// @ts-nocheck
|
||||
window.addEventListener('load', function () {
|
||||
window.minecraftOpts = ['game_frame', `${window.location.pathname}/assets.epk`];
|
||||
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
window.minecraftOpts.push(urlParams.get('server') ?? undefined);
|
||||
|
||||
history.replaceState({}, '', '/play');
|
||||
main();
|
||||
});
|
12
src/resources/scripts/google-tag.ts
Normal file
12
src/resources/scripts/google-tag.ts
Normal file
@ -0,0 +1,12 @@
|
||||
// @ts-nocheck
|
||||
const googleTag = document.createElement('script');
|
||||
googleTag.async = true;
|
||||
googleTag.src = 'https://www.googletagmanager.com/gtag/js?id=G-GD4SJRCR7Z';
|
||||
document.head.appendChild(googleTag);
|
||||
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
function gtag(...args: unknown[]) {
|
||||
dataLayer.push(...args);
|
||||
}
|
||||
gtag('js', new Date());
|
||||
gtag('config', 'G-GD4SJRCR7Z');
|
631
src/resources/scripts/main.ts
Normal file
631
src/resources/scripts/main.ts
Normal file
@ -0,0 +1,631 @@
|
||||
import { gt, coerce } from 'semver';
|
||||
import { inflate, deflate } from 'pako';
|
||||
|
||||
let selectedVersion: string | undefined = undefined;
|
||||
let articleAnimationLock = false;
|
||||
|
||||
const theme = {
|
||||
load: function (themeToLoad?: string) {
|
||||
const themeElement = document.querySelector('#theme') as HTMLLinkElement | null;
|
||||
if (themeElement) themeElement.href = themeToLoad ? `/resources/styles/themes/${themeToLoad}.css` : `/resources/styles/themes/${storage.local.get('theme') ?? 'default'}.css`;
|
||||
},
|
||||
set: function (newTheme: string) {
|
||||
storage.local.set('theme', newTheme);
|
||||
theme.load();
|
||||
},
|
||||
};
|
||||
|
||||
const versionSelector = {
|
||||
open: function () {
|
||||
const customOptions = document.querySelector('.custom-options');
|
||||
const customSelect = document.querySelector('.custom-select');
|
||||
if (customOptions && customSelect) {
|
||||
customOptions.classList.add('open');
|
||||
customSelect.classList.add('open');
|
||||
}
|
||||
},
|
||||
close: function () {
|
||||
const customOptions = document.querySelector('.custom-options');
|
||||
const customSelect = document.querySelector('.custom-select');
|
||||
if (customOptions && customSelect) {
|
||||
customOptions.classList.remove('open');
|
||||
customSelect.classList.remove('open');
|
||||
}
|
||||
},
|
||||
toggle: function () {
|
||||
const customOptions = document.querySelector('.custom-options');
|
||||
const customSelect = document.querySelector('.custom-select');
|
||||
if (customOptions && customSelect) {
|
||||
customOptions.classList.toggle('open');
|
||||
customSelect.classList.toggle('open');
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
const game = {
|
||||
play: function (version?: string) {
|
||||
if (version) {
|
||||
document.body.style.display = 'none';
|
||||
storage.session.set('lastGame', version);
|
||||
// @ts-expect-error
|
||||
window.top.location.href = version;
|
||||
} else if (selectedVersion) {
|
||||
document.body.style.display = 'none';
|
||||
storage.session.set('lastGame', selectedVersion);
|
||||
// @ts-expect-error
|
||||
window.top.location.href = selectedVersion;
|
||||
} else {
|
||||
alert('Please select a version to play.');
|
||||
return;
|
||||
}
|
||||
},
|
||||
select: function (path: string, name?: string) {
|
||||
selectedVersion = path;
|
||||
const selector = document.querySelector('.custom-select');
|
||||
if (selector?.textContent) {
|
||||
if (name) {
|
||||
selector.textContent = `Selected: ${name}`;
|
||||
} else {
|
||||
selector.textContent = `Selected: ${path}`;
|
||||
}
|
||||
}
|
||||
versionSelector.close();
|
||||
},
|
||||
archive: function (client: string) {
|
||||
const clients: Record<string, string> = {
|
||||
'1.8': '18-client-version',
|
||||
'1.5': '15-client-version',
|
||||
'b1.3': 'b13-client-version',
|
||||
};
|
||||
const dropdown = clients[client] ? (document.querySelector(`#${clients[client]}`) as HTMLSelectElement | null) : null;
|
||||
if (dropdown?.value) {
|
||||
selectedVersion = `https://archive.eaglercraft.rip/Eaglercraft${client === '1.8' ? 'X_1.8' : `_${client}`}/client/${dropdown.value}/index.html`;
|
||||
game.play();
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
const navigate = {
|
||||
home: {
|
||||
game: function () {
|
||||
document.body.style.display = 'none';
|
||||
const navUrl = '/home/game/';
|
||||
storage.session.set('lastPage', navUrl);
|
||||
window.location.href = navUrl;
|
||||
},
|
||||
clients: function () {
|
||||
document.body.style.display = 'none';
|
||||
const navUrl = '/home/clients/';
|
||||
storage.session.set('lastPage', navUrl);
|
||||
window.location.href = navUrl;
|
||||
},
|
||||
archive: function () {
|
||||
document.body.style.display = 'none';
|
||||
const navUrl = '/home/archive/';
|
||||
storage.session.set('lastPage', navUrl);
|
||||
window.location.href = navUrl;
|
||||
},
|
||||
downloads: function () {
|
||||
document.body.style.display = 'none';
|
||||
const navUrl = '/home/downloads/';
|
||||
storage.session.set('lastPage', navUrl);
|
||||
window.location.href = navUrl;
|
||||
},
|
||||
},
|
||||
mods: {
|
||||
mods: function () {
|
||||
document.body.style.display = 'none';
|
||||
const navUrl = '/mods/mods/';
|
||||
storage.session.set('lastPage', navUrl);
|
||||
window.location.href = navUrl;
|
||||
},
|
||||
resourcepacks: function () {
|
||||
document.body.style.display = 'none';
|
||||
const navUrl = '/mods/resourcepacks/';
|
||||
storage.session.set('lastPage', navUrl);
|
||||
window.location.href = navUrl;
|
||||
},
|
||||
},
|
||||
articles: function () {
|
||||
const navUrl = '/articles/';
|
||||
storage.session.set('lastPage', navUrl);
|
||||
window.location.href = navUrl;
|
||||
},
|
||||
mobile: function () {
|
||||
const navUrl = '/mobile/';
|
||||
storage.session.set('lastPage', navUrl);
|
||||
window.location.href = navUrl;
|
||||
},
|
||||
updates: function () {
|
||||
const navUrl = '/updates/';
|
||||
storage.session.set('lastPage', navUrl);
|
||||
window.location.href = navUrl;
|
||||
},
|
||||
servers: function () {
|
||||
const navUrl = '/servers/';
|
||||
storage.session.set('lastPage', navUrl);
|
||||
window.location.href = navUrl;
|
||||
},
|
||||
settings: function () {
|
||||
const navUrl = '/settings/';
|
||||
storage.session.set('lastPage', navUrl);
|
||||
window.location.href = navUrl;
|
||||
},
|
||||
};
|
||||
|
||||
const article = {
|
||||
open: function (articleId: string) {
|
||||
const modal = document.querySelector(`#article-${articleId}`) as HTMLElement | null;
|
||||
const modalContent = document.querySelector(`#article-${articleId} .article-content`) as HTMLElement | null;
|
||||
if (!articleAnimationLock && modal && modalContent) {
|
||||
articleAnimationLock = true;
|
||||
modal.style.animation = '0.5s ease-in-out 1 normal article';
|
||||
modal.style.display = 'flex';
|
||||
modalContent.scroll({ top: 0, left: 0, behavior: 'instant' });
|
||||
modal.addEventListener(
|
||||
'animationend',
|
||||
() => {
|
||||
const closeArticleHandler = (event: Event) => {
|
||||
if (event.target === modal) {
|
||||
modal.removeEventListener('click', closeArticleHandler);
|
||||
article.close(articleId);
|
||||
}
|
||||
};
|
||||
modal.addEventListener('click', closeArticleHandler);
|
||||
articleAnimationLock = false;
|
||||
},
|
||||
{ once: true }
|
||||
);
|
||||
}
|
||||
},
|
||||
close: function (articleId: string) {
|
||||
const modal = document.querySelector(`#article-${articleId}`) as HTMLElement | null;
|
||||
if (!articleAnimationLock && modal) {
|
||||
articleAnimationLock = true;
|
||||
modal.style.animation = '0.5s ease-in-out 1 reverse article-tempfix';
|
||||
modal.addEventListener(
|
||||
'animationend',
|
||||
() => {
|
||||
modal.style.display = 'none';
|
||||
articleAnimationLock = false;
|
||||
},
|
||||
{ once: true }
|
||||
);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
/*const cookie = {
|
||||
set: function (key: string, value: string | number | object | [] | boolean | null | undefined, days: number) {
|
||||
let maxAge;
|
||||
if (days) {
|
||||
maxAge = days * 60 * 60 * 24;
|
||||
} else {
|
||||
maxAge = 31536000;
|
||||
}
|
||||
document.cookie = `${encodeURIComponent(key)}=${encodeURIComponent(value)}; Max-Age=${maxAge}; Path=/; SameSite=Lax; Secure`;
|
||||
},
|
||||
get: function (key: string) {
|
||||
for (const cookie of document.cookie.split('; ')) {
|
||||
const cookiePair = cookie.split('=');
|
||||
if (encodeURIComponent(key) === cookiePair[0]) {
|
||||
return decodeURIComponent(cookiePair[1]);
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
},
|
||||
delete: function (key: string) {
|
||||
document.cookie = `${encodeURIComponent(key)}=; Max-Age=0; Path=/`;
|
||||
},
|
||||
};*/
|
||||
|
||||
const storage = {
|
||||
local: {
|
||||
get: function (key: string) {
|
||||
const item = localStorage.getItem('minexlauncher');
|
||||
if (item !== null) {
|
||||
const json = JSON.parse(item);
|
||||
if (json[key] !== undefined) {
|
||||
return json[key];
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
return undefined;
|
||||
},
|
||||
set: function (key: string, value: string | number | object | boolean | null | undefined) {
|
||||
let item = localStorage.getItem('minexlauncher');
|
||||
if (item === null) {
|
||||
item = '{}';
|
||||
}
|
||||
const json = JSON.parse(item);
|
||||
json[key] = value;
|
||||
localStorage.setItem('minexlauncher', JSON.stringify(json));
|
||||
},
|
||||
},
|
||||
session: {
|
||||
get: function (key: string) {
|
||||
const item = sessionStorage.getItem('minexlauncher');
|
||||
if (item !== null) {
|
||||
const json = JSON.parse(item);
|
||||
if (json[key] !== undefined) {
|
||||
return json[key];
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
return undefined;
|
||||
},
|
||||
set: function (key: string, value: string | number | object | boolean | null | undefined) {
|
||||
let item = sessionStorage.getItem('minexlauncher');
|
||||
if (item === null) {
|
||||
item = '{}';
|
||||
}
|
||||
const json = JSON.parse(item);
|
||||
json[key] = value;
|
||||
sessionStorage.setItem('minexlauncher', JSON.stringify(json));
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const query = {
|
||||
get: function (name: string) {
|
||||
return new URLSearchParams(window.top?.location.search).get(name);
|
||||
},
|
||||
};
|
||||
|
||||
const detect = {
|
||||
mobile: function (): boolean {
|
||||
try {
|
||||
document.exitPointerLock();
|
||||
return /Mobi/i.test(window.navigator.userAgent);
|
||||
} catch {
|
||||
return true;
|
||||
}
|
||||
},
|
||||
landscape: function () {
|
||||
return window.innerWidth > window.innerHeight;
|
||||
},
|
||||
};
|
||||
|
||||
const mods = {
|
||||
check: function (mod: string): boolean {
|
||||
const mods: string[] = storage.local.get('mods') ?? [];
|
||||
return mods.includes(mod);
|
||||
},
|
||||
add: function (mod: string): void {
|
||||
const mods: string[] = storage.local.get('mods') ?? [];
|
||||
if (!mods.includes(mod)) {
|
||||
mods.push(mod);
|
||||
mods.sort();
|
||||
storage.local.set('mods', mods);
|
||||
}
|
||||
},
|
||||
remove: function (mod: string): void {
|
||||
const mods: string[] = storage.local.get('mods') ?? [];
|
||||
const modIndex = mods.indexOf(mod);
|
||||
if (modIndex !== -1) {
|
||||
mods.splice(modIndex, 1);
|
||||
storage.local.set('mods', mods);
|
||||
}
|
||||
},
|
||||
toggle: function (modId: string): void {
|
||||
const mod = `/resources/mods/downloads/${modId}.js`;
|
||||
const mods: string[] = storage.local.get('mods') ?? [];
|
||||
const modIndex = mods.indexOf(mod);
|
||||
if (modIndex === -1) {
|
||||
mods.push(mod);
|
||||
mods.sort();
|
||||
storage.local.set('mods', mods);
|
||||
const modInstallElem = document.querySelector(`#mod-install-${modId}`);
|
||||
if (modInstallElem) {
|
||||
modInstallElem.textContent = 'Uninstall';
|
||||
modInstallElem.classList.add('installed');
|
||||
}
|
||||
} else {
|
||||
mods.splice(modIndex, 1);
|
||||
storage.local.set('mods', mods);
|
||||
const modInstallElem = document.querySelector(`#mod-install-${modId}`);
|
||||
if (modInstallElem) {
|
||||
modInstallElem.textContent = 'Install';
|
||||
modInstallElem.classList.remove('installed');
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
const sw = {
|
||||
register: function (url: string) {
|
||||
if ('serviceWorker' in navigator) {
|
||||
window.addEventListener('load', () => {
|
||||
navigator.serviceWorker.register(url).then(() => {
|
||||
navigator.serviceWorker.addEventListener('message', (event) => {
|
||||
if (event.origin === window.location.origin) {
|
||||
if (event.data.title === 'sw-install-complete') {
|
||||
alert('MineXLauncher is now ready for offline use!');
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
const base64Gzip = {
|
||||
decode: function (base64: string) {
|
||||
// Decode Base64 to binary string
|
||||
const binaryString = atob(base64);
|
||||
|
||||
// Convert binary string to Uint8Array
|
||||
const len = binaryString.length;
|
||||
const bytes = new Uint8Array(len);
|
||||
for (let i = 0; i < len; i++) {
|
||||
bytes[i] = binaryString.charCodeAt(i);
|
||||
}
|
||||
|
||||
// Use pako to decompress the Uint8Array
|
||||
const decompressed = inflate(bytes, { to: 'string' });
|
||||
|
||||
return decompressed;
|
||||
},
|
||||
encode: function (inputString: string) {
|
||||
// Convert the input string to a Uint8Array
|
||||
const encoder = new TextEncoder();
|
||||
const inputBytes = encoder.encode(inputString);
|
||||
|
||||
// Use pako to compress the Uint8Array
|
||||
const compressedBytes = deflate(inputBytes);
|
||||
|
||||
// Convert the compressed Uint8Array to a binary string
|
||||
let binaryString = '';
|
||||
for (const byte of compressedBytes) {
|
||||
binaryString += String.fromCharCode(byte);
|
||||
}
|
||||
|
||||
// Encode the binary string to Base64
|
||||
const base64String = btoa(binaryString);
|
||||
|
||||
return base64String;
|
||||
},
|
||||
};
|
||||
|
||||
if (detect.mobile()) {
|
||||
const link = document.createElement('link');
|
||||
link.rel = 'stylesheet';
|
||||
link.href = '/resources/styles/mobile.css';
|
||||
document.head.appendChild(link);
|
||||
}
|
||||
|
||||
if (window.location.pathname === '/') {
|
||||
const lastPage = storage.session.get('lastPage');
|
||||
const isMobile = detect.mobile();
|
||||
const iframe = document.createElement('iframe');
|
||||
iframe.src = !storage.local.get('lastVersion') ? '/welcome/' : lastPage ? lastPage : isMobile ? '/mobile/' : '/home/game/';
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => document.body.appendChild(iframe));
|
||||
|
||||
/* document.addEventListener('load', () => {
|
||||
if (storage.local.get('offlineCache')) {
|
||||
sw.register('/sw-full.js');
|
||||
} else {
|
||||
sw.register('/sw.js');
|
||||
}
|
||||
}); */
|
||||
document.addEventListener('load', () => sw.register('/sw.js'));
|
||||
|
||||
window.addEventListener('beforeinstallprompt', (event) => {
|
||||
// @ts-expect-error
|
||||
if (iframe.contentWindow) iframe.contentWindow.installPwaEvent = event;
|
||||
});
|
||||
} else {
|
||||
theme.load();
|
||||
|
||||
document.addEventListener('DOMContentLoaded', async () => {
|
||||
const profileName = document.querySelector('.profile-name');
|
||||
const titleBarText = document.querySelector('.title-bar span');
|
||||
|
||||
const lastVersion = storage.local.get('lastVersion');
|
||||
const updateData = (await (await fetch('/resources/data/main.json')).json()).updates;
|
||||
const currentVersion = updateData[0].version;
|
||||
const changelog = updateData[0].changelog.map((change: string) => ` - ${change}`).join('\n');
|
||||
|
||||
if (profileName) profileName.textContent = storage.local.get('username');
|
||||
if (titleBarText) titleBarText.textContent += ` ${currentVersion}`;
|
||||
|
||||
// @ts-expect-error
|
||||
if (lastVersion && gt(coerce(currentVersion, { includePrerelease: true }), coerce(lastVersion, { includePrerelease: true }))) {
|
||||
alert(`MineXLauncher has been updated to v${currentVersion}!\n\nChanges in v${currentVersion}:\n${changelog}`);
|
||||
storage.local.set('lastVersion', currentVersion);
|
||||
}
|
||||
});
|
||||
|
||||
/* if (storage.local.get('showAds')) {
|
||||
const googleAdsScript = document.createElement('script');
|
||||
googleAdsScript.async = true;
|
||||
googleAdsScript.src = 'https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-1132419379737567';
|
||||
googleAdsScript.crossOrigin = 'anonymous';
|
||||
document.head.appendChild(googleAdsScript);
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const googleAdsPush = document.createElement('script');
|
||||
googleAdsPush.text = '(adsbygoogle = window.adsbygoogle || []).push({});';
|
||||
document.body.appendChild(googleAdsPush);
|
||||
|
||||
const adsContainers = document.querySelectorAll('.ads-container');
|
||||
adsContainers.forEach((adsContainer) => {
|
||||
adsContainer.style.display = 'flex';
|
||||
});
|
||||
});
|
||||
} */
|
||||
}
|
||||
|
||||
if (window.location.pathname === '/settings/') {
|
||||
document.addEventListener('DOMContentLoaded', async () => {
|
||||
const profileName = document.querySelector('.profile-name');
|
||||
const usernameInput = document.querySelector('#username-input') as HTMLInputElement | null;
|
||||
const themeSelect = document.querySelector('#theme-select') as HTMLSelectElement | null;
|
||||
const offlineCheckbox = document.querySelector('#offline-checkbox') as HTMLInputElement | null;
|
||||
const adsCheckbox = document.querySelector('#ads-checkbox') as HTMLInputElement | null;
|
||||
const themeData: { id: string; name: string }[] = (await (await fetch('/resources/data/main.json')).json()).themes;
|
||||
|
||||
if (usernameInput) {
|
||||
usernameInput.placeholder = storage.local.get('username') ?? '';
|
||||
usernameInput.addEventListener('input', () => {
|
||||
let username = usernameInput.value.replace(/[^A-Za-z0-9]/g, '_').substring(0, 16);
|
||||
usernameInput.value = username;
|
||||
while (username.length < 3) username += '_';
|
||||
|
||||
storage.local.set('username', username);
|
||||
if (profileName) profileName.textContent = username;
|
||||
});
|
||||
}
|
||||
|
||||
if (themeSelect) {
|
||||
themeData.forEach((theme: { id: string; name: string }) => {
|
||||
const option = document.createElement('option');
|
||||
option.value = theme.id;
|
||||
option.textContent = theme.name;
|
||||
themeSelect?.appendChild(option);
|
||||
});
|
||||
themeSelect.value = storage.local.get('theme') ?? '';
|
||||
themeSelect.addEventListener('change', () => theme.set(themeSelect.value ?? 'default'));
|
||||
}
|
||||
|
||||
if (offlineCheckbox) {
|
||||
offlineCheckbox.checked = storage.local.get('offlineCache') ?? false;
|
||||
offlineCheckbox.addEventListener('change', () => {
|
||||
storage.local.set('offlineCache', offlineCheckbox.checked);
|
||||
if (offlineCheckbox.checked) {
|
||||
sw.register('/sw-full.js');
|
||||
alert(
|
||||
'Offline cache is now downloading.\nThe download size is about 1GB, so it may take a while.\n\nPlease do not leave this page while the download is in progress.\nYou will be notified when the download is complete.'
|
||||
);
|
||||
} else {
|
||||
sw.register('/sw.js');
|
||||
alert('Offline cache has been deleted.');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (adsCheckbox) {
|
||||
adsCheckbox.checked = storage.local.get('showAds');
|
||||
adsCheckbox.addEventListener('change', () => {
|
||||
storage.local.set('showAds', adsCheckbox.checked);
|
||||
const adsContainers = document.querySelectorAll('.ads-container');
|
||||
adsContainers.forEach((adsContainer: Element) => ((adsContainer as HTMLElement).style.display = 'none'));
|
||||
});
|
||||
}
|
||||
});
|
||||
} else if (window.location.pathname === '/welcome/') {
|
||||
document.addEventListener('DOMContentLoaded', async () => {
|
||||
const setupForm = document.querySelector('#setup-form') as HTMLFormElement | null;
|
||||
const usernameInput = document.querySelector('#username-input') as HTMLInputElement | null;
|
||||
const themeSelect = document.querySelector('#theme-select') as HTMLSelectElement | null;
|
||||
const offlineCheckbox = document.querySelector('#offline-checkbox') as HTMLInputElement | null;
|
||||
const themeData: { id: string; name: string }[] = (await (await fetch('/resources/data/main.json')).json()).themes;
|
||||
|
||||
if (setupForm) {
|
||||
if (usernameInput) {
|
||||
usernameInput.addEventListener('input', () => {
|
||||
const username = usernameInput.value.replace(/[^A-Za-z0-9]/g, '_').substring(0, 16);
|
||||
usernameInput.value = username;
|
||||
});
|
||||
}
|
||||
|
||||
if (themeSelect) {
|
||||
themeData.forEach((theme: { id: string; name: string }) => {
|
||||
const option = document.createElement('option');
|
||||
option.value = theme.id;
|
||||
option.textContent = theme.name;
|
||||
themeSelect?.appendChild(option);
|
||||
});
|
||||
themeSelect.addEventListener('change', () => theme.load(themeSelect.value ?? 'default'));
|
||||
}
|
||||
|
||||
setupForm.addEventListener('submit', async (event) => {
|
||||
event.preventDefault();
|
||||
|
||||
let username = usernameInput?.value.replace(/[^A-Za-z0-9]/g, '_').substring(0, 16);
|
||||
if (usernameInput) usernameInput.value = username ?? '';
|
||||
|
||||
if (!username) {
|
||||
alert('Please type a username.');
|
||||
return;
|
||||
} else {
|
||||
while (username.length < 3) username += '_';
|
||||
|
||||
storage.local.set('username', username);
|
||||
storage.local.set('theme', themeSelect?.value ?? 'default');
|
||||
// storage.local.set('offlineCache', offlineCheckbox?.checked ?? false);
|
||||
// storage.local.set('showAds', true);
|
||||
storage.local.set('mods', []);
|
||||
storage.local.set('lastVersion', (await (await fetch('/resources/data/main.json')).json()).updates[0].version);
|
||||
|
||||
if (offlineCheckbox?.checked) {
|
||||
sw.register('/sw-full.js');
|
||||
alert(
|
||||
'Offline cache is now downloading.\nThe download size is about 1GB, so it may take a while.\n\nPlease do not leave this page while the download is in progress.\nYou will be notified when the download is complete.'
|
||||
);
|
||||
} else sw.register('/sw.js');
|
||||
|
||||
// @ts-expect-error
|
||||
window.top.location.href = '/';
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
} else if (window.location.pathname === '/mods/mods/' || window.location.pathname === '/mods/resourcepacks/') {
|
||||
document.addEventListener('DOMContentLoaded', async () => {
|
||||
const addonType: 'mods' | 'resourcepacks' = window.location.pathname === '/mods/mods/' ? 'mods' : 'resourcepacks';
|
||||
const addonData: { id: string; name: string; description: string; author: string; authorLink: string; source: string }[] = (await (await fetch('/resources/data/main.json')).json()).addons;
|
||||
const modList = document.querySelector('.mod-list');
|
||||
// @ts-expect-error
|
||||
addonData[addonType].forEach((addon) => {
|
||||
const modItem = document.createElement('div');
|
||||
modItem.classList.add('mod-item');
|
||||
modItem.innerHTML = `<img class="mod-icon" loading="lazy" src="/resources/mods/icons/${addon.id}.webp" /><div class="mod-details"><strong class="mod-name">${
|
||||
addon.name
|
||||
}</strong><p class="mod-author">By <a href="${addon.authorLink}" target="_blank">${addon.author}</a></p><p class="mod-description">${addon.description}</p></div><div class="mod-links">${
|
||||
addonType === 'mods'
|
||||
? `<a href="/resources/mods/downloads/${addon.id}.js" class="mod-download" download>Download</a><a class="mod-install" id="mod-install-${addon.id}" onclick="mods.toggle('${addon.id}')">Install</a>`
|
||||
: `<a href="/resources/mods/downloads/${addon.id}.zip" class="mod-download" download>Download</a>`
|
||||
}</div>`;
|
||||
modList?.appendChild(modItem);
|
||||
});
|
||||
|
||||
if (addonType === 'mods') {
|
||||
const installedMods = storage.local.get('mods') ?? [];
|
||||
const modElements = document.querySelectorAll('.mod-install');
|
||||
modElements.forEach((modElement) => {
|
||||
const modId = /^mod-install-(.*)$/.exec(modElement.id)?.[1];
|
||||
if (installedMods.includes(`/resources/mods/downloads/${modId}.js`)) {
|
||||
modElement.textContent = 'Uninstall';
|
||||
modElement.classList.add('installed');
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
} else if (window.location.pathname === '/updates/') {
|
||||
document.addEventListener('DOMContentLoaded', async () => {
|
||||
const updatesContainer = document.querySelector('.updates-container');
|
||||
const updateData: { version: string; changelog: string[] }[] = (await (await fetch('/resources/data/main.json')).json()).updates;
|
||||
updateData.forEach((update) => {
|
||||
const versionHeader = document.createElement('strong');
|
||||
versionHeader.textContent = `MineXLauncher ${update.version}`;
|
||||
updatesContainer?.appendChild(versionHeader);
|
||||
|
||||
const changelog = document.createElement('ul');
|
||||
update.changelog.forEach((change) => {
|
||||
const changelogItem = document.createElement('li');
|
||||
changelogItem.textContent = change;
|
||||
changelog.appendChild(changelogItem);
|
||||
});
|
||||
|
||||
updatesContainer?.appendChild(changelog);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (window.location.hostname === null) {
|
||||
// Stop the minifier from removing these functions
|
||||
console.debug([navigate, query, versionSelector, game, mods, base64Gzip, article]);
|
||||
}
|
@ -1,9 +1,4 @@
|
||||
{
|
||||
"extends": ["@tsconfig/bun", "@tsconfig/strictest"],
|
||||
"exclude": ["node_modules", "public"],
|
||||
"compilerOptions": {
|
||||
//"outDir": "dist",
|
||||
//"noEmit": false,
|
||||
//"allowImportingTsExtensions": false
|
||||
}
|
||||
"exclude": ["node_modules", "public"]
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user