diff --git a/.github/workflows/nightly-custom-update-repo.yml b/.github/workflows/nightly-custom-update-repo.yml new file mode 100644 index 000000000..2de6d0bbf --- /dev/null +++ b/.github/workflows/nightly-custom-update-repo.yml @@ -0,0 +1,237 @@ +name: Nightly Custom Update Repo + +on: + workflow_dispatch: + +env: + UPDATE_REPO: 'owner/repo' # Change this to your fork before using this workflow for releases. + +jobs: + build: + name: Build + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + + - name: Setup Node.js v20.x + uses: actions/setup-node@v2 + with: + node-version: 20.x + + - name: Validate update repo + run: | + if [ "${{ env.UPDATE_REPO }}" = "owner/repo" ]; then + echo "Set UPDATE_REPO to your fork before running this workflow." + exit 1 + fi + + - name: Pack fork-update asar + run: | + npm i -g asar + node scripts/pack.js --update-repo "${{ env.UPDATE_REPO }}" --version nightly-$(git rev-parse HEAD | cut -c 1-7) --output app.asar + + - name: Upload artifact + uses: actions/upload-artifact@v4 + with: + name: built-asar-custom-update-repo + path: app.asar + retention-days: 1 + + test-linux-stable: + name: Test Linux Stable + + needs: build + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + + - name: Retrieve artifact + uses: actions/download-artifact@v4 + with: + name: built-asar-custom-update-repo + path: artifact + + - name: Extract artifact + run: | + cp artifact/app.asar . + + - name: Download Client with OpenAsar + run: | + wget "https://discord.com/api/download/stable?platform=linux&format=tar.gz" -O discord.tar.gz + tar xf discord.tar.gz + + sed -r -i "s/const config=require\('.\/config'\);/console.log\('ABRA'\);app.exit\(\); /" app.asar + cp app.asar Discord/resources/app.asar + + # workaround https://github.com/electron/electron/issues/42510 + sudo chown root ./Discord/chrome-sandbox + sudo chmod 4755 ./Discord/chrome-sandbox + + - name: Check if Discord will startup + run: | + xvfb-run -e /dev/stdout ./Discord/Discord --enable-logging | grep -m1 "ABRA" + timeout-minutes: 3 + + test-linux-canary: + name: Test Linux Canary + + needs: build + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + + - name: Retrieve artifact + uses: actions/download-artifact@v4 + with: + name: built-asar-custom-update-repo + path: artifact + + - name: Extract artifact + run: | + cp artifact/app.asar . + + - name: Download Client with OpenAsar + run: | + wget "https://discord.com/api/download/canary?platform=linux&format=tar.gz" -O discord.tar.gz + tar xf discord.tar.gz + + sed -r -i "s/const config=require\('.\/config'\);/console.log\('ABRA'\);app.exit\(\); /" app.asar + cp app.asar DiscordCanary/resources/app.asar + + # workaround https://github.com/electron/electron/issues/42510 + sudo chown root ./DiscordCanary/chrome-sandbox + sudo chmod 4755 ./DiscordCanary/chrome-sandbox + + - name: Check if Discord will startup + run: | + xvfb-run -e /dev/stdout ./DiscordCanary/DiscordCanary --enable-logging | grep -m1 "ABRA" + timeout-minutes: 3 + + test-win-stable: + name: Test Windows Stable + + needs: build + runs-on: windows-latest + + steps: + - uses: actions/checkout@v2 + + - name: Setup Node.js v20.x + uses: actions/setup-node@v2 + with: + node-version: 20.x + + - name: Retrieve artifact + uses: actions/download-artifact@v4 + with: + name: built-asar-custom-update-repo + path: artifact + + - name: Extract artifact + run: | + cp artifact/app.asar . + + - name: Setup Client + shell: bash + run: | + node scripts/downloadWin.js + tar xf client.tar + + # Install OpenAsar build and setup environment + sed -r -i "s/const config=require\('.\/config'\);/console.log\('ABRA'\);app.exit\(\); /" app.asar + sed -r -i "s/if\(next!=cur&&!options\?\.allowObsoleteHost\)/if\(false\) /" app.asar + cp -f app.asar files/resources/app.asar + mkdir discord + cp -rf files/ discord/app-1.0.0 + cd discord/app-1.0.0 + mkdir modules + + - name: Check if Discord will startup + run: | + cd discord/app-1.0.0 + ./Discord.exe | grep -m1 "ABRA" + timeout-minutes: 3 + shell: bash + + test-win-canary: + name: Test Windows Canary + + needs: build + runs-on: windows-latest + + steps: + - uses: actions/checkout@v2 + + - name: Setup Node.js v20.x + uses: actions/setup-node@v2 + with: + node-version: 20.x + + - name: Retrieve artifact + uses: actions/download-artifact@v4 + with: + name: built-asar-custom-update-repo + path: artifact + + - name: Extract artifact + run: | + cp artifact/app.asar . + + - name: Setup Client + shell: bash + run: | + node scripts/downloadWin.js canary + tar xf client.tar + + # Install OpenAsar build and setup environment + sed -r -i "s/const config=require\('.\/config'\);/console.log\('ABRA'\);app.exit\(\); /" app.asar + sed -r -i "s/if\(next!=cur&&!options\?\.allowObsoleteHost\)/if\(false\) /" app.asar + cp -f app.asar files/resources/app.asar + mkdir discord + cp -rf files/ discord/app-1.0.0 + cd discord/app-1.0.0 + mkdir modules + + - name: Check if Discord will startup + run: | + cd discord/app-1.0.0 + ./DiscordCanary.exe | grep -m1 "ABRA" + timeout-minutes: 3 + shell: bash + + release: + name: Release + needs: + - build + # - test-linux + # - test-win + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + + - name: Retrieve artifact + uses: actions/download-artifact@v4 + with: + name: built-asar-custom-update-repo + path: artifact + + - name: Extract artifact + run: | + cp artifact/app.asar . + + - name: GitHub Release + run: | + git tag -d nightly-fork || true + git push origin --delete nightly-fork || true + git tag nightly-fork + git push origin nightly-fork + gh release delete ${{ env.VERSION }} -y || true + gh release create ${{ env.VERSION }} -t "Nightly Fork" -n "$(bash scripts/nightlyNotes.sh)" ${{ env.FILES }} + env: + GITHUB_TOKEN: '${{ secrets.GITHUB_TOKEN }}' + VERSION: 'nightly-fork' + FILES: app.asar diff --git a/.github/workflows/nightly-disable-autoupdate.yml b/.github/workflows/nightly-disable-autoupdate.yml new file mode 100644 index 000000000..03d4af022 --- /dev/null +++ b/.github/workflows/nightly-disable-autoupdate.yml @@ -0,0 +1,227 @@ +name: Nightly Disable Auto-Update + +on: + workflow_dispatch: + +jobs: + build: + name: Build + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + + - name: Setup Node.js v20.x + uses: actions/setup-node@v2 + with: + node-version: 20.x + + - name: Pack no-auto-update asar + run: | + npm i -g asar + node scripts/pack.js --disable-autoupdate --version nightly-$(git rev-parse HEAD | cut -c 1-7)-localtest --output app.asar + + - name: Upload artifact + uses: actions/upload-artifact@v4 + with: + name: built-asar-no-autoupdate + path: app.asar + retention-days: 1 + + test-linux-stable: + name: Test Linux Stable + + needs: build + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + + - name: Retrieve artifact + uses: actions/download-artifact@v4 + with: + name: built-asar-no-autoupdate + path: artifact + + - name: Extract artifact + run: | + cp artifact/app.asar . + + - name: Download Client with OpenAsar + run: | + wget "https://discord.com/api/download/stable?platform=linux&format=tar.gz" -O discord.tar.gz + tar xf discord.tar.gz + + sed -r -i "s/const config=require\('.\/config'\);/console.log\('ABRA'\);app.exit\(\); /" app.asar + cp app.asar Discord/resources/app.asar + + # workaround https://github.com/electron/electron/issues/42510 + sudo chown root ./Discord/chrome-sandbox + sudo chmod 4755 ./Discord/chrome-sandbox + + - name: Check if Discord will startup + run: | + xvfb-run -e /dev/stdout ./Discord/Discord --enable-logging | grep -m1 "ABRA" + timeout-minutes: 3 + + test-linux-canary: + name: Test Linux Canary + + needs: build + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + + - name: Retrieve artifact + uses: actions/download-artifact@v4 + with: + name: built-asar-no-autoupdate + path: artifact + + - name: Extract artifact + run: | + cp artifact/app.asar . + + - name: Download Client with OpenAsar + run: | + wget "https://discord.com/api/download/canary?platform=linux&format=tar.gz" -O discord.tar.gz + tar xf discord.tar.gz + + sed -r -i "s/const config=require\('.\/config'\);/console.log\('ABRA'\);app.exit\(\); /" app.asar + cp app.asar DiscordCanary/resources/app.asar + + # workaround https://github.com/electron/electron/issues/42510 + sudo chown root ./DiscordCanary/chrome-sandbox + sudo chmod 4755 ./DiscordCanary/chrome-sandbox + + - name: Check if Discord will startup + run: | + xvfb-run -e /dev/stdout ./DiscordCanary/DiscordCanary --enable-logging | grep -m1 "ABRA" + timeout-minutes: 3 + + test-win-stable: + name: Test Windows Stable + + needs: build + runs-on: windows-latest + + steps: + - uses: actions/checkout@v2 + + - name: Setup Node.js v20.x + uses: actions/setup-node@v2 + with: + node-version: 20.x + + - name: Retrieve artifact + uses: actions/download-artifact@v4 + with: + name: built-asar-no-autoupdate + path: artifact + + - name: Extract artifact + run: | + cp artifact/app.asar . + + - name: Setup Client + shell: bash + run: | + node scripts/downloadWin.js + tar xf client.tar + + # Install OpenAsar build and setup environment + sed -r -i "s/const config=require\('.\/config'\);/console.log\('ABRA'\);app.exit\(\); /" app.asar + sed -r -i "s/if\(next!=cur&&!options\?\.allowObsoleteHost\)/if\(false\) /" app.asar + cp -f app.asar files/resources/app.asar + mkdir discord + cp -rf files/ discord/app-1.0.0 + cd discord/app-1.0.0 + mkdir modules + + - name: Check if Discord will startup + run: | + cd discord/app-1.0.0 + ./Discord.exe | grep -m1 "ABRA" + timeout-minutes: 3 + shell: bash + + test-win-canary: + name: Test Windows Canary + + needs: build + runs-on: windows-latest + + steps: + - uses: actions/checkout@v2 + + - name: Setup Node.js v20.x + uses: actions/setup-node@v2 + with: + node-version: 20.x + + - name: Retrieve artifact + uses: actions/download-artifact@v4 + with: + name: built-asar-no-autoupdate + path: artifact + + - name: Extract artifact + run: | + cp artifact/app.asar . + + - name: Setup Client + shell: bash + run: | + node scripts/downloadWin.js canary + tar xf client.tar + + # Install OpenAsar build and setup environment + sed -r -i "s/const config=require\('.\/config'\);/console.log\('ABRA'\);app.exit\(\); /" app.asar + sed -r -i "s/if\(next!=cur&&!options\?\.allowObsoleteHost\)/if\(false\) /" app.asar + cp -f app.asar files/resources/app.asar + mkdir discord + cp -rf files/ discord/app-1.0.0 + cd discord/app-1.0.0 + mkdir modules + + - name: Check if Discord will startup + run: | + cd discord/app-1.0.0 + ./DiscordCanary.exe | grep -m1 "ABRA" + timeout-minutes: 3 + shell: bash + + release: + name: Release + needs: + - build + # - test-linux + # - test-win + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + + - name: Retrieve artifact + uses: actions/download-artifact@v4 + with: + name: built-asar-no-autoupdate + path: artifact + + - name: Extract artifact + run: | + cp artifact/app.asar . + + - name: GitHub Release + run: | + git tag -d nightly-no-autoupdate || true + git push origin --delete nightly-no-autoupdate || true + git tag nightly-no-autoupdate + git push origin nightly-no-autoupdate + gh release delete ${{ env.VERSION }} -y || true + gh release create ${{ env.VERSION }} -t "Nightly No Auto-Update" -n "$(bash scripts/nightlyNotes.sh)" ${{ env.FILES }} + env: + GITHUB_TOKEN: '${{ secrets.GITHUB_TOKEN }}' + VERSION: 'nightly-no-autoupdate' + FILES: app.asar diff --git a/.gitignore b/.gitignore index 48bef8880..bf0c17f46 100644 --- a/.gitignore +++ b/.gitignore @@ -3,5 +3,8 @@ src/package-lock.json *.asar _* miniSrc/ +tmp/ +AGENTS.md +.venv/ -*.crswap # crostini tmp files \ No newline at end of file +*.crswap # crostini tmp files diff --git a/README.md b/README.md index 139af6698..f3185de1a 100644 --- a/README.md +++ b/README.md @@ -19,5 +19,12 @@ ## [Install Guide](https://github.com/GooseMod/OpenAsar/wiki/Install-Guide) +## Local Build +See [docs/build.md](docs/build.md) for local build instructions, including local test builds with `--disable-autoupdate`. + +## Changelog +See [docs/changelog.md](docs/changelog.md) for the current local work log / changelog. + ## Config + You can configure OpenAsar by clicking the "OpenAsar..." version info in the bottom of your settings sidebar, which will open the config window. diff --git a/docs/build.md b/docs/build.md new file mode 100644 index 000000000..816979d07 --- /dev/null +++ b/docs/build.md @@ -0,0 +1,82 @@ +# Local Build + +The GitHub nightly workflow builds OpenAsar by: + +- stamping a `nightly-` version into `src/index.js` +- stripping the `src/` tree with `node scripts/strip.js` +- packing the final archive with `asar pack` + +For local builds, use `scripts/pack.js`, which follows that same flow without modifying your working tree in place. + +## Requirements +- `node` +- `asar` + +Example install for `asar`: + +```bash +npm i -g asar +``` + +## Build With Normal Auto-Update Behavior +This keeps the default OpenAsar self-update behavior enabled. + +```bash +node scripts/pack.js --version nightly-$(git rev-parse --short HEAD) --output tmp/openasar-build/app.asar +``` + +This build updates from the default upstream release repo: + +```text +GooseMod/OpenAsar +``` + +## Build With A Custom Update Repo +Use this when you want a build to self-update from your own fork releases instead of upstream. + +```bash +node scripts/pack.js --update-repo owner/repo --version nightly-$(git rev-parse --short HEAD) --output tmp/openasar-build/app.asar +``` + +## Build With Auto-Update Disabled +Use this for local testing when you do not want the built `app.asar` to replace itself with the upstream nightly release on launch. + +```bash +node scripts/pack.js --disable-autoupdate --version nightly-$(git rev-parse --short HEAD)-localtest --output tmp/openasar-build/app.asar +``` + +## Output +All commands above produce: + +```text +tmp/openasar-build/app.asar +``` + +## Optional GitHub Workflows +The default upstream workflow remains in `.github/workflows/nightly.yml`. + +Additional opt-in workflow templates are included for forks: + +- `.github/workflows/nightly-disable-autoupdate.yml` +- `.github/workflows/nightly-custom-update-repo.yml` + +Both templates now mirror the main nightly pipeline structure more closely: + +- build an `app.asar` +- run the same Linux and Windows startup smoke tests +- publish a release in the repo where the workflow runs + +The custom update repo workflow is manual-only and requires editing: + +```text +UPDATE_REPO: 'owner/repo' +``` + +before use so releases point at the correct fork. + +The intended differences are: + +- `nightly-disable-autoupdate.yml`: packs with `--disable-autoupdate` +- `nightly-disable-autoupdate.yml`: publishes `nightly-no-autoupdate` +- `nightly-custom-update-repo.yml`: packs with `--update-repo owner/repo` +- `nightly-custom-update-repo.yml`: publishes `nightly-fork` diff --git a/docs/changelog.md b/docs/changelog.md new file mode 100644 index 000000000..19c1cedef --- /dev/null +++ b/docs/changelog.md @@ -0,0 +1,34 @@ +# Changelog + +## 2026-04-17 + +### BetterDiscord settings compatibility + +- Investigated [OpenAsar issue #222](https://github.com/GooseMod/OpenAsar/issues/222), [PR #225](https://github.com/GooseMod/OpenAsar/pull/225), and the [TirOFlanc/OpenAsar fork](https://github.com/TirOFlanc/OpenAsar). +- Reviewed the local BetterDiscord codebase to understand how it patches Discord's settings UI. +- Identified the main failure mode in `src/mainWindow.js`: OpenAsar's settings item injection was incorrectly gated behind version/footer detection. +- Reworked the settings injection flow in `src/mainWindow.js` so version injection and menu item injection are independent. +- Kept the injected menu entry based on cloning native Discord sidebar items instead of creating fresh nodes with hard-coded Discord classes. +- Added a settings-area-scoped `MutationObserver` so the OpenAsar item can be restored after BetterDiscord or Discord rerenders the sidebar. +- Kept a slower interval fallback to retry injection without relying on a global full-page observer. +- Evaluated a hybrid settings injection path that patched Discord's internal settings layout alongside DOM injection. +- Matched the current BetterDiscord settings DOM where `Advanced` is absent and `Developer` / `Log Out` are the stable fallback anchors. +- Identified that local test builds were being overwritten on first launch by OpenAsar's self-updater. +- Added a build-time auto-update flag so local test archives can be packed with self-updates disabled intentionally. +- Kept the default source behavior with self-updates enabled unless the build flag is explicitly set. +- Added `scripts/pack.js` to build local `app.asar` files with options such as `--disable-autoupdate` and `--update-repo owner/repo`. +- Confirmed the DOM-only settings injection works once local test builds stop being overwritten by the self-updater. +- Reverted the experimental hybrid settings patch and kept the DOM-only fix for the final code path. +- Local test build command: + `node scripts/pack.js --disable-autoupdate --version nightly-$(git rev-parse --short HEAD)-localtest --output tmp/openasar-build/app.asar` +- Added optional fork-oriented GitHub workflow templates for no-auto-update artifacts and custom update repos while leaving the main upstream nightly workflow unchanged. +- Found and fixed a pack-time stamping bug where `scripts/pack.js` only replaced the first `` placeholder even though `src/index.js` used that placeholder more than once. +- Fixed that placeholder bug by changing the pack step to replace all `` occurrences so packed builds do not keep half-stamped fallback logic. +- Found and fixed a runtime fallback bug where the initial `oaUpdateRepo` comparison collapsed stamped fork builds back to `GooseMod/OpenAsar` instead of preserving the custom repo. +- Fixed that runtime bug by switching the fallback check to `stampedUpdateRepo.startsWith('<')`, so unpacked source still defaults upstream while packed builds keep the explicitly stamped fork repo. +- Verified the fork-update path by packing a test build with `--disable-autoupdate --update-repo XxUnkn0wnxX/OpenAsar` and confirming the stamped temp build source kept the custom repo value. +- Expanded the two optional fork workflows so they follow the main nightly pipeline shape more closely instead of only uploading artifacts. +- Matched the extra workflows to the main pipeline by adding the same Linux stable/canary and Windows stable/canary startup smoke-test jobs plus the same release structure. +- Kept the intended workflow differences narrow: one packs with `--disable-autoupdate` and publishes `nightly-no-autoupdate`, and the other packs with `--update-repo owner/repo` and publishes `nightly-fork`. +- Verified the updated file with `node -c src/mainWindow.js`. +- Verified the repo still packs locally by producing `tmp/pack-test/app.asar`. diff --git a/scripts/pack.js b/scripts/pack.js new file mode 100644 index 000000000..7fb9b83b5 --- /dev/null +++ b/scripts/pack.js @@ -0,0 +1,127 @@ +const { cpSync, existsSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } = require('fs'); +const { join, resolve } = require('path'); +const { spawnSync } = require('child_process'); + +const root = resolve(__dirname, '..'); +const tmpRoot = join(root, 'tmp', 'pack-build'); + +const args = process.argv.slice(2); +let disableAutoUpdate = false; +let updateRepo = 'GooseMod/OpenAsar'; +let version; +let output = join(root, 'tmp', 'openasar-build', 'app.asar'); + +for (let i = 0; i < args.length; i++) { + const arg = args[i]; + + if (arg === '--disable-autoupdate') { + disableAutoUpdate = true; + continue; + } + + if (arg === '--version') { + version = args[++i]; + continue; + } + + if (arg === '--update-repo') { + updateRepo = args[++i]; + continue; + } + + if (arg === '--output') { + output = resolve(args[++i]); + continue; + } + + if (arg === '--help') { + console.log('Usage: node scripts/pack.js [--disable-autoupdate] [--update-repo ] [--version ] [--output ]'); + process.exit(0); + } + + throw new Error(`Unknown argument: ${arg}`); +} + +if (!/^[^/\s]+\/[^/\s]+$/.test(updateRepo)) throw new Error(`Invalid --update-repo value: ${updateRepo}`); + +if (!version) { + const git = spawnSync('git', ['rev-parse', '--short', 'HEAD'], { + cwd: root, + encoding: 'utf8' + }); + + const shortSha = git.status === 0 ? git.stdout.trim() : 'local'; + version = `nightly-${shortSha}`; +} + +const stripCode = code => code + .replace(/(^| )\/\/.*$/gm, '') + .replaceAll('const ', 'const~') + .replaceAll('let ', 'let~') + .replaceAll('var ', 'var~') + .replaceAll('class ', 'class~') + .replace(/get [^=}]/g, _ => _.replaceAll(' ', '~')) + .replaceAll('delete ', 'delete~') + .replaceAll(' extends ', '~extends~') + .replaceAll('typeof ', 'typeof~') + .replaceAll(' of ', '~of~') + .replaceAll(' in ', '~in~') + .replaceAll('case ', 'case~') + .replaceAll('await ', 'await~') + .replaceAll('new ', 'new~') + .replaceAll('return ', 'return~') + .replaceAll('function ', 'function~') + .replaceAll('void ', 'void~') + .replaceAll('throw ', 'throw~') + .replaceAll('async ', 'async~') + .replaceAll('else ', 'else~') + .replace('/([0-9]+) files/', '/([0-9]+)~files/') + .replace(/((['"`])[\s\S]*?\2)|[ \n]/g, (_, g1) => g1 || '') + .replaceAll('~', ' ') + .replaceAll('? ?', '??'); + +const fixHtml = code => code + .replaceAll(' loop', '~loop') + .replaceAll(' autoplay', '~autoplay') + .replaceAll(' src', '~src') + .replaceAll(' id', '~id'); + +const stripJs = path => writeFileSync(path, stripCode(readFileSync(path, 'utf8'))); +const stripHtml = path => writeFileSync(path, stripCode(fixHtml(readFileSync(path, 'utf8')))); +const stripJson = path => { + const data = JSON.parse(readFileSync(path, 'utf8')); + if (data.description) delete data.description; + writeFileSync(path, JSON.stringify(data)); +}; + +const stripTree = dirPath => readdirSync(dirPath, { withFileTypes: true }).forEach(entry => { + const path = join(dirPath, entry.name); + + if (entry.isDirectory()) return stripTree(path); + if (entry.name.endsWith('.js')) return stripJs(path); + if (entry.name.endsWith('.json')) return stripJson(path); + if (entry.name.endsWith('.html')) return stripHtml(path); +}); + +rmSync(tmpRoot, { recursive: true, force: true }); +mkdirSync(tmpRoot, { recursive: true }); +cpSync(join(root, 'src'), join(tmpRoot, 'src'), { recursive: true }); + +const indexPath = join(tmpRoot, 'src', 'index.js'); +let indexCode = readFileSync(indexPath, 'utf8'); +indexCode = indexCode.replace("global.oaVersion = 'nightly';", `global.oaVersion = '${version}';`); +indexCode = indexCode.replace('', disableAutoUpdate ? 'true' : 'false'); +indexCode = indexCode.replaceAll('', updateRepo); +writeFileSync(indexPath, indexCode); + +stripTree(join(tmpRoot, 'src')); + +mkdirSync(resolve(output, '..'), { recursive: true }); +const asar = spawnSync('asar', ['pack', join(tmpRoot, 'src'), output], { + cwd: root, + stdio: 'inherit' +}); + +if (asar.status !== 0) process.exit(asar.status ?? 1); + +if (existsSync(output)) console.log(output); diff --git a/src/asarUpdate.js b/src/asarUpdate.js index fd4113281..32da6c4fc 100644 --- a/src/asarUpdate.js +++ b/src/asarUpdate.js @@ -11,10 +11,14 @@ const redirs = url => new Promise(res => get(url, r => { // Minimal wrapper arou })); module.exports = async () => { // (Try) update asar + if (global.oaDisableAutoUpdate) return log('AsarUpdate', 'Skipping build-configured auto-update disable'); if (!oaVersion.includes('-')) return; + const releaseChannel = oaVersion.split('-')[0]; + const updateRepo = global.oaUpdateRepo || 'GooseMod/OpenAsar'; + log('AsarUpdate', 'Updating...'); - const res = (await redirs(`https://github.com/GooseMod/OpenAsar/releases/download/${oaVersion.split('-')[0]}/app.asar`)); + const res = (await redirs(`https://github.com/${updateRepo}/releases/download/${releaseChannel}/app.asar`)); let data = []; res.on('data', d => { diff --git a/src/index.js b/src/index.js index 41fe9bde5..904652622 100644 --- a/src/index.js +++ b/src/index.js @@ -2,7 +2,11 @@ const { join } = require('path'); global.log = (area, ...args) => console.log(`[\x1b[38;2;88;101;242mOpenAsar\x1b[0m > ${area}]`, ...args); // Make log global for easy usage everywhere +const defaultUpdateRepo = 'GooseMod/OpenAsar'; +const stampedUpdateRepo = ''; global.oaVersion = 'nightly'; +global.oaDisableAutoUpdate = '' === 'true'; +global.oaUpdateRepo = stampedUpdateRepo.startsWith('<') ? defaultUpdateRepo : stampedUpdateRepo; log('Init', 'OpenAsar', oaVersion); @@ -37,4 +41,4 @@ if (process.argv.includes('--overlay-host')) { // If overlay require('discord_overlay2/standalone_host.js'); // Start overlay } else { require('./bootstrap')(); // Start bootstrap -} \ No newline at end of file +} diff --git a/src/mainWindow.js b/src/mainWindow.js index 440bacbf4..fbe6a2abb 100644 --- a/src/mainWindow.js +++ b/src/mainWindow.js @@ -26,32 +26,126 @@ const themesync = async () => { }; // Settings injection -setInterval(() => { - const versionInfo = document.querySelector('[class*="sidebar"] [class*="compactInfo"]'); - if (!versionInfo || document.getElementById('openasar-ver')) return; +const openOpenAsarSettings = () => DiscordNative.ipc.send('DISCORD_UPDATED_QUOTES', 'o'); +const firstNode = (...getters) => { + for (const getter of getters) { + const node = getter(); + if (node) return node; + } +}; + +const sidebarHasSettingsMarkers = sidebar => !!sidebar && firstNode( + () => sidebar.querySelector('[data-settings-sidebar-item]'), + () => sidebar.querySelector('[data-list-item-id^="settings-sidebar___"]'), + () => sidebar.parentElement?.querySelector('.bd-version-info'), + () => sidebar.querySelector('[class*="compactInfo"]'), + () => sidebar.querySelector('[aria-label="User Settings"]'), + () => sidebar.querySelector('[aria-label="App Settings"]') +); + +const findSettingsSidebar = () => { + const candidates = [ + ...document.querySelectorAll('[data-list-id="settings-sidebar"]'), + ...document.querySelectorAll('[class*="sidebar"] [class*="nav"]') + ]; + + return candidates.find(sidebarHasSettingsMarkers); +}; + +const findVersionInfo = () => firstNode( + () => document.querySelector('.bd-version-info > div:nth-child(2)'), + () => document.querySelector('.bd-version-info'), + () => document.querySelector('[class*="sidebar"] [class*="compactInfo"]'), + () => [...document.querySelectorAll('[class*="sidebar"] [class*="info"] [class*="line"]')].find(x => x.textContent?.startsWith('Host ')) +); + +const findAdvancedItem = () => { + const sidebar = findSettingsSidebar(); + return firstNode( + () => sidebar?.querySelector('[data-list-item-id="settings-sidebar___advanced_sidebar_item"]'), + () => sidebar?.querySelector('[data-list-item-id*="advanced"]'), + () => sidebar?.querySelector('[data-settings-sidebar-item="advanced_panel"]')?.querySelector('[class*="item"]'), + () => sidebar?.querySelector('[data-settings-sidebar-item="developer_panel"] [role="listitem"]'), + () => sidebar?.querySelector('[data-list-item-id="settings-sidebar___developer_sidebar_item"]'), + () => sidebar?.querySelector('[data-list-item-id="settings-sidebar___logout_sidebar_item"]'), + () => { + const item = sidebar?.querySelector('[data-settings-sidebar-item="betterdiscord_settings_panel"]'); + return item?.previousElementSibling?.querySelector?.('[role="listitem"]') ?? item?.querySelector?.('[role="listitem"]'); + }, + () => document.querySelector('[data-tab-id="Advanced"]'), + () => [...(sidebar?.querySelectorAll('[class*="item"]') ?? [])].find(x => x.textContent === 'Advanced'), + () => [...document.querySelectorAll('[class*="item"]')].find(x => x.textContent === 'Advanced') + ); +}; + +const injectVersionInfo = () => { + if (document.getElementById('openasar-ver')) return; + + const versionInfo = findVersionInfo(); + if (!versionInfo) return; const oaVersionInfo = versionInfo.cloneNode(true); - const oaVersion = oaVersionInfo.children[0]; + const oaVersion = oaVersionInfo.children?.[0] ?? oaVersionInfo; oaVersion.id = 'openasar-ver'; oaVersion.textContent = 'OpenAsar ()'; - oaVersion.onclick = () => DiscordNative.ipc.send('DISCORD_UPDATED_QUOTES', 'o'); + oaVersion.onclick = openOpenAsarSettings; - oaVersionInfo.textContent = ''; - oaVersionInfo.appendChild(oaVersion); - versionInfo.parentElement.parentElement.lastElementChild.insertAdjacentElement('beforebegin', oaVersionInfo); + if (oaVersionInfo !== oaVersion) { + oaVersionInfo.textContent = ''; + oaVersionInfo.appendChild(oaVersion); + } + + const versionTarget = versionInfo.parentElement?.parentElement?.lastElementChild; + if (versionTarget) versionTarget.insertAdjacentElement('beforebegin', oaVersionInfo); + else versionInfo.insertAdjacentElement('afterend', oaVersionInfo); +}; +const injectSettingsItem = () => { if (document.getElementById('openasar-item')) return; - let advanced = document.querySelector('[data-list-item-id="settings-sidebar___advanced_sidebar_item"]'); - if (!advanced) advanced = document.querySelector('[class*="sidebar"] [class*="nav"] > [class*="section"]:nth-child(3) > :last-child'); - if (!advanced) advanced = [...document.querySelectorAll('[class*="item"]')].find(x => x.textContent === 'Advanced'); + + const advanced = findAdvancedItem(); + if (!advanced) return; const oaSetting = advanced.cloneNode(true); - oaSetting.querySelector('[class*="text"]').textContent = 'OpenAsar'; + const text = oaSetting.querySelector('[class*="text"]') ?? oaSetting; + oaSetting.id = 'openasar-item'; - oaSetting.onclick = oaVersion.onclick; + oaSetting.setAttribute('aria-label', 'OpenAsar'); + oaSetting.setAttribute('data-openasar', 'true'); + if (oaSetting.hasAttribute('data-list-item-id')) oaSetting.setAttribute('data-list-item-id', 'settings-sidebar___openasar_sidebar_item'); + if (oaSetting.hasAttribute('data-tab-id')) oaSetting.setAttribute('data-tab-id', 'OpenAsar'); + + text.textContent = 'OpenAsar'; + oaSetting.onclick = openOpenAsarSettings; advanced.insertAdjacentElement('afterend', oaSetting); -}, 800); +}; + +let settingsObserver; +let settingsObserverTarget; +const attachSettingsObserver = () => { + const sidebar = findSettingsSidebar(); + const nextTarget = sidebar?.closest('[class*="sidebar"]') ?? sidebar?.parentElement ?? sidebar; + if (!nextTarget || nextTarget === settingsObserverTarget) return; + + settingsObserver?.disconnect(); + settingsObserverTarget = nextTarget; + settingsObserver = new MutationObserver(() => { + injectVersionInfo(); + injectSettingsItem(); + attachSettingsObserver(); + }); + settingsObserver.observe(nextTarget, { childList: true, subtree: true }); +}; + +const syncSettingsInjection = () => { + injectVersionInfo(); + injectSettingsItem(); + attachSettingsObserver(); +}; + +setInterval(syncSettingsInjection, 1500); +syncSettingsInjection(); const injCSS = x => { const el = document.createElement('style');