diff --git a/.github/ISSUE_TEMPLATE/blank.yml b/.github/ISSUE_TEMPLATE/blank.yml index 89588f3d8..8deb782ca 100644 --- a/.github/ISSUE_TEMPLATE/blank.yml +++ b/.github/ISSUE_TEMPLATE/blank.yml @@ -5,9 +5,9 @@ body: - type: markdown attributes: value: | - ![Are you a developer? No? This form is not for you!](https://github.com/Vendicated/Vencord/blob/main/.github/ISSUE_TEMPLATE/developer-banner.png?raw=true) + ![Are you a developer? No? This form is not for you!](https://github.com/Equicord/Equicord/blob/main/.github/ISSUE_TEMPLATE/developer-banner.png?raw=true) - GitHub Issues are for development, not support! Please use our [support server](https://vencord.dev/discord) unless you are a Vencord Developer. + GitHub Issues are for development, not support! Please use our [support server](https://equicord.org/discord) unless you are a Vencord Developer. - type: textarea id: content diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b9aff5e65..b81e980a9 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -3,13 +3,15 @@ on: push: branches: - main + env: FORCE_COLOR: true GITHUB_TOKEN: ${{ secrets.ETOKEN }} REPO: Equicord/Equibored USERNAME: GitHub-Actions -permissions: write-all +permissions: + contents: write jobs: Build: @@ -38,9 +40,10 @@ jobs: - name: Generate plugin lists run: | - pnpm generatePluginJson dist/plugins.json - pnpm generateEquicordPluginJson dist/equicordplugins.json - pnpm generateVencordPluginJson dist/vencordplugins.json + pnpm generatePluginJson dist/plugins.json + pnpm generateEquicordPluginJson dist/equicordplugins.json + pnpm generateVencordPluginJson dist/vencordplugins.json + pnpm generateDevsList dist/devs.json - name: Collect files to be released run: | @@ -48,7 +51,7 @@ jobs: mkdir release cp browser/browser.* release - cp Vencord.user.{js,js.LEGAL.txt} release + cp Equicord.user.{js,js.LEGAL.txt} release # copy the plugin data jsons, the extension zips and the desktop/vesktop asars cp *.{json,zip,asar} release @@ -65,7 +68,6 @@ jobs: rm release/*.map - name: Upload Equicord Stable - if: ${{ github.ref_name == 'main' }} run: | gh release upload latest --clobber dist/release/* @@ -76,7 +78,7 @@ jobs: git clone https://$USERNAME:$GITHUB_TOKEN@github.com/$REPO.git plugins cd plugins - cp ../dist/release/*plugins.json . + cp ../dist/release/*.json . git add -A git commit -m "Plugins for https://github.com/$GITHUB_REPOSITORY/commit/$GITHUB_SHA" diff --git a/.github/workflows/nixosBuild.yml b/.github/workflows/nixosBuild.yml index 6890f7748..2ea5a91b9 100644 --- a/.github/workflows/nixosBuild.yml +++ b/.github/workflows/nixosBuild.yml @@ -7,7 +7,8 @@ env: FORCE_COLOR: true GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} -permissions: write-all +permissions: + contents: write jobs: Build: @@ -36,9 +37,10 @@ jobs: - name: Generate plugin lists run: | - pnpm generatePluginJson dist/plugins.json - pnpm generateEquicordPluginJson dist/equicordplugins.json - pnpm generateVencordPluginJson dist/vencordplugins.json + pnpm generatePluginJson dist/plugins.json + pnpm generateEquicordPluginJson dist/equicordplugins.json + pnpm generateVencordPluginJson dist/vencordplugins.json + pnpm generateDevsList dist/devs.json - name: Collect files to be released run: | @@ -46,7 +48,7 @@ jobs: mkdir release cp browser/browser.* release - cp Vencord.user.{js,js.LEGAL.txt} release + cp Equicord.user.{js,js.LEGAL.txt} release # copy the plugin data jsons, the extension zips and the desktop/vesktop asars cp *.{json,zip,asar} release diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 507a00894..23499757e 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -1,44 +1,98 @@ name: Release Browser Extension on: push: - tags: - - v* + branches: + - main + +env: + FORCE_COLOR: true + +permissions: + contents: write jobs: - Publish: + release: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + with: + fetch-depth: 0 - - name: check that tag matches package.json version + - name: Get package.json version + id: pkg + run: echo "version=v$(jq -r .version < package.json)" >> $GITHUB_OUTPUT + + - name: Get latest Git tag + id: tag run: | - pkg_version="v$(jq -r .version < package.json)" - if [[ "${{ github.ref_name }}" != "$pkg_version" ]]; then - echo "Tag ${{ github.ref_name }} does not match package.json version $pkg_version" >&2 - exit 1 + latest_tag=$(git tag --list 'v*' --sort=-v:refname | head -n 1 || echo "v0.0.0") + echo "latest=$latest_tag" >> $GITHUB_OUTPUT + + - name: Check if package.json version is newer + id: check + run: | + pkg="${{ steps.pkg.outputs.version }}" + latest="${{ steps.tag.outputs.latest }}" + + echo "Latest tag: $latest" + echo "Package version: $pkg" + + if [ "$pkg" != "$latest" ] && [ "$(sort -V <<<"$latest"$'\n'"$pkg" | tail -n1)" = "$pkg" ]; then + echo "release=true" >> $GITHUB_OUTPUT + else + echo "release=false" >> $GITHUB_OUTPUT fi - - uses: pnpm/action-setup@v3 + - name: Stop if not newer + if: steps.check.outputs.release == 'false' + run: | + echo "No new version to release (package.json ${{ steps.pkg.outputs.version }} <= latest tag ${{ steps.tag.outputs.latest }})" + exit 0 + + - name: Create Tag + if: steps.check.outputs.release == 'true' + uses: butlerlogic/action-autotag@1.1.2 + env: + GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" + with: + tag_prefix: "v" + + - name: Setup PNPM + if: steps.check.outputs.release == 'true' + uses: pnpm/action-setup@v3 - name: Use Node.js 20 + if: steps.check.outputs.release == 'true' uses: actions/setup-node@v4 with: node-version: 20 cache: "pnpm" - name: Install dependencies + if: steps.check.outputs.release == 'true' run: pnpm install --frozen-lockfile - name: Build web + if: steps.check.outputs.release == 'true' run: pnpm buildWebStandalone + - name: Create sources zip + if: steps.check.outputs.release == 'true' + run: zip -r dist/extension-sources.zip . -x "node_modules/*" -x "dist/*" -x ".git/*" + - name: Publish extension - run: | - cd dist/browser/chromium-unpacked - pnpx chrome-webstore-upload-cli@2.1.0 upload --auto-publish + if: steps.check.outputs.release == 'true' + run: pnpx publish-browser-extension@latest --chrome-zip dist/extension-chrome.zip --firefox-zip dist/extension-firefox.zip --firefox-sources-zip dist/extension-sources.zip --edge-zip dist/extension-chrome.zip + env: - EXTENSION_ID: ${{ secrets.CHROME_EXTENSION_ID }} - CLIENT_ID: ${{ secrets.CHROME_CLIENT_ID }} - CLIENT_SECRET: ${{ secrets.CHROME_CLIENT_SECRET }} - REFRESH_TOKEN: ${{ secrets.CHROME_REFRESH_TOKEN }} + CHROME_EXTENSION_ID: ${{ secrets.CHROME_EXTENSION_ID }} + CHROME_CLIENT_ID: ${{ secrets.CHROME_CLIENT_ID }} + CHROME_CLIENT_SECRET: ${{ secrets.CHROME_CLIENT_SECRET }} + CHROME_REFRESH_TOKEN: ${{ secrets.CHROME_REFRESH_TOKEN }} + FIREFOX_EXTENSION_ID: ${{ secrets.FIREFOX_EXTENSION_ID }} + FIREFOX_JWT_ISSUER: ${{ secrets.FIREFOX_JWT_ISSUER }} + FIREFOX_JWT_SECRET: ${{ secrets.FIREFOX_JWT_SECRET }} + EDGE_PRODUCT_ID: ${{ secrets.EDGE_PRODUCT_ID }} + EDGE_CLIENT_ID: ${{ secrets.EDGE_CLIENT_ID }} + EDGE_API_KEY: ${{ secrets.EDGE_API_KEY }} diff --git a/.github/workflows/reportBrokenPlugins.yml b/.github/workflows/reportBrokenPlugins.yml index 12cb1a5a5..b989f896f 100644 --- a/.github/workflows/reportBrokenPlugins.yml +++ b/.github/workflows/reportBrokenPlugins.yml @@ -20,9 +20,6 @@ on: env: FORCE_COLOR: true - GITHUB_TOKEN: ${{ secrets.ETOKEN }} - REPO: Equicord/Equibored - USERNAME: GitHub-Actions jobs: TestPlugins: @@ -95,27 +92,6 @@ jobs: done cat "$stable_output_file" "$canary_output_file" >> $GITHUB_STEP_SUMMARY - - git config --global user.name "GitHub-Actions" - git config --global user.email actions@github.com - git clone https://$USERNAME:$GITHUB_TOKEN@github.com/$REPO.git report - cd report - - cp ../dist/reporter*.json . - git add -A - - if git diff --cached --quiet; then - echo "Nothing to commit" - if [[ $exit_code -eq 0 ]]; then - exit 0 - else - exit 1 - fi - else - git commit -m "Reporter Results" - git push --force https://$USERNAME:$GITHUB_TOKEN@github.com/$REPO.git - fi - exit $exit_code env: WEBHOOK_URL: ${{ inputs.webhook_url || secrets.WEBHOOK_URL }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f3df68ee0..708974e68 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -9,8 +9,6 @@ on: - main - dev -permissions: write-all - jobs: Test: name: Test @@ -37,9 +35,3 @@ jobs: - name: Test if plugin structure is valid run: pnpm generatePluginJson - - - name: Create Tag - if: ${{ github.ref_name == 'main' }} - uses: butlerlogic/action-autotag@1.1.2 - env: - GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" diff --git a/.gitignore b/.gitignore index 9ed57e067..34faacbfa 100644 --- a/.gitignore +++ b/.gitignore @@ -29,3 +29,6 @@ ExtensionCache/ # vencord companion module cache .modules + +# wakatime related stuff +.wakatime-project diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 9cddd3aa6..bb9e82c58 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -4,6 +4,6 @@ "EditorConfig.EditorConfig", "GregorBiswanger.json2ts", "stylelint.vscode-stylelint", - "thororen.equicord-companion" + "sadan.vencord-user-companion" ] } \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1e945c0d0..9bc1e9616 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,6 +1,6 @@ -# Contributing to Vencord +# Contributing to Equicord -Vencord is a community project and welcomes any kind of contribution from anyone! +Equicord is a community project and welcomes any kind of contribution from anyone! We have development documentation for new contributors, which can be found at . @@ -20,8 +20,8 @@ Before starting your plugin: - Be in our discord - Check existing pull requests to see if someone is already working on a similar plugin -- Check our [plugin requests tracker](https://discord.com/channels/1173279886065029291/1173334591302553631) to see if there is an existing request, or if the same idea has been rejected -- If there isn't an existing request, [open one](https://discord.com/channels/1173279886065029291/1173334591302553631) yourself. +- Check our [plugin requests tracker](https://discord.com/channels/1173279886065029291/1419347113745059961) to see if there is an existing request, or if the same idea has been rejected +- If there isn't an existing request, [open one](https://discord.com/channels/1173279886065029291/1419347113745059961) yourself. and include that you'd like to work on this yourself. Then wait for feedback to see if the idea even has any chance of being accepted. Or maybe others have some ideas to improve it! - Familarise yourself with our plugin rules below to ensure your plugin is not banned diff --git a/README.md b/README.md index 63fa54e90..9d4992b1a 100644 --- a/README.md +++ b/README.md @@ -6,11 +6,11 @@ Equicord is a fork of [Vencord](https://github.com/Vendicated/Vencord), with over 300+ plugins. -You can join our [discord server](https://equicord.org/discord) for commits, changes, chat or even support. +You can join our [Discord server](https://equicord.org/discord) for commits, changes, chatting, or even support. ### Included Plugins -Our included plugins can be found [here](https://equicord.org/plugins) +Our included plugins can be found [here](https://equicord.org/plugins). ## Installing / Uninstalling @@ -25,7 +25,7 @@ MacOS Linux -- [GUI-X11](https://github.com/Equicord/Equilotl/releases/latest/download/Equilotl-x11) +- [GUI](https://github.com/Equicord/Equilotl/releases/latest/download/Equilotl-x11) - [CLI](https://github.com/Equicord/Equilotl/releases/latest/download/EquilotlCli-Linux) - [AUR](https://aur.archlinux.org/packages?O=0&K=equicord) @@ -68,12 +68,22 @@ Build Equicord: pnpm build ``` -Inject Equicord into your client: +Inject Equicord into your desktop client: ```shell pnpm inject ``` +Build Equicord for web: + +```shell +pnpm buildWeb +``` + +After building Equicord's web extension, locate the appropriate ZIP file in the `dist` directory and follow your browser’s guide for installing custom extensions, if supported. + +Note: Firefox extension zip requires Firefox for developers + ## Credits Thank you to [Vendicated](https://github.com/Vendicated) for creating [Vencord](https://github.com/Vendicated/Vencord) & [Suncord](https://github.com/verticalsync/Suncord) by [verticalsync](https://github.com/verticalsync) for helping when needed. @@ -90,7 +100,7 @@ Thank you to [Vendicated](https://github.com/Vendicated) for creating [Vencord]( ## Disclaimer -Discord is trademark of Discord Inc. and solely mentioned for the sake of descriptivity. +Discord is trademark of Discord Inc., and solely mentioned for the sake of descriptivity. Mentioning it does not imply any affiliation with or endorsement by Discord Inc. Vencord is not connected to Equicord and as such, all donation links go to Vendicated's donation link. @@ -101,8 +111,8 @@ Client modifications are against Discord’s Terms of Service. However, Discord is pretty indifferent about them and there are no known cases of users getting banned for using client mods! So you should generally be fine if you don’t use plugins that implement abusive behaviour. But no worries, all inbuilt plugins are safe to use! -Regardless, if your account is essential to you and getting disabled would be a disaster for you, you should probably not use any client mods (not exclusive to Equicord), just to be safe +Regardless, if your account is essential to you and getting disabled would be a disaster for you, you should probably not use any client mods (not exclusive to Equicord), just to be safe. -Additionally, make sure not to post screenshots with Equicord in a server where you might get banned for it +Additionally, make sure not to post screenshots with Equicord in a server where you might get banned for it. diff --git a/browser/VencordNativeStub.ts b/browser/VencordNativeStub.ts index ba789c666..70978f31b 100644 --- a/browser/VencordNativeStub.ts +++ b/browser/VencordNativeStub.ts @@ -19,13 +19,15 @@ /// /// +// Be very careful with imports in this file to avoid circular dependency issues. +// Only import pure modules that don't import other parts of Vencord. import monacoHtmlLocal from "file://monacoWin.html?minify"; -import * as DataStore from "../src/api/DataStore"; -import { debounce, localStorage } from "../src/utils"; -import { EXTENSION_BASE_URL } from "../src/utils/web-metadata"; -import { getTheme, Theme } from "../src/utils/discord"; -import { Settings } from "../src/Vencord"; +import * as DataStore from "@api/DataStore"; +import type { Settings } from "@api/Settings"; +import { debounce } from "@shared/debounce"; +import { localStorage } from "@utils/localStorage"; import { getStylusWebStoreUrl } from "@utils/web"; +import { EXTENSION_BASE_URL } from "@utils/web-metadata"; // listeners for ipc.on const cssListeners = new Set<(css: string) => void>(); @@ -90,6 +92,8 @@ window.VencordNative = { return; } + const { getTheme, Theme } = require("@utils/discord"); + win.baseUrl = EXTENSION_BASE_URL; win.setCss = setCssDebounced; win.getCurrentCss = () => VencordNative.quickCss.get(); diff --git a/browser/content.js b/browser/content.js index 57964af64..cd1f59793 100644 --- a/browser/content.js +++ b/browser/content.js @@ -5,12 +5,12 @@ if (typeof browser === "undefined") { const style = document.createElement("link"); style.type = "text/css"; style.rel = "stylesheet"; -style.href = browser.runtime.getURL("dist/Vencord.css"); +style.href = browser.runtime.getURL("dist/Equicord.css"); document.addEventListener( "DOMContentLoaded", () => { - document.documentElement.append(style); + document.body.insertAdjacentElement("afterend", style); window.postMessage({ type: "vencord:meta", meta: { diff --git a/browser/manifest.json b/browser/manifest.json index 96fcdc571..cb594c1a7 100644 --- a/browser/manifest.json +++ b/browser/manifest.json @@ -3,7 +3,7 @@ "minimum_chrome_version": "111", "name": "Equicord Web", "description": "The other cutest Discord mod now in your browser", - "author": "Thororen", + "author": "Equicord", "homepage_url": "https://github.com/Equicord/Equicord", "icons": { "128": "icon.png" @@ -33,7 +33,7 @@ "*://*.discord.com/*" ], "js": [ - "dist/Vencord.js" + "dist/Equicord.js" ], "all_frames": true, "world": "MAIN" diff --git a/browser/manifestv2.json b/browser/manifestv2.json index 172a42e50..22187aea9 100644 --- a/browser/manifestv2.json +++ b/browser/manifestv2.json @@ -3,7 +3,7 @@ "minimum_chrome_version": "91", "name": "Equicord Web", "description": "The other cutest Discord mod now in your browser", - "author": "Thororen", + "author": "Equicord", "homepage_url": "https://github.com/Equicord/Equicord", "icons": { "128": "icon.png" @@ -32,7 +32,7 @@ "*://*.discord.com/*" ], "js": [ - "dist/Vencord.js" + "dist/Equicord.js" ], "all_frames": true, "world": "MAIN" @@ -44,13 +44,22 @@ ] }, "web_accessible_resources": [ - "dist/Vencord.js", - "dist/Vencord.css" + "dist/Equicord.js", + "dist/Equicord.css" ], "browser_specific_settings": { "gecko": { - "id": "vencord-firefox@vendicated.dev", - "strict_min_version": "128.0" + "id": "firefox@equicord.org", + "strict_min_version": "128.0", + "data_collection_permissions": { + "required": [ + "none" + ], + "optional": [ + "authenticationInfo", + "locationInfo" + ] + } } } } \ No newline at end of file diff --git a/eslint.config.mjs b/eslint.config.mjs index fabceecd1..544de86aa 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -88,7 +88,7 @@ export default tseslint.config( "@stylistic/no-extra-semi": "error", // TS Rules - "@stylistic/func-call-spacing": ["error", "never"], + "@stylistic/function-call-spacing": ["error", "never"], // ESLint Rules "yoda": "error", @@ -113,7 +113,7 @@ export default tseslint.config( "no-useless-escape": [ "error", { - "extra": "i" + "allowRegexCharacters": ["i"] } ], "no-fallthrough": "error", @@ -138,7 +138,7 @@ export default tseslint.config( // Plugin Rules "simple-import-sort/imports": "error", "simple-import-sort/exports": "error", - "unused-imports/no-unused-imports": "error", + "unused-imports/no-unused-imports": "error" } } ); diff --git a/package.json b/package.json index e649e0326..2c1e75003 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "equicord", "private": "true", - "version": "1.12.9", + "version": "1.13.9.1", "description": "The other cutest Discord client mod", "homepage": "https://github.com/Equicord/Equicord#readme", "bugs": { @@ -23,12 +23,14 @@ "watch": "pnpm build --watch", "dev": "pnpm watch", "watchWeb": "pnpm buildWeb --watch", + "generateDevsList": "tsx scripts/generateDevsList.ts", "generatePluginJson": "tsx scripts/generatePluginList.ts", - "generateEquicordPluginJson": "tsx scripts/generateEquicordPluginList.ts", - "generateVencordPluginJson": "tsx scripts/generateVencordPluginList.ts", + "generateEquicordPluginJson": "tsx scripts/generatePluginList.ts --equicord", + "generateVencordPluginJson": "tsx scripts/generatePluginList.ts --vencord", "generateTypes": "tspc --emitDeclarationOnly --declaration --outDir packages/vencord-types --allowJs false", "inject": "node scripts/runInstaller.mjs -- --install", "uninject": "node scripts/runInstaller.mjs -- --uninstall", + "repair": "node scripts/runInstaller.mjs -- --repair", "lint": "eslint", "lint-styles": "stylelint \"src/**/*.css\" --ignore-pattern src/userplugins", "lint:fix": "pnpm lint --fix", @@ -40,7 +42,7 @@ "@ffmpeg/ffmpeg": "^0.12.10", "@ffmpeg/util": "^0.12.1", "@intrnl/xxhash64": "^0.1.2", - "@types/less": "^3.0.6", + "@types/less": "^3.0.8", "@types/stylus": "^0.48.42", "@types/tinycolor2": "^1.4.6", "@vap/core": "0.0.12", @@ -49,51 +51,52 @@ "gifenc": "github:mattdesl/gifenc#64842fca317b112a8590f8fef2bf3825da8f6fe3", "idb": "8.0.3", "jsqr": "1.4.0", - "monaco-editor": "^0.52.2", - "nanoid": "^5.1.5", + "monaco-editor": "^0.54.0", + "nanoid": "^5.1.6", "usercss-meta": "^0.12.0", "virtual-merge": "^1.0.1" }, "devDependencies": { - "@electron/asar": "^3.2.10", - "@stylistic/eslint-plugin": "^4.2.0", - "@types/chrome": "^0.0.312", + "@electron/asar": "^4.0.1", + "@stylistic/eslint-plugin": "^5.6.0", + "@types/adm-zip": "^0.5.7", + "@types/chrome": "^0.1.30", "@types/diff": "^7.0.2", - "@types/lodash": "^4.17.14", - "@types/node": "^22.13.13", + "@types/lodash": "^4.17.20", + "@types/node": "^24.10.1", "@types/react": "^19.0.10", "@types/react-dom": "^19.0.4", - "@types/yazl": "^2.4.5", + "@types/yazl": "^3.3.0", "@vencord/discord-types": "link:packages/discord-types", - "diff": "^7.0.0", - "esbuild": "^0.25.1", - "eslint": "9.20.1", + "adm-zip": "^0.5.16", + "diff": "^8.0.2", + "esbuild": "^0.27.0", + "eslint": "9.39.1", "eslint-import-resolver-alias": "^1.1.2", - "eslint-plugin-react": "^7.37.3", + "eslint-plugin-react": "^7.37.5", "eslint-plugin-simple-header": "^1.2.1", "eslint-plugin-simple-import-sort": "^12.1.1", - "eslint-plugin-unused-imports": "^4.1.4", + "eslint-plugin-unused-imports": "^4.3.0", "highlight.js": "11.11.1", "html-minifier-terser": "^7.2.0", "moment": "^2.22.2", - "puppeteer-core": "^24.4.0", + "puppeteer-core": "^24.30.0", "standalone-electron-types": "^34.2.0", - "stylelint": "^16.17.0", - "stylelint-config-standard": "^37.0.0", + "stylelint": "^16.25.0", + "stylelint-config-standard": "^39.0.1", "ts-patch": "^3.3.0", "ts-pattern": "^5.6.0", - "tsx": "^4.19.3", - "type-fest": "^4.38.0", - "typescript": "^5.8.2", - "typescript-eslint": "^8.28.0", + "tsx": "^4.20.6", + "type-fest": "^5.2.0", + "typescript": "^5.9.3", + "typescript-eslint": "^8.47.0", "typescript-transform-paths": "^3.5.5", "zip-local": "^0.3.5" }, - "packageManager": "pnpm@10.14.0", + "packageManager": "pnpm@10.22.0", "pnpm": { "patchedDependencies": { - "eslint@9.20.1": "patches/eslint@9.20.1.patch", - "@types/less@3.0.6": "patches/@types__less@3.0.6.patch" + "@types/less@3.0.8": "patches/@types__less@3.0.8.patch" }, "peerDependencyRules": { "ignoreMissing": [ @@ -110,7 +113,10 @@ }, "onlyBuiltDependencies": [ "esbuild" - ] + ], + "overrides": { + "@eslint/plugin-kit@<0.3.4": ">=0.3.4" + } }, "engines": { "node": ">=18" diff --git a/packages/discord-types/.npmignore b/packages/discord-types/.npmignore new file mode 100644 index 000000000..3c3629e64 --- /dev/null +++ b/packages/discord-types/.npmignore @@ -0,0 +1 @@ +node_modules diff --git a/packages/discord-types/CONTRIBUTING.md b/packages/discord-types/CONTRIBUTING.md new file mode 100644 index 000000000..3bef40926 --- /dev/null +++ b/packages/discord-types/CONTRIBUTING.md @@ -0,0 +1 @@ +Hint: https://docs.discord.food is an incredible resource and allows you to copy paste complete enums and interfaces diff --git a/packages/discord-types/enums/activity.ts b/packages/discord-types/enums/activity.ts new file mode 100644 index 000000000..513a65dee --- /dev/null +++ b/packages/discord-types/enums/activity.ts @@ -0,0 +1,30 @@ +export const enum ActivityType { + PLAYING = 0, + STREAMING = 1, + LISTENING = 2, + WATCHING = 3, + CUSTOM_STATUS = 4, + COMPETING = 5, + HANG_STATUS = 6 +} + +export const enum ActivityFlags { + INSTANCE = 1 << 0, + JOIN = 1 << 1, + /** @deprecated */ + SPECTATE = 1 << 2, + /** @deprecated */ + JOIN_REQUEST = 1 << 3, + SYNC = 1 << 4, + PLAY = 1 << 5, + PARTY_PRIVACY_FRIENDS = 1 << 6, + PARTY_PRIVACY_VOICE_CHANNEL = 1 << 7, + EMBEDDED = 1 << 8, + CONTEXTLESS = 1 << 9 +} + +export const enum ActivityStatusDisplayType { + NAME = 0, + STATE = 1, + DETAILS = 2 +} diff --git a/packages/discord-types/enums/channel.ts b/packages/discord-types/enums/channel.ts new file mode 100644 index 000000000..7aae546bd --- /dev/null +++ b/packages/discord-types/enums/channel.ts @@ -0,0 +1,15 @@ +export const enum ChannelType { + GUILD_TEXT = 0, + DM = 1, + GUILD_VOICE = 2, + GROUP_DM = 3, + GUILD_CATEGORY = 4, + GUILD_ANNOUNCEMENT = 5, + ANNOUNCEMENT_THREAD = 10, + PUBLIC_THREAD = 11, + PRIVATE_THREAD = 12, + GUILD_STAGE_VOICE = 13, + GUILD_DIRECTORY = 14, + GUILD_FORUM = 15, + GUILD_MEDIA = 16 +} \ No newline at end of file diff --git a/packages/discord-types/enums/index.ts b/packages/discord-types/enums/index.ts index e2227d09f..a25ff3b5d 100644 --- a/packages/discord-types/enums/index.ts +++ b/packages/discord-types/enums/index.ts @@ -1,2 +1,5 @@ +export * from "./activity"; +export * from "./channel"; export * from "./commands"; export * from "./messages"; +export * from "./misc"; diff --git a/packages/discord-types/enums/messages.ts b/packages/discord-types/enums/messages.ts index 9c0025b79..8aa6fbb38 100644 --- a/packages/discord-types/enums/messages.ts +++ b/packages/discord-types/enums/messages.ts @@ -11,3 +11,586 @@ export const enum StickerFormatType { LOTTIE = 3, GIF = 4 } + +export const enum MessageType { + /** + * A default message (see below) + * + * Value: 0 + * Name: DEFAULT + * Rendered Content: "{content}" + * Deletable: true + */ + DEFAULT = 0, + /** + * A message sent when a user is added to a group DM or thread + * + * Value: 1 + * Name: RECIPIENT_ADD + * Rendered Content: "{author} added {mentions [0] } to the {group/thread}." + * Deletable: false + */ + RECIPIENT_ADD = 1, + /** + * A message sent when a user is removed from a group DM or thread + * + * Value: 2 + * Name: RECIPIENT_REMOVE + * Rendered Content: "{author} removed {mentions [0] } from the {group/thread}." + * Deletable: false + */ + RECIPIENT_REMOVE = 2, + /** + * A message sent when a user creates a call in a private channel + * + * Value: 3 + * Name: CALL + * Rendered Content: participated ? "{author} started a call{ended ? " that lasted {duration}" : " — Join the call"}." : "You missed a call from {author} that lasted {duration}." + * Deletable: false + */ + CALL = 3, + /** + * A message sent when a group DM or thread's name is changed + * + * Value: 4 + * Name: CHANNEL_NAME_CHANGE + * Rendered Content: "{author} changed the {is_forum ? "post title" : "channel name"}: {content} " + * Deletable: false + */ + CHANNEL_NAME_CHANGE = 4, + /** + * A message sent when a group DM's icon is changed + * + * Value: 5 + * Name: CHANNEL_ICON_CHANGE + * Rendered Content: "{author} changed the channel icon." + * Deletable: false + */ + CHANNEL_ICON_CHANGE = 5, + /** + * A message sent when a message is pinned in a channel + * + * Value: 6 + * Name: CHANNEL_PINNED_MESSAGE + * Rendered Content: "{author} pinned a message to this channel." + * Deletable: true + */ + CHANNEL_PINNED_MESSAGE = 6, + /** + * A message sent when a user joins a guild + * + * Value: 7 + * Name: USER_JOIN + * Rendered Content: See user join message type , obtained via the formula timestamp_ms % 13 + * Deletable: true + */ + USER_JOIN = 7, + /** + * A message sent when a user subscribes to (boosts) a guild + * + * Value: 8 + * Name: PREMIUM_GUILD_SUBSCRIPTION + * Rendered Content: "{author} just boosted the server{content ? " {content} times"}!" + * Deletable: true + */ + PREMIUM_GUILD_SUBSCRIPTION = 8, + /** + * A message sent when a user subscribes to (boosts) a guild to tier 1 + * + * Value: 9 + * Name: PREMIUM_GUILD_SUBSCRIPTION_TIER_1 + * Rendered Content: "{author} just boosted the server{content ? " {content} times"}! {guild} has achieved Level 1! " + * Deletable: true + */ + PREMIUM_GUILD_SUBSCRIPTION_TIER_1 = 9, + /** + * A message sent when a user subscribes to (boosts) a guild to tier 2 + * + * Value: 10 + * Name: PREMIUM_GUILD_SUBSCRIPTION_TIER_2 + * Rendered Content: "{author} just boosted the server{content ? " {content} times"}! {guild} has achieved Level 2! " + * Deletable: true + */ + PREMIUM_GUILD_SUBSCRIPTION_TIER_2 = 10, + /** + * A message sent when a user subscribes to (boosts) a guild to tier 3 + * + * Value: 11 + * Name: PREMIUM_GUILD_SUBSCRIPTION_TIER_3 + * Rendered Content: "{author} just boosted the server{content ? " {content} times"}! {guild} has achieved Level 3! " + * Deletable: true + */ + PREMIUM_GUILD_SUBSCRIPTION_TIER_3 = 11, + /** + * A message sent when a news channel is followed + * + * Value: 12 + * Name: CHANNEL_FOLLOW_ADD + * Rendered Content: "{author} has added {content} to this channel. Its most important updates will show up here." + * Deletable: true + */ + CHANNEL_FOLLOW_ADD = 12, + /** + * A message sent when a guild is disqualified from discovery + * + * Value: 14 + * Name: GUILD_DISCOVERY_DISQUALIFIED + * Rendered Content: "This server has been removed from Server Discovery because it no longer passes all the requirements. Check Server Settings for more details." + * Deletable: true + */ + GUILD_DISCOVERY_DISQUALIFIED = 14, + /** + * A message sent when a guild requalifies for discovery + * + * Value: 15 + * Name: GUILD_DISCOVERY_REQUALIFIED + * Rendered Content: "This server is eligible for Server Discovery again and has been automatically relisted!" + * Deletable: true + */ + GUILD_DISCOVERY_REQUALIFIED = 15, + /** + * A message sent when a guild has failed discovery requirements for a week + * + * Value: 16 + * Name: GUILD_DISCOVERY_GRACE_PERIOD_INITIAL_WARNING + * Rendered Content: "This server has failed Discovery activity requirements for 1 week. If this server fails for 4 weeks in a row, it will be automatically removed from Discovery." + * Deletable: true + */ + GUILD_DISCOVERY_GRACE_PERIOD_INITIAL_WARNING = 16, + /** + * A message sent when a guild has failed discovery requirements for 3 weeks + * + * Value: 17 + * Name: GUILD_DISCOVERY_GRACE_PERIOD_FINAL_WARNING + * Rendered Content: "This server has failed Discovery activity requirements for 3 weeks in a row. If this server fails for 1 more week, it will be removed from Discovery." + * Deletable: true + */ + GUILD_DISCOVERY_GRACE_PERIOD_FINAL_WARNING = 17, + /** + * A message sent when a thread is created + * + * Value: 18 + * Name: THREAD_CREATED + * Rendered Content: "{author} started a thread: {content} . See all threads." + * Deletable: true + */ + THREAD_CREATED = 18, + /** + * A message sent when a user replies to a message + * + * Value: 19 + * Name: REPLY + * Rendered Content: "{content}" + * Deletable: true + */ + REPLY = 19, + /** + * A message sent when a user uses a slash command + * + * Value: 20 + * Name: CHAT_INPUT_COMMAND + * Rendered Content: "{content}" + * Deletable: true + */ + CHAT_INPUT_COMMAND = 20, + /** + * A message sent when a thread starter message is added to a thread + * + * Value: 21 + * Name: THREAD_STARTER_MESSAGE + * Rendered Content: "{referenced_message?.content}" ?? "Sorry, we couldn't load the first message in this thread" + * Deletable: false + */ + THREAD_STARTER_MESSAGE = 21, + /** + * A message sent to remind users to invite friends to a guild + * + * Value: 22 + * Name: GUILD_INVITE_REMINDER + * Rendered Content: "Wondering who to invite?\nStart by inviting anyone who can help you build the server!" + * Deletable: true + */ + GUILD_INVITE_REMINDER = 22, + /** + * A message sent when a user uses a context menu command + * + * Value: 23 + * Name: CONTEXT_MENU_COMMAND + * Rendered Content: "{content}" + * Deletable: true + */ + CONTEXT_MENU_COMMAND = 23, + /** + * A message sent when auto moderation takes an action + * + * Value: 24 + * Name: AUTO_MODERATION_ACTION + * Rendered Content: Special embed rendered from embeds[0] + * Deletable: true 1 + */ + AUTO_MODERATION_ACTION = 24, + /** + * A message sent when a user purchases or renews a role subscription + * + * Value: 25 + * Name: ROLE_SUBSCRIPTION_PURCHASE + * Rendered Content: "{author} {is_renewal ? "renewed" : "joined"} {role_subscription.tier_name} and has been a subscriber of {guild} for {role_subscription.total_months_subscribed} month(?s)!" + * Deletable: true + */ + ROLE_SUBSCRIPTION_PURCHASE = 25, + /** + * A message sent when a user is upsold to a premium interaction + * + * Value: 26 + * Name: INTERACTION_PREMIUM_UPSELL + * Rendered Content: "{content}" + * Deletable: true + */ + INTERACTION_PREMIUM_UPSELL = 26, + /** + * A message sent when a stage channel starts + * + * Value: 27 + * Name: STAGE_START + * Rendered Content: "{author} started {content} " + * Deletable: true + */ + STAGE_START = 27, + /** + * A message sent when a stage channel ends + * + * Value: 28 + * Name: STAGE_END + * Rendered Content: "{author} ended {content} " + * Deletable: true + */ + STAGE_END = 28, + /** + * A message sent when a user starts speaking in a stage channel + * + * Value: 29 + * Name: STAGE_SPEAKER + * Rendered Content: "{author} is now a speaker." + * Deletable: true + */ + STAGE_SPEAKER = 29, + /** + * A message sent when a user raises their hand in a stage channel + * + * Value: 30 + * Name: STAGE_RAISE_HAND + * Rendered Content: "{author} requested to speak." + * Deletable: true + */ + STAGE_RAISE_HAND = 30, + /** + * A message sent when a stage channel's topic is changed + * + * Value: 31 + * Name: STAGE_TOPIC + * Rendered Content: "{author} changed the Stage topic: {content} " + * Deletable: true + */ + STAGE_TOPIC = 31, + /** + * A message sent when a user purchases an application premium subscription + * + * Value: 32 + * Name: GUILD_APPLICATION_PREMIUM_SUBSCRIPTION + * Rendered Content: "{author} upgraded {application ?? "a deleted application"} to premium for this server!" + * Deletable: true + */ + GUILD_APPLICATION_PREMIUM_SUBSCRIPTION = 32, + /** + * A message sent when a user gifts a premium (Nitro) referral + * + * Value: 35 + * Name: PREMIUM_REFERRAL + * Rendered Content: "{content}" + * Deletable: true + */ + PREMIUM_REFERRAL = 35, + /** + * A message sent when a user enabled lockdown for the guild + * + * Value: 36 + * Name: GUILD_INCIDENT_ALERT_MODE_ENABLED + * Rendered Content: "{author} enabled security actions until {content}." + * Deletable: true + */ + GUILD_INCIDENT_ALERT_MODE_ENABLED = 36, + /** + * A message sent when a user disables lockdown for the guild + * + * Value: 37 + * Name: GUILD_INCIDENT_ALERT_MODE_DISABLED + * Rendered Content: "{author} disabled security actions." + * Deletable: true + */ + GUILD_INCIDENT_ALERT_MODE_DISABLED = 37, + /** + * A message sent when a user reports a raid for the guild + * + * Value: 38 + * Name: GUILD_INCIDENT_REPORT_RAID + * Rendered Content: "{author} reported a raid in {guild}." + * Deletable: true + */ + GUILD_INCIDENT_REPORT_RAID = 38, + /** + * A message sent when a user reports a false alarm for the guild + * + * Value: 39 + * Name: GUILD_INCIDENT_REPORT_FALSE_ALARM + * Rendered Content: "{author} reported a false alarm in {guild}." + * Deletable: true + */ + GUILD_INCIDENT_REPORT_FALSE_ALARM = 39, + /** + * A message sent when no one sends a message in the current channel for 1 hour + * + * Value: 40 + * Name: GUILD_DEADCHAT_REVIVE_PROMPT + * Rendered Content: "{content}" + * Deletable: true + */ + GUILD_DEADCHAT_REVIVE_PROMPT = 40, + /** + * A message sent when a user buys another user a gift + * + * Value: 41 + * Name: CUSTOM_GIFT + * Rendered Content: Special embed rendered from embeds[0].url and gift_info + * Deletable: true + */ + CUSTOM_GIFT = 41, + /** + * Value: 42 + * Name: GUILD_GAMING_STATS_PROMPT + * Rendered Content: "{content}" + * Deletable: true + */ + GUILD_GAMING_STATS_PROMPT = 42, + /** + * A message sent when a user purchases a guild product + * + * Value: 44 + * Name: PURCHASE_NOTIFICATION + * Rendered Content: "{author} has purchased {purchase_notification.guild_product_purchase.product_name}!" + * Deletable: true + */ + PURCHASE_NOTIFICATION = 44, + /** + * A message sent when a poll is finalized + * + * Value: 46 + * Name: POLL_RESULT + * Rendered Content: Special embed rendered from embeds[0] + * Deletable: true + */ + POLL_RESULT = 46, + /** + * A message sent by the Discord Updates account when a new changelog is posted + * + * Value: 47 + * Name: CHANGELOG + * Rendered Content: "{content}" + * Deletable: true + */ + CHANGELOG = 47, + /** + * A message sent when a Nitro promotion is triggered + * + * Value: 48 + * Name: NITRO_NOTIFICATION + * Rendered Content: Special embed rendered from content + * Deletable: true + */ + NITRO_NOTIFICATION = 48, + /** + * A message sent when a voice channel is linked to a lobby + * + * Value: 49 + * Name: CHANNEL_LINKED_TO_LOBBY + * Rendered Content: "{content}" + * Deletable: true + */ + CHANNEL_LINKED_TO_LOBBY = 49, + /** + * A local-only ephemeral message sent when a user is prompted to gift Nitro to a friend on their friendship anniversary + * + * Value: 50 + * Name: GIFTING_PROMPT + * Rendered Content: Special embed + * Deletable: true + */ + GIFTING_PROMPT = 50, + /** + * A local-only message sent when a user receives an in-game message NUX + * + * Value: 51 + * Name: IN_GAME_MESSAGE_NUX + * Rendered Content: "{author} messaged you from {application.name}. In-game chat may not include rich messaging features such as images, polls, or apps. Learn More " + * Deletable: true + */ + IN_GAME_MESSAGE_NUX = 51, + /** + * A message sent when a user accepts a guild join request + * + * Value: 52 + * Name: GUILD_JOIN_REQUEST_ACCEPT_NOTIFICATION 2 + * Rendered Content: "{join_request.user}'s application to {content} was approved! Welcome!" + * Deletable: true + */ + GUILD_JOIN_REQUEST_ACCEPT_NOTIFICATION = 52, + /** + * A message sent when a user rejects a guild join request + * + * Value: 53 + * Name: GUILD_JOIN_REQUEST_REJECT_NOTIFICATION 2 + * Rendered Content: "{join_request.user}'s application to {content} was rejected." + * Deletable: true + */ + GUILD_JOIN_REQUEST_REJECT_NOTIFICATION = 53, + /** + * A message sent when a user withdraws a guild join request + * + * Value: 54 + * Name: GUILD_JOIN_REQUEST_WITHDRAWN_NOTIFICATION 2 + * Rendered Content: "{join_request.user}'s application to {content} has been withdrawn." + * Deletable: true + */ + GUILD_JOIN_REQUEST_WITHDRAWN_NOTIFICATION = 54, + /** + * A message sent when a user upgrades to HD streaming + * + * Value: 55 + * Name: HD_STREAMING_UPGRADED + * Rendered Content: "{author} activated HD Splash Potion " + * Deletable: true + */ + HD_STREAMING_UPGRADED = 55, + /** + * A message sent when a user resolves a moderation report by deleting the offending message + * + * Value: 58 + * Name: REPORT_TO_MOD_DELETED_MESSAGE + * Rendered Content: "{author} deleted the message" + * Deletable: true + */ + REPORT_TO_MOD_DELETED_MESSAGE = 58, + /** + * A message sent when a user resolves a moderation report by timing out the offending user + * + * Value: 59 + * Name: REPORT_TO_MOD_TIMEOUT_USER + * Rendered Content: "{author} timed out {mentions [0] }" + * Deletable: true + */ + REPORT_TO_MOD_TIMEOUT_USER = 59, + /** + * A message sent when a user resolves a moderation report by kicking the offending user + * + * Value: 60 + * Name: REPORT_TO_MOD_KICK_USER + * Rendered Content: "{author} kicked {mentions [0] }" + * Deletable: true + */ + REPORT_TO_MOD_KICK_USER = 60, + /** + * A message sent when a user resolves a moderation report by banning the offending user + * + * Value: 61 + * Name: REPORT_TO_MOD_BAN_USER + * Rendered Content: "{author} banned {mentions [0] }" + * Deletable: true + */ + REPORT_TO_MOD_BAN_USER = 61, + /** + * A message sent when a user resolves a moderation report + * + * Value: 62 + * Name: REPORT_TO_MOD_CLOSED_REPORT + * Rendered Content: "{author} resolved this flag" + * Deletable: true + */ + REPORT_TO_MOD_CLOSED_REPORT = 62, + /** + * A message sent when a user adds a new emoji to a guild + * + * Value: 63 + * Name: EMOJI_ADDED + * Rendered Content: "{author} added a new emoji, {content} :{emoji.name}: " + * Deletable: true + */ + EMOJI_ADDED = 63, +} + +export const enum MessageFlags { + /** + * Message has been published to subscribed channels (via Channel Following) + * + * Value: 1 << 0 + */ + CROSSPOSTED = 1 << 0, + /** + * Message originated from a message in another channel (via Channel Following) + */ + IS_CROSSPOST = 1 << 1, + /** + * Embeds will not be included when serializing this message + */ + SUPPRESS_EMBEDS = 1 << 2, + /** + * Source message for this crosspost has been deleted (via Channel Following) + */ + SOURCE_MESSAGE_DELETED = 1 << 3, + /** + * Message came from the urgent message system + */ + URGENT = 1 << 4, + /** + * Message has an associated thread, with the same ID as the message + */ + HAS_THREAD = 1 << 5, + /** + * Message is only visible to the user who invoked the interaction + */ + EPHEMERAL = 1 << 6, + /** + * Message is an interaction response and the bot is "thinking" + */ + LOADING = 1 << 7, + /** + * Some roles were not mentioned and added to the thread + */ + FAILED_TO_MENTION_SOME_ROLES_IN_THREAD = 1 << 8, + /** + * Message is hidden from the guild's feed + */ + GUILD_FEED_HIDDEN = 1 << 9, + /** + * Message contains a link that impersonates Discord + */ + SHOULD_SHOW_LINK_NOT_DISCORD_WARNING = 1 << 10, + /** + * Message will not trigger push and desktop notifications + */ + SUPPRESS_NOTIFICATIONS = 1 << 12, + /** + * Message's audio attachment is rendered as a voice message + */ + IS_VOICE_MESSAGE = 1 << 13, + /** + * Message has a forwarded message snapshot attached + */ + HAS_SNAPSHOT = 1 << 14, + /** + * Message contains components from version 2 of the UI kit + */ + IS_COMPONENTS_V2 = 1 << 15, + /** + * Message was triggered by the social layer integration + */ + SENT_BY_SOCIAL_LAYER_INTEGRATION = 1 << 16, +} diff --git a/packages/discord-types/enums/misc.ts b/packages/discord-types/enums/misc.ts new file mode 100644 index 000000000..d1419ba3e --- /dev/null +++ b/packages/discord-types/enums/misc.ts @@ -0,0 +1,4 @@ +export const enum CloudUploadPlatform { + REACT_NATIVE = 0, + WEB = 1, +} diff --git a/packages/discord-types/package.json b/packages/discord-types/package.json index 7b8726fd2..8c95d6749 100644 --- a/packages/discord-types/package.json +++ b/packages/discord-types/package.json @@ -1,6 +1,7 @@ { "name": "@vencord/discord-types", "author": "Vencord Contributors", + "private": false, "description": "Typescript definitions for the webpack modules of the Discord Web app", "version": "1.0.0", "license": "LGPL-3.0-or-later", @@ -12,8 +13,10 @@ "directory": "packages/discord-types" }, "dependencies": { - "@types/react": "^19.0.10", "moment": "^2.22.2", "type-fest": "^4.41.0" + }, + "peerDependencies": { + "@types/react": "^19.0.10" } } diff --git a/packages/discord-types/src/common/Activity.d.ts b/packages/discord-types/src/common/Activity.d.ts index a4df551a0..06e91e7bd 100644 --- a/packages/discord-types/src/common/Activity.d.ts +++ b/packages/discord-types/src/common/Activity.d.ts @@ -1,45 +1,51 @@ -import { CSSProperties, ImgHTMLAttributes, JSX } from "react"; -import { Application } from "./Application"; -import { User } from "./User"; +import { ActivityFlags, ActivityStatusDisplayType, ActivityType } from "../../enums"; -export interface ActivityTimestamp { - start?: number; - end?: number; +export interface ActivityAssets { + large_image?: string; + large_text?: string; + large_url?: string; + small_image?: string; + small_text?: string; + small_url?: string; +} + +export interface ActivityButton { + label: string; + url: string; } export interface Activity { - created_at: number; - id: string; + created_at?: number; + id?: string; name: string; - type: number; + application_id: string; + type: ActivityType; emoji?: { animated: boolean; id: string; name: string; }; state?: string; - flags?: number; - sync_id?: string; + state_url?: string; details?: string; - application_id?: string; - assets?: { - large_text?: string; - large_image?: string; - small_text?: string; - small_image?: string; + details_url?: string; + url?: string; + flags: ActivityFlags; + status_display_type?: ActivityStatusDisplayType; + timestamps?: { + start?: number; + end?: number; }; - buttons?: Array; + assets?: ActivityAssets; + buttons?: string[]; metadata?: { button_urls?: Array; }; - timestamps?: ActivityTimestamp; platform?: string; + party?: { + id?: string; + size?: [number, number]; + }; } -export const enum ActivityType { - PLAYING = 0, - STREAMING = 1, - LISTENING = 2, - WATCHING = 3, - COMPETING = 5 -} +export type OnlineStatus = "online" | "idle" | "dnd" | "invisible" | "offline" | "unknown" | "streaming"; diff --git a/packages/discord-types/src/common/Channel.d.ts b/packages/discord-types/src/common/Channel.d.ts index 7ad5ce535..8742c9c43 100644 --- a/packages/discord-types/src/common/Channel.d.ts +++ b/packages/discord-types/src/common/Channel.d.ts @@ -71,6 +71,7 @@ export class Channel extends DiscordRecord { isGroupDM(): boolean; isGuildStageVoice(): boolean; isGuildVoice(): boolean; + isGuildVocal(): boolean; isListenModeCapable(): boolean; isManaged(): boolean; isMultiUserDM(): boolean; diff --git a/packages/discord-types/src/common/Guild.d.ts b/packages/discord-types/src/common/Guild.d.ts index 5b5c3c745..65e973ffe 100644 --- a/packages/discord-types/src/common/Guild.d.ts +++ b/packages/discord-types/src/common/Guild.d.ts @@ -47,7 +47,7 @@ export class Guild extends DiscordRecord { }; premiumProgressBarEnabled: boolean; premiumSubscriberCount: number; - premiumTier: number; + premiumTier: 0 | 1 | 2 | 3; profile: { badge: string | undefined; tag: string | undefined; diff --git a/packages/discord-types/src/common/User.d.ts b/packages/discord-types/src/common/User.d.ts index 07eb47187..06f7b4067 100644 --- a/packages/discord-types/src/common/User.d.ts +++ b/packages/discord-types/src/common/User.d.ts @@ -62,4 +62,5 @@ export interface UserJSON { id: string; publicFlags: number; username: string; + globalName: string | undefined; } diff --git a/packages/discord-types/src/common/messages/Message.d.ts b/packages/discord-types/src/common/messages/Message.d.ts index e3586255a..4a6f3e8b5 100644 --- a/packages/discord-types/src/common/messages/Message.d.ts +++ b/packages/discord-types/src/common/messages/Message.d.ts @@ -2,9 +2,9 @@ import { CommandOption } from './Commands'; import { User, UserJSON } from '../User'; import { Embed, EmbedJSON } from './Embed'; import { DiscordRecord } from "../Record"; -import { StickerFormatType } from "../../../enums"; +import { ApplicationIntegrationType, MessageFlags, MessageType, StickerFormatType } from "../../../enums"; -/** +/* * TODO: looks like discord has moved over to Date instead of Moment; */ export class Message extends DiscordRecord { @@ -35,7 +35,7 @@ export class Message extends DiscordRecord { customRenderedContent: unknown; editedTimestamp: Date; embeds: Embed[]; - flags: number; + flags: MessageFlags; giftCodes: string[]; id: string; interaction: { @@ -71,6 +71,19 @@ export class Message extends DiscordRecord { type: number; version: string; }[]; + interactionMetadata?: { + id: string; + type: number; + name?: string; + command_type?: number; + ephemerality_reason?: number; + user: User; + authorizing_integration_owners: Record; + original_response_message_id?: string; + interacted_message_id?: string; + target_user?: User; + target_message_id?: string; + }; interactionError: unknown[]; isSearchHit: boolean; loggingName: unknown; @@ -100,7 +113,7 @@ export class Message extends DiscordRecord { stickers: unknown[]; timestamp: moment.Moment; tts: boolean; - type: number; + type: MessageType; webhookId: string | undefined; /** @@ -121,10 +134,13 @@ export class Message extends DiscordRecord { removeReaction(emoji: ReactionEmoji, fromCurrentUser: boolean): Message; getChannelId(): string; - hasFlag(flag: number): boolean; + hasFlag(flag: MessageFlags): boolean; isCommandType(): boolean; isEdited(): boolean; isSystemDM(): boolean; + + /** Vencord added */ + deleted?: boolean; } /** A smaller Message object found in FluxDispatcher and elsewhere. */ @@ -193,3 +209,9 @@ export interface MessageReaction { emoji: ReactionEmoji; me: boolean; } + +// Object.keys(findByProps("REPLYABLE")).map(JSON.stringify).join("|") +export type MessageTypeSets = Record< + "UNDELETABLE" | "GUILD_DISCOVERY_STATUS" | "USER_MESSAGE" | "NOTIFIABLE_SYSTEM_MESSAGE" | "REPLYABLE" | "FORWARDABLE" | "REFERENCED_MESSAGE_AVAILABLE" | "AVAILABLE_IN_GUILD_FEED" | "DEADCHAT_PROMPTS" | "NON_COLLAPSIBLE" | "NON_PARSED" | "AUTOMOD_INCIDENT_ACTIONS" | "SELF_MENTIONABLE_SYSTEM" | "SCHEDULABLE", + Set +>; diff --git a/packages/discord-types/src/components.d.ts b/packages/discord-types/src/components.d.ts index b5f1fb779..4fb634a12 100644 --- a/packages/discord-types/src/components.d.ts +++ b/packages/discord-types/src/components.d.ts @@ -1,55 +1,40 @@ import { Moment } from "moment"; import type { ComponentClass, ComponentPropsWithRef, ComponentType, CSSProperties, FunctionComponent, HtmlHTMLAttributes, HTMLProps, JSX, KeyboardEvent, MouseEvent, PointerEvent, PropsWithChildren, ReactNode, Ref, RefObject } from "react"; -// copy(find(m => Array.isArray(m) && m.includes("heading-sm/normal")).map(JSON.stringify).join("|")) -export type TextVariant = "heading-sm/normal" | "heading-sm/medium" | "heading-sm/semibold" | "heading-sm/bold" | "heading-sm/extrabold" | "heading-md/normal" | "heading-md/medium" | "heading-md/semibold" | "heading-md/bold" | "heading-md/extrabold" | "heading-lg/normal" | "heading-lg/medium" | "heading-lg/semibold" | "heading-lg/bold" | "heading-lg/extrabold" | "heading-xl/normal" | "heading-xl/medium" | "heading-xl/semibold" | "heading-xl/bold" | "heading-xl/extrabold" | "heading-xxl/normal" | "heading-xxl/medium" | "heading-xxl/semibold" | "heading-xxl/bold" | "heading-xxl/extrabold" | "eyebrow" | "heading-deprecated-12/normal" | "heading-deprecated-12/medium" | "heading-deprecated-12/semibold" | "heading-deprecated-12/bold" | "heading-deprecated-12/extrabold" | "redesign/heading-18/bold" | "text-xxs/normal" | "text-xxs/medium" | "text-xxs/semibold" | "text-xxs/bold" | "text-xs/normal" | "text-xs/medium" | "text-xs/semibold" | "text-xs/bold" | "text-sm/normal" | "text-sm/medium" | "text-sm/semibold" | "text-sm/bold" | "text-md/normal" | "text-md/medium" | "text-md/semibold" | "text-md/bold" | "text-lg/normal" | "text-lg/medium" | "text-lg/semibold" | "text-lg/bold" | "redesign/message-preview/normal" | "redesign/message-preview/medium" | "redesign/message-preview/semibold" | "redesign/message-preview/bold" | "redesign/channel-title/normal" | "redesign/channel-title/medium" | "redesign/channel-title/semibold" | "redesign/channel-title/bold" | "display-sm" | "display-md" | "display-lg" | "code"; -export type FormTextTypes = Record<"DEFAULT" | "INPUT_PLACEHOLDER" | "DESCRIPTION" | "LABEL_BOLD" | "LABEL_SELECTED" | "LABEL_DESCRIPTOR" | "ERROR" | "SUCCESS", string>; -export type HeadingTag = `h${1 | 2 | 3 | 4 | 5 | 6}`; +// #region Old compability + +export type HeadingTag = `h${1 | 2 | 3 | 4 | 5 | 6}`; export type Margins = Record<"marginTop16" | "marginTop8" | "marginBottom8" | "marginTop20" | "marginBottom20", string>; +// copy(find(m => Array.isArray(m) && m.includes("heading-sm/normal")).map(JSON.stringify).join("|")) +export type TextVariant = "heading-sm/normal" | "heading-sm/medium" | "heading-sm/semibold" | "heading-sm/bold" | "heading-sm/extrabold" | "heading-md/normal" | "heading-md/medium" | "heading-md/semibold" | "heading-md/bold" | "heading-md/extrabold" | "heading-lg/normal" | "heading-lg/medium" | "heading-lg/semibold" | "heading-lg/bold" | "heading-lg/extrabold" | "heading-xl/normal" | "heading-xl/medium" | "heading-xl/semibold" | "heading-xl/bold" | "heading-xl/extrabold" | "heading-xxl/normal" | "heading-xxl/medium" | "heading-xxl/semibold" | "heading-xxl/bold" | "heading-xxl/extrabold" | "text-xxs/normal" | "text-xxs/medium" | "text-xxs/semibold" | "text-xxs/bold" | "text-xs/normal" | "text-xs/medium" | "text-xs/semibold" | "text-xs/bold" | "text-sm/normal" | "text-sm/medium" | "text-sm/semibold" | "text-sm/bold" | "text-md/normal" | "text-md/medium" | "text-md/semibold" | "text-md/bold" | "text-lg/normal" | "text-lg/medium" | "text-lg/semibold" | "text-lg/bold"; + export type TextProps = PropsWithChildren & { variant?: TextVariant; tag?: "div" | "span" | "p" | "strong" | HeadingTag; - selectable?: boolean; - lineClamp?: number; }>; export type Text = ComponentType; -export type Heading = ComponentType; -export type FormTitle = ComponentType & PropsWithChildren<{ - /** default is h5 */ - tag?: HeadingTag; - faded?: boolean; - disabled?: boolean; - required?: boolean; - error?: ReactNode; -}>>; +export interface ButtonProps extends PropsWithChildren, "size">> { + /** Button.Looks.FILLED */ + look?: string; + /** Button.Colors.BRAND */ + color?: string; + /** Button.Sizes.MEDIUM */ + size?: string; -export type FormSection = ComponentType>; +} -export type FormDivider = ComponentType<{ - className?: string; - style?: CSSProperties; -}>; +export type Button = ComponentType & { + Colors: Record<"BRAND" | "RED" | "GREEN" | "PRIMARY" | "LINK" | "WHITE" | "TRANSPARENT" | "CUSTOM", string>; + Looks: Record<"FILLED" | "LINK", string>; + Sizes: Record<"NONE" | "SMALL" | "MEDIUM" | "LARGE" | "XLARGE" | "MIN", string>; +}; - -export type FormText = ComponentType & TextProps> & { Types: FormTextTypes; }; +// #endregion export type Tooltip = ComponentType<{ text: ReactNode | ComponentType; @@ -127,7 +112,6 @@ export type ComboboxPopout = ComponentType>; -export interface ButtonProps extends PropsWithChildren, "size">> { - /** Button.Looks.FILLED */ - look?: string; - /** Button.Colors.BRAND */ - color?: string; - /** Button.Sizes.MEDIUM */ - size?: string; - /** Button.BorderColors.BLACK */ - borderColor?: string; - - wrapperClassName?: string; - className?: string; - innerClassName?: string; - - buttonRef?: Ref; - focusProps?: any; - submitting?: boolean; - - submittingStartedLabel?: string; - submittingFinishedLabel?: string; -} - -export type Button = ComponentType & { - BorderColors: Record<"BLACK" | "BRAND" | "BRAND_NEW" | "GREEN" | "LINK" | "PRIMARY" | "RED" | "TRANSPARENT" | "WHITE" | "YELLOW", string>; - Colors: Record<"BRAND" | "RED" | "GREEN" | "YELLOW" | "PRIMARY" | "LINK" | "WHITE" | "BLACK" | "TRANSPARENT" | "BRAND_NEW" | "CUSTOM", string>; - Hovers: Record<"DEFAULT" | "BRAND" | "RED" | "GREEN" | "YELLOW" | "PRIMARY" | "LINK" | "WHITE" | "BLACK" | "TRANSPARENT", string>; - Looks: Record<"FILLED" | "INVERTED" | "OUTLINED" | "LINK" | "BLANK", string>; - Sizes: Record<"NONE" | "TINY" | "SMALL" | "MEDIUM" | "LARGE" | "XLARGE" | "MIN" | "MAX" | "ICON", string>; - - Link: any; -}; - -export type Switch = ComponentType>; - export type CheckboxAligns = { CENTER: "center"; TOP: "top"; @@ -379,14 +318,6 @@ export type Slider = ComponentClass>; -// TODO - type maybe idk probably not that useful other than the constants -export type Flex = ComponentType> & { - Align: Record<"START" | "END" | "CENTER" | "STRETCH" | "BASELINE", string>; - Direction: Record<"VERTICAL" | "HORIZONTAL" | "HORIZONTAL_REVERSE", string>; - Justify: Record<"START" | "END" | "CENTER" | "BETWEEN" | "AROUND", string>; - Wrap: Record<"NO_WRAP" | "WRAP" | "WRAP_REVERSE", string>; -}; - declare enum PopoutAnimation { NONE = "1", TRANSLATE = "2", diff --git a/packages/discord-types/src/index.d.ts b/packages/discord-types/src/index.d.ts index 78c980c9f..10a46a020 100644 --- a/packages/discord-types/src/index.d.ts +++ b/packages/discord-types/src/index.d.ts @@ -4,8 +4,8 @@ export * from "./components"; export * from "./flux"; export * from "./fluxEvents"; export * from "./menu"; +export * from "./modules"; export * from "./stores"; export * from "./passiveupdatestate"; export * from "./utils"; -export * from "./voicestate"; export * as Webpack from "../webpack"; diff --git a/packages/discord-types/src/menu.d.ts b/packages/discord-types/src/menu.d.ts index 20c3494c3..78810296a 100644 --- a/packages/discord-types/src/menu.d.ts +++ b/packages/discord-types/src/menu.d.ts @@ -1,4 +1,4 @@ -import type { ComponentType, CSSProperties, MouseEvent, PropsWithChildren, ReactNode, UIEvent } from "react"; +import type { ComponentType, CSSProperties, ForwardRefRenderFunction, MouseEvent, PropsWithChildren, ReactNode, UIEvent } from "react"; type RC = ComponentType>>; @@ -46,6 +46,8 @@ export interface Menu { MenuControlItem: RC<{ id: string; interactive?: boolean; + label?: string; + control: ForwardRefRenderFunction; }>; MenuSliderControl: RC<{ minValue?: number, diff --git a/packages/discord-types/src/modules/CloudUpload.d.ts b/packages/discord-types/src/modules/CloudUpload.d.ts new file mode 100644 index 000000000..b8a9d8ead --- /dev/null +++ b/packages/discord-types/src/modules/CloudUpload.d.ts @@ -0,0 +1,72 @@ +import EventEmitter from "events"; +import { CloudUploadPlatform } from "../../enums"; + +interface BaseUploadItem { + platform: CloudUploadPlatform; + id?: string; + origin?: string; + isThumbnail?: boolean; + clip?: unknown; +} + +export interface ReactNativeUploadItem extends BaseUploadItem { + platform: CloudUploadPlatform.REACT_NATIVE; + uri: string; + filename?: string; + mimeType?: string; + durationSecs?: number; + waveform?: string; +} + +export interface WebUploadItem extends BaseUploadItem { + platform: CloudUploadPlatform.WEB; + file: File; +} + +export type CloudUploadItem = ReactNativeUploadItem | WebUploadItem; + +export class CloudUpload extends EventEmitter { + constructor(item: CloudUploadItem, channelId: string, showLargeMessageDialog?: boolean, reactNativeFileIndex?: number); + + channelId: string; + classification: string; + clip: unknown; + contentHash: unknown; + currentSize: number; + description: string | null; + durationSecs: number | undefined; + etag: string | undefined; + error: unknown; + filename: string; + id: string; + isImage: boolean; + isThumbnail: boolean; + isVideo: boolean; + item: { + file: File; + platform: CloudUploadPlatform; + origin: string; + }; + loaded: number; + mimeType: string; + origin: string; + postCompressionSize: number | undefined; + preCompressionSize: number; + responseUrl: string; + sensitive: boolean; + showLargeMessageDialog: boolean; + spoiler: boolean; + startTime: number; + status: "NOT_STARTED" | "STARTED" | "UPLOADING" | "ERROR" | "COMPLETED" | "CANCELLED" | "REMOVED_FROM_MSG_DRAFT"; + uniqueId: string; + uploadedFilename: string; + waveform: string | undefined; + + // there are many more methods than just these but I didn't find them particularly useful + upload(): Promise; + cancel(): void; + delete(): Promise; + getSize(): number; + maybeConvertToWebP(): Promise; + removeFromMsgDraft(): void; +} diff --git a/packages/discord-types/src/modules/index.d.ts b/packages/discord-types/src/modules/index.d.ts new file mode 100644 index 000000000..fe0c1e248 --- /dev/null +++ b/packages/discord-types/src/modules/index.d.ts @@ -0,0 +1 @@ +export * from "./CloudUpload"; diff --git a/packages/discord-types/src/passiveupdatestate.d.ts b/packages/discord-types/src/passiveupdatestate.d.ts index 468dd9641..31bed9ffa 100644 --- a/packages/discord-types/src/passiveupdatestate.d.ts +++ b/packages/discord-types/src/passiveupdatestate.d.ts @@ -4,7 +4,7 @@ * SPDX-License-Identifier: GPL-3.0-or-later */ -import { VoiceState } from "./voicestate"; +import { VoiceState } from "./stores"; export interface PassiveUpdateState { type: string; diff --git a/packages/discord-types/src/stores/MediaEngineStore.d.ts b/packages/discord-types/src/stores/MediaEngineStore.d.ts index 2452a6056..4ef81639d 100644 --- a/packages/discord-types/src/stores/MediaEngineStore.d.ts +++ b/packages/discord-types/src/stores/MediaEngineStore.d.ts @@ -1,8 +1,37 @@ import { FluxStore } from ".."; export class MediaEngineStore extends FluxStore { - isSelfMute(): boolean; + isAdvancedVoiceActivitySupported(): boolean; + isAecDumpSupported(): boolean; + isAnyLocalVideoAutoDisabled(): boolean; + isAutomaticGainControlSupported(): boolean; + isDeaf(): boolean; + isEnableHardwareMuteNotice(): boolean; + isEnabled(): boolean; + isExperimentalEncodersSupported(): boolean; + isHardwareMute(): boolean; + isInputProfileCustom(): boolean; + isInteractionRequired(): boolean; + isLocalMute(): boolean; + isLocalMute(userId): boolean; + isLocalVideoAutoDisabled(userId): boolean; + isLocalVideoDisabled(userId): boolean; + isLocalVideoDisabled(): boolean; + isMediaFilterSettingLoading(): boolean; + isMute(): boolean; + isNativeAudioPermissionReady(): boolean; + isNoiseCancellationError(): boolean; + isNoiseCancellationSupported(): boolean; + isNoiseSuppressionSupported(): boolean; + isScreenSharing(): boolean; isSelfDeaf(): boolean; + isSelfMute(): boolean; + isSelfMutedTemporarily(): boolean; + isSimulcastSupported(): boolean; + isSoundSharing(): boolean; + isSupported(): boolean; + isVideoAvailable(): boolean; + isVideoEnabled(): boolean; setAv1Enabled(AV1: boolean): void; setH265Enabled(H265: boolean): void; diff --git a/packages/discord-types/src/stores/PresenceStore.d.ts b/packages/discord-types/src/stores/PresenceStore.d.ts new file mode 100644 index 000000000..3f245aca9 --- /dev/null +++ b/packages/discord-types/src/stores/PresenceStore.d.ts @@ -0,0 +1,26 @@ +import { Activity, OnlineStatus } from "../common"; +import { FluxStore } from "./FluxStore"; + +export interface UserAndActivity { + userId: string; + activity: Activity; +} + +export type DiscordPlatform = "desktop" | "mobile" | "web" | "embedded"; + +export class PresenceStore extends FluxStore { + findActivity(userId: string, predicate: (activity: Activity) => boolean, guildId?: string): Activity | undefined; + getActivities(userId: string, guildId?: string): Activity[]; + getActivityMetadata(userId: string): any; + getAllApplicationActivities(applicationId: string): UserAndActivity[]; + getApplicationActivity(userId: string, applicationId: string, guildId?: string): Activity | null; + getClientStatus(userId: string): Record; + getHiddenActivities(): any; + /** literally just getActivities(...)[0] */ + getPrimaryActivity(userId: string, guildId?: string): Activity | null; + getState(): any; + getStatus(userId: string, guildId?: string | null, defaultStatus?: OnlineStatus): OnlineStatus; + getUnfilteredActivities(userId: string, guildId?: string): Activity[]; + getUserIds(): string[]; + isMobileOnline(userId: string): boolean; +} diff --git a/packages/discord-types/src/stores/StreamerModeStore.d.ts b/packages/discord-types/src/stores/StreamerModeStore.d.ts index 5585b4aba..efa6cfc9e 100644 --- a/packages/discord-types/src/stores/StreamerModeStore.d.ts +++ b/packages/discord-types/src/stores/StreamerModeStore.d.ts @@ -1,6 +1,11 @@ -import { FluxStore } from ".."; +import { FluxStore } from "@vencord/discord-types"; export class StreamerModeStore extends FluxStore { - enabled: boolean; - autoToggle: boolean; + get autoToggle(): boolean; + get disableNotifications(): boolean; + get disableSounds(): boolean; + get enableContentProtection(): boolean; + get enabled(): boolean; + get hideInstantInvites(): boolean; + get hidePersonalInformation(): boolean; } diff --git a/packages/discord-types/src/stores/UploadAttachmentStore.d.ts b/packages/discord-types/src/stores/UploadAttachmentStore.d.ts new file mode 100644 index 000000000..99490aff7 --- /dev/null +++ b/packages/discord-types/src/stores/UploadAttachmentStore.d.ts @@ -0,0 +1,11 @@ +import { CloudUpload, DraftType, FluxStore } from ".."; + +export class UploadAttachmentStore extends FluxStore { + getFirstUpload(channelId: string, draftType: string): CloudUpload; + hasAdditionalUploads(channelId: string, draftType: string): boolean; + getUploads(channelId: string, draftType: DraftType): CloudUpload[]; + getUploadCount(channelId: string, draftType: DraftType): number; + getUpload(channelId: string, commandName: string, draftType: DraftType): CloudUpload; + findUpload(channelId: string, draftType: string, predicate: (upload: CloudUpload) => boolean): CloudUpload | undefined; + +} diff --git a/packages/discord-types/src/stores/VoiceStateStore.d.ts b/packages/discord-types/src/stores/VoiceStateStore.d.ts new file mode 100644 index 000000000..2feaf3925 --- /dev/null +++ b/packages/discord-types/src/stores/VoiceStateStore.d.ts @@ -0,0 +1,46 @@ +import { DiscordRecord } from "../common"; +import { FluxStore } from "./FluxStore"; + +export type UserVoiceStateRecords = Record; +export type VoiceStates = Record; + +export interface VoiceState extends DiscordRecord { + userId: string; + channelId: string | null | undefined; + sessionId: string | null | undefined; + mute: boolean; + deaf: boolean; + stream: boolean; + selfMute: boolean; + selfDeaf: boolean; + selfVideo: boolean; + selfStream: boolean | undefined; + suppress: boolean; + requestToSpeakTimestamp: string | null | undefined; + discoverable: boolean; + + isVoiceMuted(): boolean; + isVoiceDeafened(): boolean; + + oldChannelId?: string; + guildId?: string; +} + +export class VoiceStateStore extends FluxStore { + getAllVoiceStates(): VoiceStates; + + getVoiceStates(guildId?: string | null): UserVoiceStateRecords; + getVoiceStatesForChannel(channelId: string): UserVoiceStateRecords; + getVideoVoiceStatesForChannel(channelId: string): UserVoiceStateRecords; + + getVoiceState(guildId: string | null, userId: string): VoiceState | undefined; + getUserVoiceChannelId(guildId: string | null, userId: string): string | undefined; + getVoiceStateForChannel(channelId: string, userId?: string): VoiceState | undefined; + getVoiceStateForUser(userId: string): VoiceState | undefined; + + getCurrentClientVoiceChannelId(guildId: string | null): string | undefined; + isCurrentClientInVoiceChannel(): boolean; + + isInChannel(channelId: string, userId?: string): boolean; + hasVideo(channelId: string): boolean; +} diff --git a/packages/discord-types/src/stores/index.d.ts b/packages/discord-types/src/stores/index.d.ts index bf7d7152b..ecbb256ef 100644 --- a/packages/discord-types/src/stores/index.d.ts +++ b/packages/discord-types/src/stores/index.d.ts @@ -8,6 +8,7 @@ export * from "./GuildMemberStore"; export * from "./GuildRoleStore"; export * from "./GuildStore"; export * from "./MessageStore"; +export * from "./PresenceStore"; export * from "./RelationshipStore"; export * from "./MediaEngineStore"; export * from "./StreamerModeStore"; @@ -15,10 +16,12 @@ export * from "./SpellCheckStore"; export * from "./SelectedChannelStore"; export * from "./SelectedGuildStore"; export * from "./StickersStore"; +export * from "./StreamerModeStore"; export * from "./ThemeStore"; export * from "./TypingStore"; export * from "./UserProfileStore"; export * from "./UserStore"; +export * from "./VoiceStateStore"; export * from "./WindowStore"; /** diff --git a/packages/discord-types/src/utils.d.ts b/packages/discord-types/src/utils.d.ts index 1a8af6323..77ef1212a 100644 --- a/packages/discord-types/src/utils.d.ts +++ b/packages/discord-types/src/utils.d.ts @@ -6,15 +6,17 @@ import type { FluxEvents } from "./fluxEvents"; export { FluxEvents }; +type FluxEventsAutoComplete = LiteralUnion; + export interface FluxDispatcher { _actionHandlers: any; _interceptors: any; _subscriptions: any; addInterceptor(interceptor: any): void; - dispatch(event: { [key: string]: unknown; type: FluxEvents; }): Promise; + dispatch(event: { [key: string]: unknown; type: FluxEventsAutoComplete; }): Promise; isDispatching(): boolean; - subscribe(event: FluxEvents, callback: (data: any) => void): void; - unsubscribe(event: FluxEvents, callback: (data: any) => void): void; + subscribe(event: FluxEventsAutoComplete, callback: (data: any) => void): void; + unsubscribe(event: FluxEventsAutoComplete, callback: (data: any) => void): void; wait(callback: () => void): void; } diff --git a/packages/discord-types/src/voicestate.d.ts b/packages/discord-types/src/voicestate.d.ts deleted file mode 100644 index aa7ddcce2..000000000 --- a/packages/discord-types/src/voicestate.d.ts +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Vencord, a Discord client mod - * Copyright (c) 2024 Vendicated and contributors - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -export interface VoiceState { - userId: string; - channelId?: string; - oldChannelId?: string; - guildId?: string; - deaf: boolean; - mute: boolean; - selfDeaf: boolean; - selfMute: boolean; - selfStream: boolean; - selfVideo: boolean; - sessionId: string; - suppress: boolean; - requestToSpeakTimestamp: string | null; -} diff --git a/packages/vencord-types/README.md b/packages/vencord-types/README.md index 0513f8703..7a625edd5 100644 --- a/packages/vencord-types/README.md +++ b/packages/vencord-types/README.md @@ -1,11 +1,11 @@ -# Vencord Types +# Equicord Types -Typings for Vencord's api, published to npm +Typings for Equicord's api, published to npm ```sh -npm i @vencord/types +npm i @equicord/types -yarn add @vencord/types +yarn add @equicord/types -pnpm add @vencord/types +pnpm add @equicord/types ``` diff --git a/packages/vencord-types/package.json b/packages/vencord-types/package.json index 645869190..6373da60d 100644 --- a/packages/vencord-types/package.json +++ b/packages/vencord-types/package.json @@ -1,7 +1,7 @@ { - "name": "@vencord/types", + "name": "@equicord/types", "private": false, - "version": "1.11.5", + "version": "1.13.8", "description": "", "types": "index.d.ts", "scripts": { @@ -19,9 +19,14 @@ "dependencies": { "@types/lodash": "4.17.15", "@types/node": "^22.13.4", - "@types/react": "18.3.1", - "@types/react-dom": "18.3.1", - "standalone-electron-types": "^34.2.0", + "@vencord/discord-types": "^1.0.0", + "highlight.js": "11.11.1", + "moment": "^2.22.2", + "ts-pattern": "^5.6.0", "type-fest": "^4.35.0" + }, + "peerDependencies": { + "@types/react": "18.3.1", + "@types/react-dom": "18.3.1" } } diff --git a/patches/@types__less@3.0.6.patch b/patches/@types__less@3.0.8.patch similarity index 100% rename from patches/@types__less@3.0.6.patch rename to patches/@types__less@3.0.8.patch diff --git a/patches/eslint@9.20.1.patch b/patches/eslint@9.20.1.patch deleted file mode 100644 index c91e45d56..000000000 --- a/patches/eslint@9.20.1.patch +++ /dev/null @@ -1,49 +0,0 @@ -diff --git a/lib/rules/no-useless-escape.js b/lib/rules/no-useless-escape.js -index 0e0f6f09f2c35f3276173c08f832cde9f2cf56a0..7dc22851715f3574d935f513c1b5e35552985711 100644 ---- a/lib/rules/no-useless-escape.js -+++ b/lib/rules/no-useless-escape.js -@@ -65,13 +65,31 @@ module.exports = { - escapeBackslash: "Replace the `\\` with `\\\\` to include the actual backslash character." - }, - -- schema: [] -+ schema: [{ -+ type: "object", -+ properties: { -+ extra: { -+ type: "string", -+ default: "" -+ }, -+ extraCharClass: { -+ type: "string", -+ default: "" -+ }, -+ }, -+ additionalProperties: false -+ }] - }, - - create(context) { -+ const options = context.options[0] || {}; -+ const { extra, extraCharClass } = options; - const sourceCode = context.sourceCode; - const parser = new RegExpParser(); - -+ const NON_CHARCLASS_ESCAPES = union(REGEX_NON_CHARCLASS_ESCAPES, new Set(extra)); -+ const CHARCLASS_ESCAPES = union(REGEX_GENERAL_ESCAPES, new Set(extraCharClass)); -+ - /** - * Reports a node - * @param {ASTNode} node The node to report -@@ -200,9 +218,9 @@ module.exports = { - let allowedEscapes; - - if (characterClassStack.length) { -- allowedEscapes = unicodeSets ? REGEX_CLASSSET_CHARACTER_ESCAPES : REGEX_GENERAL_ESCAPES; -+ allowedEscapes = unicodeSets ? REGEX_CLASSSET_CHARACTER_ESCAPES : CHARCLASS_ESCAPES; - } else { -- allowedEscapes = REGEX_NON_CHARCLASS_ESCAPES; -+ allowedEscapes = NON_CHARCLASS_ESCAPES; - } - if (allowedEscapes.has(escapedChar)) { - return; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b46a73ac3..3e4b4ce56 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -4,13 +4,13 @@ settings: autoInstallPeers: true excludeLinksFromLockfile: false +overrides: + '@eslint/plugin-kit@<0.3.4': '>=0.3.4' + patchedDependencies: - '@types/less@3.0.6': + '@types/less@3.0.8': hash: 641e6c93bb737bac7fc283416857bd095cd85bcbcba63becb7a8bbcc78f73076 - path: patches/@types__less@3.0.6.patch - eslint@9.20.1: - hash: 4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215 - path: patches/eslint@9.20.1.patch + path: patches/@types__less@3.0.8.patch importers: @@ -26,8 +26,8 @@ importers: specifier: ^0.1.2 version: 0.1.2 '@types/less': - specifier: ^3.0.6 - version: 3.0.6(patch_hash=641e6c93bb737bac7fc283416857bd095cd85bcbcba63becb7a8bbcc78f73076) + specifier: ^3.0.8 + version: 3.0.8(patch_hash=641e6c93bb737bac7fc283416857bd095cd85bcbcba63becb7a8bbcc78f73076) '@types/stylus': specifier: ^0.48.42 version: 0.48.42 @@ -53,11 +53,11 @@ importers: specifier: 1.4.0 version: 1.4.0 monaco-editor: - specifier: ^0.52.2 - version: 0.52.2 + specifier: ^0.54.0 + version: 0.54.0 nanoid: - specifier: ^5.1.5 - version: 5.1.5 + specifier: ^5.1.6 + version: 5.1.6 usercss-meta: specifier: ^0.12.0 version: 0.12.0 @@ -66,23 +66,26 @@ importers: version: 1.0.2 devDependencies: '@electron/asar': - specifier: ^3.2.10 - version: 3.2.10 + specifier: ^4.0.1 + version: 4.0.1 '@stylistic/eslint-plugin': - specifier: ^4.2.0 - version: 4.2.0(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215))(typescript@5.8.2) + specifier: ^5.6.0 + version: 5.6.0(eslint@9.39.1) + '@types/adm-zip': + specifier: ^0.5.7 + version: 0.5.7 '@types/chrome': - specifier: ^0.0.312 - version: 0.0.312 + specifier: ^0.1.30 + version: 0.1.30 '@types/diff': specifier: ^7.0.2 version: 7.0.2 '@types/lodash': - specifier: ^4.17.14 - version: 4.17.15 + specifier: ^4.17.20 + version: 4.17.20 '@types/node': - specifier: ^22.13.13 - version: 22.13.13 + specifier: ^24.10.1 + version: 24.10.1 '@types/react': specifier: ^19.0.10 version: 19.0.12 @@ -90,35 +93,38 @@ importers: specifier: ^19.0.4 version: 19.0.4(@types/react@19.0.12) '@types/yazl': - specifier: ^2.4.5 - version: 2.4.6 + specifier: ^3.3.0 + version: 3.3.0 '@vencord/discord-types': specifier: link:packages/discord-types version: link:packages/discord-types + adm-zip: + specifier: ^0.5.16 + version: 0.5.16 diff: - specifier: ^7.0.0 - version: 7.0.0 + specifier: ^8.0.2 + version: 8.0.2 esbuild: - specifier: ^0.25.1 - version: 0.25.1 + specifier: ^0.27.0 + version: 0.27.0 eslint: - specifier: 9.20.1 - version: 9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215) + specifier: 9.39.1 + version: 9.39.1 eslint-import-resolver-alias: specifier: ^1.1.2 - version: 1.1.2(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.28.0(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215))(typescript@5.8.2))(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215))) + version: 1.1.2(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.47.0(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1)) eslint-plugin-react: - specifier: ^7.37.3 - version: 7.37.4(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215)) + specifier: ^7.37.5 + version: 7.37.5(eslint@9.39.1) eslint-plugin-simple-header: specifier: ^1.2.1 - version: 1.2.2(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215)) + version: 1.2.2(eslint@9.39.1) eslint-plugin-simple-import-sort: specifier: ^12.1.1 - version: 12.1.1(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215)) + version: 12.1.1(eslint@9.39.1) eslint-plugin-unused-imports: - specifier: ^4.1.4 - version: 4.1.4(@typescript-eslint/eslint-plugin@8.28.0(@typescript-eslint/parser@8.28.0(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215))(typescript@5.8.2))(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215))(typescript@5.8.2))(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215)) + specifier: ^4.3.0 + version: 4.3.0(@typescript-eslint/eslint-plugin@8.47.0(@typescript-eslint/parser@8.47.0(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1) highlight.js: specifier: 11.11.1 version: 11.11.1 @@ -129,17 +135,17 @@ importers: specifier: ^2.22.2 version: 2.30.1 puppeteer-core: - specifier: ^24.4.0 - version: 24.4.0 + specifier: ^24.30.0 + version: 24.30.0 standalone-electron-types: specifier: ^34.2.0 version: 34.2.0 stylelint: - specifier: ^16.17.0 - version: 16.17.0(typescript@5.8.2) + specifier: ^16.25.0 + version: 16.25.0(typescript@5.9.3) stylelint-config-standard: - specifier: ^37.0.0 - version: 37.0.0(stylelint@16.17.0(typescript@5.8.2)) + specifier: ^39.0.1 + version: 39.0.1(stylelint@16.25.0(typescript@5.9.3)) ts-patch: specifier: ^3.3.0 version: 3.3.0 @@ -147,20 +153,20 @@ importers: specifier: ^5.6.0 version: 5.6.2 tsx: - specifier: ^4.19.3 - version: 4.19.3 + specifier: ^4.20.6 + version: 4.20.6 type-fest: - specifier: ^4.38.0 - version: 4.38.0 + specifier: ^5.2.0 + version: 5.2.0 typescript: - specifier: ^5.8.2 - version: 5.8.2 + specifier: ^5.9.3 + version: 5.9.3 typescript-eslint: - specifier: ^8.28.0 - version: 8.28.0(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215))(typescript@5.8.2) + specifier: ^8.47.0 + version: 8.47.0(eslint@9.39.1)(typescript@5.9.3) typescript-transform-paths: specifier: ^3.5.5 - version: 3.5.5(typescript@5.8.2) + version: 3.5.5(typescript@5.9.3) zip-local: specifier: ^0.3.5 version: 0.3.5 @@ -191,9 +197,18 @@ importers: '@types/react-dom': specifier: 18.3.1 version: 18.3.1 - standalone-electron-types: - specifier: ^34.2.0 - version: 34.2.0 + '@vencord/discord-types': + specifier: ^1.0.0 + version: 1.0.0(@types/react@18.3.1) + highlight.js: + specifier: 11.11.1 + version: 11.11.1 + moment: + specifier: ^2.22.2 + version: 2.30.1 + ts-pattern: + specifier: ^5.6.0 + version: 5.6.2 type-fest: specifier: ^4.35.0 version: 4.38.0 @@ -222,22 +237,28 @@ packages: resolution: {integrity: sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==} engines: {node: '>=6.9.0'} - '@csstools/css-parser-algorithms@3.0.4': - resolution: {integrity: sha512-Up7rBoV77rv29d3uKHUIVubz1BTcgyUK72IvCQAbfbMv584xHcGKCKbWh7i8hPrRJ7qU4Y8IO3IY9m+iTB7P3A==} + '@cacheable/memory@2.0.5': + resolution: {integrity: sha512-fkiAxCvssEyJZ5fxX4tcdZFRmW9JehSTGvvqmXn6rTzG5cH6V/3C4ad8yb01vOjp2xBydHkHrgpW0qeGtzt6VQ==} + + '@cacheable/utils@2.3.1': + resolution: {integrity: sha512-38NJXjIr4W1Sghun8ju+uYWD8h2c61B4dKwfnQHVDFpAJ9oS28RpfqZQJ6Dgd3RceGkILDY9YT+72HJR3LoeSQ==} + + '@csstools/css-parser-algorithms@3.0.5': + resolution: {integrity: sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==} engines: {node: '>=18'} peerDependencies: - '@csstools/css-tokenizer': ^3.0.3 + '@csstools/css-tokenizer': ^3.0.4 - '@csstools/css-tokenizer@3.0.3': - resolution: {integrity: sha512-UJnjoFsmxfKUdNYdWgOB0mWUypuLvAfQPH1+pyvRJs6euowbFkFC6P13w1l8mJyi3vxYMxc9kld5jZEGRQs6bw==} + '@csstools/css-tokenizer@3.0.4': + resolution: {integrity: sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==} engines: {node: '>=18'} - '@csstools/media-query-list-parser@4.0.2': - resolution: {integrity: sha512-EUos465uvVvMJehckATTlNqGj4UJWkTmdWuDMjqvSUkjGpmOyFZBVwb4knxCm/k2GMTXY+c/5RkdndzFYWeX5A==} + '@csstools/media-query-list-parser@4.0.3': + resolution: {integrity: sha512-HAYH7d3TLRHDOUQK4mZKf9k9Ph/m8Akstg66ywKR4SFAigjs3yBiUeZtFxywiTm5moZMAp/5W/ZuFnNXXYLuuQ==} engines: {node: '>=18'} peerDependencies: - '@csstools/css-parser-algorithms': ^3.0.4 - '@csstools/css-tokenizer': ^3.0.3 + '@csstools/css-parser-algorithms': ^3.0.5 + '@csstools/css-tokenizer': ^3.0.4 '@csstools/selector-specificity@5.0.0': resolution: {integrity: sha512-PCqQV3c4CoVm3kdPhyeZ07VmBRdH2EpMFA/pd9OASpOEC3aXNGoqPDAZ80D0cLpMBxnmk0+yNhGsEx31hq7Gtw==} @@ -245,12 +266,12 @@ packages: peerDependencies: postcss-selector-parser: ^7.0.0 - '@dual-bundle/import-meta-resolve@4.1.0': - resolution: {integrity: sha512-+nxncfwHM5SgAtrVzgpzJOI1ol0PkumhVo469KCf9lUi21IGcY90G98VuHm9VRrUypmAzawAHO9bs6hqeADaVg==} + '@dual-bundle/import-meta-resolve@4.2.1': + resolution: {integrity: sha512-id+7YRUgoUX6CgV0DtuhirQWodeeA7Lf4i2x71JS/vtA5pRb/hIGWlw+G6MeXvsM+MXrz0VAydTGElX1rAfgPg==} - '@electron/asar@3.2.10': - resolution: {integrity: sha512-mvBSwIBUeiRscrCeJE1LwctAriBj65eUDm0Pc11iE5gRwzkmsdbS7FnZ1XUWjpSeQWL1L5g12Fc/SchPM9DUOw==} - engines: {node: '>=10.12.0'} + '@electron/asar@4.0.1': + resolution: {integrity: sha512-F4Ykm1jiBGY1WV/o8Q8oFW8Nq0u+S2/vPujzNJtdSJ6C4LHC4CiGLn7c17s7SolZ23gcvCebMncmZtNc+MkxPQ==} + engines: {node: '>=22.12.0'} hasBin: true '@esbuild/aix-ppc64@0.25.1': @@ -259,152 +280,308 @@ packages: cpu: [ppc64] os: [aix] + '@esbuild/aix-ppc64@0.27.0': + resolution: {integrity: sha512-KuZrd2hRjz01y5JK9mEBSD3Vj3mbCvemhT466rSuJYeE/hjuBrHfjjcjMdTm/sz7au+++sdbJZJmuBwQLuw68A==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + '@esbuild/android-arm64@0.25.1': resolution: {integrity: sha512-50tM0zCJW5kGqgG7fQ7IHvQOcAn9TKiVRuQ/lN0xR+T2lzEFvAi1ZcS8DiksFcEpf1t/GYOeOfCAgDHFpkiSmA==} engines: {node: '>=18'} cpu: [arm64] os: [android] + '@esbuild/android-arm64@0.27.0': + resolution: {integrity: sha512-CC3vt4+1xZrs97/PKDkl0yN7w8edvU2vZvAFGD16n9F0Cvniy5qvzRXjfO1l94efczkkQE6g1x0i73Qf5uthOQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + '@esbuild/android-arm@0.25.1': resolution: {integrity: sha512-dp+MshLYux6j/JjdqVLnMglQlFu+MuVeNrmT5nk6q07wNhCdSnB7QZj+7G8VMUGh1q+vj2Bq8kRsuyA00I/k+Q==} engines: {node: '>=18'} cpu: [arm] os: [android] + '@esbuild/android-arm@0.27.0': + resolution: {integrity: sha512-j67aezrPNYWJEOHUNLPj9maeJte7uSMM6gMoxfPC9hOg8N02JuQi/T7ewumf4tNvJadFkvLZMlAq73b9uwdMyQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + '@esbuild/android-x64@0.25.1': resolution: {integrity: sha512-GCj6WfUtNldqUzYkN/ITtlhwQqGWu9S45vUXs7EIYf+7rCiiqH9bCloatO9VhxsL0Pji+PF4Lz2XXCES+Q8hDw==} engines: {node: '>=18'} cpu: [x64] os: [android] + '@esbuild/android-x64@0.27.0': + resolution: {integrity: sha512-wurMkF1nmQajBO1+0CJmcN17U4BP6GqNSROP8t0X/Jiw2ltYGLHpEksp9MpoBqkrFR3kv2/te6Sha26k3+yZ9Q==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + '@esbuild/darwin-arm64@0.25.1': resolution: {integrity: sha512-5hEZKPf+nQjYoSr/elb62U19/l1mZDdqidGfmFutVUjjUZrOazAtwK+Kr+3y0C/oeJfLlxo9fXb1w7L+P7E4FQ==} engines: {node: '>=18'} cpu: [arm64] os: [darwin] + '@esbuild/darwin-arm64@0.27.0': + resolution: {integrity: sha512-uJOQKYCcHhg07DL7i8MzjvS2LaP7W7Pn/7uA0B5S1EnqAirJtbyw4yC5jQ5qcFjHK9l6o/MX9QisBg12kNkdHg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + '@esbuild/darwin-x64@0.25.1': resolution: {integrity: sha512-hxVnwL2Dqs3fM1IWq8Iezh0cX7ZGdVhbTfnOy5uURtao5OIVCEyj9xIzemDi7sRvKsuSdtCAhMKarxqtlyVyfA==} engines: {node: '>=18'} cpu: [x64] os: [darwin] + '@esbuild/darwin-x64@0.27.0': + resolution: {integrity: sha512-8mG6arH3yB/4ZXiEnXof5MK72dE6zM9cDvUcPtxhUZsDjESl9JipZYW60C3JGreKCEP+p8P/72r69m4AZGJd5g==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + '@esbuild/freebsd-arm64@0.25.1': resolution: {integrity: sha512-1MrCZs0fZa2g8E+FUo2ipw6jw5qqQiH+tERoS5fAfKnRx6NXH31tXBKI3VpmLijLH6yriMZsxJtaXUyFt/8Y4A==} engines: {node: '>=18'} cpu: [arm64] os: [freebsd] + '@esbuild/freebsd-arm64@0.27.0': + resolution: {integrity: sha512-9FHtyO988CwNMMOE3YIeci+UV+x5Zy8fI2qHNpsEtSF83YPBmE8UWmfYAQg6Ux7Gsmd4FejZqnEUZCMGaNQHQw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + '@esbuild/freebsd-x64@0.25.1': resolution: {integrity: sha512-0IZWLiTyz7nm0xuIs0q1Y3QWJC52R8aSXxe40VUxm6BB1RNmkODtW6LHvWRrGiICulcX7ZvyH6h5fqdLu4gkww==} engines: {node: '>=18'} cpu: [x64] os: [freebsd] + '@esbuild/freebsd-x64@0.27.0': + resolution: {integrity: sha512-zCMeMXI4HS/tXvJz8vWGexpZj2YVtRAihHLk1imZj4efx1BQzN76YFeKqlDr3bUWI26wHwLWPd3rwh6pe4EV7g==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + '@esbuild/linux-arm64@0.25.1': resolution: {integrity: sha512-jaN3dHi0/DDPelk0nLcXRm1q7DNJpjXy7yWaWvbfkPvI+7XNSc/lDOnCLN7gzsyzgu6qSAmgSvP9oXAhP973uQ==} engines: {node: '>=18'} cpu: [arm64] os: [linux] + '@esbuild/linux-arm64@0.27.0': + resolution: {integrity: sha512-AS18v0V+vZiLJyi/4LphvBE+OIX682Pu7ZYNsdUHyUKSoRwdnOsMf6FDekwoAFKej14WAkOef3zAORJgAtXnlQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + '@esbuild/linux-arm@0.25.1': resolution: {integrity: sha512-NdKOhS4u7JhDKw9G3cY6sWqFcnLITn6SqivVArbzIaf3cemShqfLGHYMx8Xlm/lBit3/5d7kXvriTUGa5YViuQ==} engines: {node: '>=18'} cpu: [arm] os: [linux] + '@esbuild/linux-arm@0.27.0': + resolution: {integrity: sha512-t76XLQDpxgmq2cNXKTVEB7O7YMb42atj2Re2Haf45HkaUpjM2J0UuJZDuaGbPbamzZ7bawyGFUkodL+zcE+jvQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + '@esbuild/linux-ia32@0.25.1': resolution: {integrity: sha512-OJykPaF4v8JidKNGz8c/q1lBO44sQNUQtq1KktJXdBLn1hPod5rE/Hko5ugKKZd+D2+o1a9MFGUEIUwO2YfgkQ==} engines: {node: '>=18'} cpu: [ia32] os: [linux] + '@esbuild/linux-ia32@0.27.0': + resolution: {integrity: sha512-Mz1jxqm/kfgKkc/KLHC5qIujMvnnarD9ra1cEcrs7qshTUSksPihGrWHVG5+osAIQ68577Zpww7SGapmzSt4Nw==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + '@esbuild/linux-loong64@0.25.1': resolution: {integrity: sha512-nGfornQj4dzcq5Vp835oM/o21UMlXzn79KobKlcs3Wz9smwiifknLy4xDCLUU0BWp7b/houtdrgUz7nOGnfIYg==} engines: {node: '>=18'} cpu: [loong64] os: [linux] + '@esbuild/linux-loong64@0.27.0': + resolution: {integrity: sha512-QbEREjdJeIreIAbdG2hLU1yXm1uu+LTdzoq1KCo4G4pFOLlvIspBm36QrQOar9LFduavoWX2msNFAAAY9j4BDg==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + '@esbuild/linux-mips64el@0.25.1': resolution: {integrity: sha512-1osBbPEFYwIE5IVB/0g2X6i1qInZa1aIoj1TdL4AaAb55xIIgbg8Doq6a5BzYWgr+tEcDzYH67XVnTmUzL+nXg==} engines: {node: '>=18'} cpu: [mips64el] os: [linux] + '@esbuild/linux-mips64el@0.27.0': + resolution: {integrity: sha512-sJz3zRNe4tO2wxvDpH/HYJilb6+2YJxo/ZNbVdtFiKDufzWq4JmKAiHy9iGoLjAV7r/W32VgaHGkk35cUXlNOg==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + '@esbuild/linux-ppc64@0.25.1': resolution: {integrity: sha512-/6VBJOwUf3TdTvJZ82qF3tbLuWsscd7/1w+D9LH0W/SqUgM5/JJD0lrJ1fVIfZsqB6RFmLCe0Xz3fmZc3WtyVg==} engines: {node: '>=18'} cpu: [ppc64] os: [linux] + '@esbuild/linux-ppc64@0.27.0': + resolution: {integrity: sha512-z9N10FBD0DCS2dmSABDBb5TLAyF1/ydVb+N4pi88T45efQ/w4ohr/F/QYCkxDPnkhkp6AIpIcQKQ8F0ANoA2JA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + '@esbuild/linux-riscv64@0.25.1': resolution: {integrity: sha512-nSut/Mx5gnilhcq2yIMLMe3Wl4FK5wx/o0QuuCLMtmJn+WeWYoEGDN1ipcN72g1WHsnIbxGXd4i/MF0gTcuAjQ==} engines: {node: '>=18'} cpu: [riscv64] os: [linux] + '@esbuild/linux-riscv64@0.27.0': + resolution: {integrity: sha512-pQdyAIZ0BWIC5GyvVFn5awDiO14TkT/19FTmFcPdDec94KJ1uZcmFs21Fo8auMXzD4Tt+diXu1LW1gHus9fhFQ==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + '@esbuild/linux-s390x@0.25.1': resolution: {integrity: sha512-cEECeLlJNfT8kZHqLarDBQso9a27o2Zd2AQ8USAEoGtejOrCYHNtKP8XQhMDJMtthdF4GBmjR2au3x1udADQQQ==} engines: {node: '>=18'} cpu: [s390x] os: [linux] + '@esbuild/linux-s390x@0.27.0': + resolution: {integrity: sha512-hPlRWR4eIDDEci953RI1BLZitgi5uqcsjKMxwYfmi4LcwyWo2IcRP+lThVnKjNtk90pLS8nKdroXYOqW+QQH+w==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + '@esbuild/linux-x64@0.25.1': resolution: {integrity: sha512-xbfUhu/gnvSEg+EGovRc+kjBAkrvtk38RlerAzQxvMzlB4fXpCFCeUAYzJvrnhFtdeyVCDANSjJvOvGYoeKzFA==} engines: {node: '>=18'} cpu: [x64] os: [linux] + '@esbuild/linux-x64@0.27.0': + resolution: {integrity: sha512-1hBWx4OUJE2cab++aVZ7pObD6s+DK4mPGpemtnAORBvb5l/g5xFGk0vc0PjSkrDs0XaXj9yyob3d14XqvnQ4gw==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + '@esbuild/netbsd-arm64@0.25.1': resolution: {integrity: sha512-O96poM2XGhLtpTh+s4+nP7YCCAfb4tJNRVZHfIE7dgmax+yMP2WgMd2OecBuaATHKTHsLWHQeuaxMRnCsH8+5g==} engines: {node: '>=18'} cpu: [arm64] os: [netbsd] + '@esbuild/netbsd-arm64@0.27.0': + resolution: {integrity: sha512-6m0sfQfxfQfy1qRuecMkJlf1cIzTOgyaeXaiVaaki8/v+WB+U4hc6ik15ZW6TAllRlg/WuQXxWj1jx6C+dfy3w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + '@esbuild/netbsd-x64@0.25.1': resolution: {integrity: sha512-X53z6uXip6KFXBQ+Krbx25XHV/NCbzryM6ehOAeAil7X7oa4XIq+394PWGnwaSQ2WRA0KI6PUO6hTO5zeF5ijA==} engines: {node: '>=18'} cpu: [x64] os: [netbsd] + '@esbuild/netbsd-x64@0.27.0': + resolution: {integrity: sha512-xbbOdfn06FtcJ9d0ShxxvSn2iUsGd/lgPIO2V3VZIPDbEaIj1/3nBBe1AwuEZKXVXkMmpr6LUAgMkLD/4D2PPA==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + '@esbuild/openbsd-arm64@0.25.1': resolution: {integrity: sha512-Na9T3szbXezdzM/Kfs3GcRQNjHzM6GzFBeU1/6IV/npKP5ORtp9zbQjvkDJ47s6BCgaAZnnnu/cY1x342+MvZg==} engines: {node: '>=18'} cpu: [arm64] os: [openbsd] + '@esbuild/openbsd-arm64@0.27.0': + resolution: {integrity: sha512-fWgqR8uNbCQ/GGv0yhzttj6sU/9Z5/Sv/VGU3F5OuXK6J6SlriONKrQ7tNlwBrJZXRYk5jUhuWvF7GYzGguBZQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + '@esbuild/openbsd-x64@0.25.1': resolution: {integrity: sha512-T3H78X2h1tszfRSf+txbt5aOp/e7TAz3ptVKu9Oyir3IAOFPGV6O9c2naym5TOriy1l0nNf6a4X5UXRZSGX/dw==} engines: {node: '>=18'} cpu: [x64] os: [openbsd] + '@esbuild/openbsd-x64@0.27.0': + resolution: {integrity: sha512-aCwlRdSNMNxkGGqQajMUza6uXzR/U0dIl1QmLjPtRbLOx3Gy3otfFu/VjATy4yQzo9yFDGTxYDo1FfAD9oRD2A==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.27.0': + resolution: {integrity: sha512-nyvsBccxNAsNYz2jVFYwEGuRRomqZ149A39SHWk4hV0jWxKM0hjBPm3AmdxcbHiFLbBSwG6SbpIcUbXjgyECfA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + '@esbuild/sunos-x64@0.25.1': resolution: {integrity: sha512-2H3RUvcmULO7dIE5EWJH8eubZAI4xw54H1ilJnRNZdeo8dTADEZ21w6J22XBkXqGJbe0+wnNJtw3UXRoLJnFEg==} engines: {node: '>=18'} cpu: [x64] os: [sunos] + '@esbuild/sunos-x64@0.27.0': + resolution: {integrity: sha512-Q1KY1iJafM+UX6CFEL+F4HRTgygmEW568YMqDA5UV97AuZSm21b7SXIrRJDwXWPzr8MGr75fUZPV67FdtMHlHA==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + '@esbuild/win32-arm64@0.25.1': resolution: {integrity: sha512-GE7XvrdOzrb+yVKB9KsRMq+7a2U/K5Cf/8grVFRAGJmfADr/e/ODQ134RK2/eeHqYV5eQRFxb1hY7Nr15fv1NQ==} engines: {node: '>=18'} cpu: [arm64] os: [win32] + '@esbuild/win32-arm64@0.27.0': + resolution: {integrity: sha512-W1eyGNi6d+8kOmZIwi/EDjrL9nxQIQ0MiGqe/AWc6+IaHloxHSGoeRgDRKHFISThLmsewZ5nHFvGFWdBYlgKPg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + '@esbuild/win32-ia32@0.25.1': resolution: {integrity: sha512-uOxSJCIcavSiT6UnBhBzE8wy3n0hOkJsBOzy7HDAuTDE++1DJMRRVCPGisULScHL+a/ZwdXPpXD3IyFKjA7K8A==} engines: {node: '>=18'} cpu: [ia32] os: [win32] + '@esbuild/win32-ia32@0.27.0': + resolution: {integrity: sha512-30z1aKL9h22kQhilnYkORFYt+3wp7yZsHWus+wSKAJR8JtdfI76LJ4SBdMsCopTR3z/ORqVu5L1vtnHZWVj4cQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + '@esbuild/win32-x64@0.25.1': resolution: {integrity: sha512-Y1EQdcfwMSeQN/ujR5VayLOJ1BHaK+ssyk0AEzPjC+t1lITgsnccPqFjb6V+LsTp/9Iov4ysfjxLaGJ9RPtkVg==} engines: {node: '>=18'} cpu: [x64] os: [win32] - '@eslint-community/eslint-utils@4.5.1': - resolution: {integrity: sha512-soEIOALTfTK6EjmKMMoLugwaP0rzkad90iIWd1hMO9ARkSAyjfMfkRRhLvD5qH7vvM0Cg72pieUfR6yh6XxC4w==} + '@esbuild/win32-x64@0.27.0': + resolution: {integrity: sha512-aIitBcjQeyOhMTImhLZmtxfdOcuNRpwlPNmlFKPcHQYPhEssw75Cl1TSXJXpMkzaua9FUetx/4OQKq7eJul5Cg==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@eslint-community/eslint-utils@4.9.0': + resolution: {integrity: sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 @@ -413,32 +590,32 @@ packages: resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==} engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} - '@eslint/config-array@0.19.2': - resolution: {integrity: sha512-GNKqxfHG2ySmJOBSHg7LxeUx4xpuCoFjacmlCoYWEbaPXLwvfIjixRI12xCQZeULksQb23uiA8F40w5TojpV7w==} + '@eslint/config-array@0.21.1': + resolution: {integrity: sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/core@0.11.0': - resolution: {integrity: sha512-DWUB2pksgNEb6Bz2fggIy1wh6fGgZP4Xyy/Mt0QZPiloKKXerbqq9D3SBQTlCRYOrcRPu4vuz+CGjwdfqxnoWA==} + '@eslint/config-helpers@0.4.2': + resolution: {integrity: sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/core@0.12.0': - resolution: {integrity: sha512-cmrR6pytBuSMTaBweKoGMwu3EiHiEC+DoyupPmlZ0HxBJBtIxwe+j/E4XPIKNx+Q74c8lXKPwYawBf5glsTkHg==} + '@eslint/core@0.17.0': + resolution: {integrity: sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@eslint/eslintrc@3.3.1': resolution: {integrity: sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/js@9.20.0': - resolution: {integrity: sha512-iZA07H9io9Wn836aVTytRaNqh00Sad+EamwOVJT12GTLw1VGMFV/4JaME+JjLtr9fiGaoWgYnS54wrfWsSs4oQ==} + '@eslint/js@9.39.1': + resolution: {integrity: sha512-S26Stp4zCy88tH94QbBv3XCuzRQiZ9yXofEILmglYTh/Ug/a9/umqvgFtYBAo3Lp0nsI/5/qH1CCrbdK3AP1Tw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/object-schema@2.1.6': - resolution: {integrity: sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==} + '@eslint/object-schema@2.1.7': + resolution: {integrity: sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/plugin-kit@0.2.7': - resolution: {integrity: sha512-JubJ5B2pJ4k4yGxaNLdbjrnk9d/iDz6/q8wOilpIowd6PJPgaxCuHBnBszq7Ce2TyMrywm5r4PnKm6V3iiZF+g==} + '@eslint/plugin-kit@0.4.1': + resolution: {integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@ffmpeg/ffmpeg@0.12.10': @@ -476,6 +653,18 @@ packages: '@intrnl/xxhash64@0.1.2': resolution: {integrity: sha512-1+lx7j99fdph+uy3EnjQyr39KQZ7LP56+aWOr6finJWpgYpvb7XrhFUqDwnEk/wpPC98nCjAT6RulpW3crWjlg==} + '@isaacs/balanced-match@4.0.1': + resolution: {integrity: sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==} + engines: {node: 20 || >=22} + + '@isaacs/brace-expansion@5.0.0': + resolution: {integrity: sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==} + engines: {node: 20 || >=22} + + '@isaacs/cliui@8.0.2': + resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} + engines: {node: '>=12'} + '@jridgewell/gen-mapping@0.3.5': resolution: {integrity: sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==} engines: {node: '>=6.0.0'} @@ -497,8 +686,14 @@ packages: '@jridgewell/trace-mapping@0.3.25': resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} - '@keyv/serialize@1.0.3': - resolution: {integrity: sha512-qnEovoOp5Np2JDGonIDL6Ayihw0RhnRh6vxPuHo4RDn1UOzwEo4AeIfpL6UGIrsceWrCMiVPgwRjbHu4vYFc3g==} + '@keyv/bigmap@1.3.0': + resolution: {integrity: sha512-KT01GjzV6AQD5+IYrcpoYLkCu1Jod3nau1Z7EsEuViO3TZGRacSbO9MfHmbJ1WaOXFtWLxPVj169cn2WNKPkIg==} + engines: {node: '>= 18'} + peerDependencies: + keyv: ^5.5.4 + + '@keyv/serialize@1.1.1': + resolution: {integrity: sha512-dXn3FZhPv0US+7dtJsIi2R+c7qWYiReoEh5zUntWCf4oSpMNib8FDhSoed6m3QyZdx5hK7iLFkYk3rNxwt8vTA==} '@nodelib/fs.scandir@2.1.5': resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} @@ -512,16 +707,16 @@ packages: resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} engines: {node: '>= 8'} - '@puppeteer/browsers@2.8.0': - resolution: {integrity: sha512-yTwt2KWRmCQAfhvbCRjebaSX8pV1//I0Y3g+A7f/eS7gf0l4eRJoUCvcYdVtboeU4CTOZQuqYbZNS8aBYb8ROQ==} + '@puppeteer/browsers@2.10.13': + resolution: {integrity: sha512-a9Ruw3j3qlnB5a/zHRTkruppynxqaeE4H9WNj5eYGRWqw0ZauZ23f4W2ARf3hghF5doozyD+CRtt7XSYuYRI/Q==} engines: {node: '>=18'} hasBin: true '@rtsao/scc@1.1.0': resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==} - '@stylistic/eslint-plugin@4.2.0': - resolution: {integrity: sha512-8hXezgz7jexGHdo5WN6JBEIPHCSFyyU4vgbxevu4YLVS5vl+sxqAAGyXSzfNDyR6xMNSH5H1x67nsXcYMOHtZA==} + '@stylistic/eslint-plugin@5.6.0': + resolution: {integrity: sha512-owEc4B8ME+O/xyZOkLVyLqPMsUgJXIM4XzCm5Vt3WvRXpyoOfYxgA+JkEiFqXPCI8+Nc2BzAT+KGAK7QleGs8Q==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: '>=9.0.0' @@ -529,8 +724,11 @@ packages: '@tootallnate/quickjs-emscripten@0.23.0': resolution: {integrity: sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==} - '@types/chrome@0.0.312': - resolution: {integrity: sha512-m204djOoU/YqF1dRw3YLk2L3tcDTYREUERDQDUAmROK3Rv94nLbMJ4uvjzr/Yj/A7SyEpTrpoCuXBKTuerEdqg==} + '@types/adm-zip@0.5.7': + resolution: {integrity: sha512-DNEs/QvmyRLurdQPChqq0Md4zGvPwHerAJYWk9l2jCbD1VPpnzRJorOdiq4zsw09NFbYnhfsoEhWtxIzXpn2yw==} + + '@types/chrome@0.1.30': + resolution: {integrity: sha512-rcP14i+ip4s3SLG6JzNOWIDd+0i8Bg+kBslHl8d4g6m/e2kEbWnR90AtlOPUZj9ONVRbKxnQUqsInxFzXyqLdA==} '@types/diff@7.0.2': resolution: {integrity: sha512-JSWRMozjFKsGlEjiiKajUjIJVKuKdE3oVy2DNtK+fUo8q82nhFZ2CPQwicAIkXrofahDXrWJ7mjelvZphMS98Q==} @@ -559,20 +757,23 @@ packages: '@types/jsonfile@6.1.4': resolution: {integrity: sha512-D5qGUYwjvnNNextdU59/+fI+spnwtTFmyQP0h+PfIOSkNfpU6AOICUOkm4i0OnSk+NyjdPJrxCDro0sJsWlRpQ==} - '@types/less@3.0.6': - resolution: {integrity: sha512-PecSzorDGdabF57OBeQO/xFbAkYWo88g4Xvnsx7LRwqLC17I7OoKtA3bQB9uXkY6UkMWCOsA8HSVpaoitscdXw==} + '@types/less@3.0.8': + resolution: {integrity: sha512-Gjm4+H9noDJgu5EdT3rUw5MhPBag46fiOy27BefvWkNL8mlZnKnCaVVVTLKj6RYXed9b62CPKnPav9govyQDzA==} '@types/lodash@4.17.15': resolution: {integrity: sha512-w/P33JFeySuhN6JLkysYUK2gEmy9kHHFN7E8ro0tkfmlDOgxBDzWEZ/J8cWA+fHqFevpswDTFZnDx+R9lbL6xw==} - '@types/node@22.10.5': - resolution: {integrity: sha512-F8Q+SeGimwOo86fiovQh8qiXfFEh2/ocYv7tU5pJ3EXMSSxk1Joj5wefpFK2fHTf/N6HKGSxIDBT9f3gCxXPkQ==} + '@types/lodash@4.17.20': + resolution: {integrity: sha512-H3MHACvFUEiujabxhaI/ImO6gUrd8oOurg7LQtS7mbwIXA/cUqWrvBsaeJ23aZEPk1TAYkurjfMbSELfoCXlGA==} '@types/node@22.13.13': resolution: {integrity: sha512-ClsL5nMwKaBRwPcCvH8E7+nU4GxHVx1axNvMZTFHMEfNI7oahimt26P5zjVCRrjiIWj6YFXfE1v3dEp94wLcGQ==} - '@types/prop-types@15.7.14': - resolution: {integrity: sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==} + '@types/node@24.10.1': + resolution: {integrity: sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==} + + '@types/prop-types@15.7.15': + resolution: {integrity: sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==} '@types/react-dom@18.3.1': resolution: {integrity: sha512-qW1Mfv8taImTthu4KoXgDfLuk4bydU6Q/TkADnDWWHwi4NX4BR+LWfTp2sVmTqRrsHvyDDTelgelxJ+SsejKKQ==} @@ -597,54 +798,66 @@ packages: '@types/yauzl@2.10.3': resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==} - '@types/yazl@2.4.6': - resolution: {integrity: sha512-/ifFjQtcKaoZOjl5NNCQRR0fAKafB3Foxd7J/WvFPTMea46zekapcR30uzkwIkKAAuq5T6d0dkwz754RFH27hg==} + '@types/yazl@3.3.0': + resolution: {integrity: sha512-mFL6lGkk2N5u5nIxpNV/K5LW3qVSbxhJrMxYGOOxZndWxMgCamr/iCsq/1t9kd8pEwhuNP91LC5qZm/qS9pOEw==} - '@typescript-eslint/eslint-plugin@8.28.0': - resolution: {integrity: sha512-lvFK3TCGAHsItNdWZ/1FkvpzCxTHUVuFrdnOGLMa0GGCFIbCgQWVk3CzCGdA7kM3qGVc+dfW9tr0Z/sHnGDFyg==} + '@typescript-eslint/eslint-plugin@8.47.0': + resolution: {integrity: sha512-fe0rz9WJQ5t2iaLfdbDc9T80GJy0AeO453q8C3YCilnGozvOyCG5t+EZtg7j7D88+c3FipfP/x+wzGnh1xp8ZA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - '@typescript-eslint/parser': ^8.0.0 || ^8.0.0-alpha.0 + '@typescript-eslint/parser': ^8.47.0 eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <5.9.0' + typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/parser@8.28.0': - resolution: {integrity: sha512-LPcw1yHD3ToaDEoljFEfQ9j2xShY367h7FZ1sq5NJT9I3yj4LHer1Xd1yRSOdYy9BpsrxU7R+eoDokChYM53lQ==} + '@typescript-eslint/parser@8.47.0': + resolution: {integrity: sha512-lJi3PfxVmo0AkEY93ecfN+r8SofEqZNGByvHAI3GBLrvt1Cw6H5k1IM02nSzu0RfUafr2EvFSw0wAsZgubNplQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <5.9.0' + typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/scope-manager@8.28.0': - resolution: {integrity: sha512-u2oITX3BJwzWCapoZ/pXw6BCOl8rJP4Ij/3wPoGvY8XwvXflOzd1kLrDUUUAIEdJSFh+ASwdTHqtan9xSg8buw==} + '@typescript-eslint/project-service@8.47.0': + resolution: {integrity: sha512-2X4BX8hUeB5JcA1TQJ7GjcgulXQ+5UkNb0DL8gHsHUHdFoiCTJoYLTpib3LtSDPZsRET5ygN4qqIWrHyYIKERA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/scope-manager@8.47.0': + resolution: {integrity: sha512-a0TTJk4HXMkfpFkL9/WaGTNuv7JWfFTQFJd6zS9dVAjKsojmv9HT55xzbEpnZoY+VUb+YXLMp+ihMLz/UlZfDg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/type-utils@8.28.0': - resolution: {integrity: sha512-oRoXu2v0Rsy/VoOGhtWrOKDiIehvI+YNrDk5Oqj40Mwm0Yt01FC/Q7nFqg088d3yAsR1ZcZFVfPCTTFCe/KPwg==} + '@typescript-eslint/tsconfig-utils@8.47.0': + resolution: {integrity: sha512-ybUAvjy4ZCL11uryalkKxuT3w3sXJAuWhOoGS3T/Wu+iUu1tGJmk5ytSY8gbdACNARmcYEB0COksD2j6hfGK2g==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/type-utils@8.47.0': + resolution: {integrity: sha512-QC9RiCmZ2HmIdCEvhd1aJELBlD93ErziOXXlHEZyuBo3tBiAZieya0HLIxp+DoDWlsQqDawyKuNEhORyku+P8A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <5.9.0' + typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/types@8.28.0': - resolution: {integrity: sha512-bn4WS1bkKEjx7HqiwG2JNB3YJdC1q6Ue7GyGlwPHyt0TnVq6TtD/hiOdTZt71sq0s7UzqBFXD8t8o2e63tXgwA==} + '@typescript-eslint/types@8.47.0': + resolution: {integrity: sha512-nHAE6bMKsizhA2uuYZbEbmp5z2UpffNrPEqiKIeN7VsV6UY/roxanWfoRrf6x/k9+Obf+GQdkm0nPU+vnMXo9A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/typescript-estree@8.28.0': - resolution: {integrity: sha512-H74nHEeBGeklctAVUvmDkxB1mk+PAZ9FiOMPFncdqeRBXxk1lWSYraHw8V12b7aa6Sg9HOBNbGdSHobBPuQSuA==} + '@typescript-eslint/typescript-estree@8.47.0': + resolution: {integrity: sha512-k6ti9UepJf5NpzCjH31hQNLHQWupTRPhZ+KFF8WtTuTpy7uHPfeg2NM7cP27aCGajoEplxJDFVCEm9TGPYyiVg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - typescript: '>=4.8.4 <5.9.0' + typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/utils@8.28.0': - resolution: {integrity: sha512-OELa9hbTYciYITqgurT1u/SzpQVtDLmQMFzy/N8pQE+tefOyCWT79jHsav294aTqV1q1u+VzqDGbuujvRYaeSQ==} + '@typescript-eslint/utils@8.47.0': + resolution: {integrity: sha512-g7XrNf25iL4TJOiPqatNuaChyqt49a/onq5YsJ9+hXeugK+41LVg7AxikMfM02PC6jbNtZLCJj6AUcQXJS/jGQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <5.9.0' + typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/visitor-keys@8.28.0': - resolution: {integrity: sha512-hbn8SZ8w4u2pRwgQ1GlUrPKE+t2XvcCW5tTRF7j6SMYIuYG37XuzIW44JCZPa36evi0Oy2SnM664BlIaAuQcvg==} + '@typescript-eslint/visitor-keys@8.47.0': + resolution: {integrity: sha512-SIV3/6eftCy1bNzCQoPmbWsRLujS8t5iDIZ4spZOBHqrM+yfX2ogg8Tt3PDTAVKw3sSCiUgg30uOAvK2r9zGjQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@vap/core@0.0.12': @@ -653,6 +866,11 @@ packages: '@vap/shiki@0.10.5': resolution: {integrity: sha512-5BHVGvQT8qonbLSASon5aQFQ18OZU4FxSl9tLSj6oJ0sap3KdMbYcfGq25M9zFZR1g1dJN7fgjmZXBIS5omIdw==} + '@vencord/discord-types@1.0.0': + resolution: {integrity: sha512-idtijKdbjiqo8k+4mo5Aq9zrQYwr2u11VmiRasaf+8+cGC5bYsMTIZl7zKLF3VdYx2a0ICjxgZtcfa1U3mv1Yg==} + peerDependencies: + '@types/react': ^19.0.10 + acorn-jsx@5.3.2: resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: @@ -663,6 +881,15 @@ packages: engines: {node: '>=0.4.0'} hasBin: true + acorn@8.15.0: + resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==} + engines: {node: '>=0.4.0'} + hasBin: true + + adm-zip@0.5.16: + resolution: {integrity: sha512-TGw5yVi4saajsSEgz25grObGHEUaDrniwvA2qwSC060KfqGPdglhvPMA2lPIoxs3PQIItj2iag35fONcQqgUaQ==} + engines: {node: '>=12.0'} + agent-base@7.1.3: resolution: {integrity: sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==} engines: {node: '>= 14'} @@ -677,6 +904,10 @@ packages: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} + ansi-regex@6.2.2: + resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==} + engines: {node: '>=12'} + ansi-styles@3.2.1: resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} engines: {node: '>=4'} @@ -685,6 +916,10 @@ packages: resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} engines: {node: '>=8'} + ansi-styles@6.2.3: + resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} + engines: {node: '>=12'} + argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} @@ -782,18 +1017,15 @@ packages: bare-events: optional: true - base64-js@1.5.1: - resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} - basic-ftp@5.0.5: resolution: {integrity: sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg==} engines: {node: '>=10.0.0'} - brace-expansion@1.1.11: - resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} + brace-expansion@1.1.12: + resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} - brace-expansion@2.0.1: - resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} + brace-expansion@2.0.2: + resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} braces@3.0.3: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} @@ -805,20 +1037,13 @@ packages: buffer-from@1.1.2: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} - buffer@6.0.3: - resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} - - cacheable@1.8.9: - resolution: {integrity: sha512-FicwAUyWnrtnd4QqYAoRlNs44/a1jTL7XDKqm5gJ90wz1DQPlC7U2Rd1Tydpv+E7WAr4sQHuw8Q8M3nZMAyecQ==} + cacheable@2.2.0: + resolution: {integrity: sha512-LEJxRqfeomiiRd2t0uON6hxAtgOoWDfY3fugebbz+J3vDLO+SkdfFChQcOHTZhj9SYa9iwE9MGYNX72dKiOE4w==} call-bind-apply-helpers@1.0.2: resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} engines: {node: '>= 0.4'} - call-bind@1.0.7: - resolution: {integrity: sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==} - engines: {node: '>= 0.4'} - call-bind@1.0.8: resolution: {integrity: sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==} engines: {node: '>= 0.4'} @@ -842,8 +1067,8 @@ packages: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} - chromium-bidi@2.1.2: - resolution: {integrity: sha512-vtRWBK2uImo5/W2oG6/cDkkHSm+2t6VHgnj+Rcwhb0pP74OoUb4GipyRX/T/y39gYQPhioP0DPShn+A7P6CHNw==} + chromium-bidi@11.0.0: + resolution: {integrity: sha512-cM3DI+OOb89T3wO8cpPSro80Q9eKYJ7hGVXoGS3GkDPxnYSqiv+6xwpIf6XERyJ9Tdsl09hmNmY94BkgZdVekw==} peerDependencies: devtools-protocol: '*' @@ -875,13 +1100,13 @@ packages: resolution: {integrity: sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==} engines: {node: '>=14'} + commander@13.1.0: + resolution: {integrity: sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==} + engines: {node: '>=18'} + commander@2.20.3: resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} - commander@5.1.0: - resolution: {integrity: sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==} - engines: {node: '>= 6'} - concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} @@ -938,8 +1163,8 @@ packages: supports-color: optional: true - debug@4.3.6: - resolution: {integrity: sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==} + debug@4.4.1: + resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==} engines: {node: '>=6.0'} peerDependencies: supports-color: '*' @@ -947,8 +1172,8 @@ packages: supports-color: optional: true - debug@4.4.0: - resolution: {integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==} + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} engines: {node: '>=6.0'} peerDependencies: supports-color: '*' @@ -971,11 +1196,11 @@ packages: resolution: {integrity: sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==} engines: {node: '>= 14'} - devtools-protocol@0.0.1413902: - resolution: {integrity: sha512-yRtvFD8Oyk7C9Os3GmnFZLu53yAfsnyw1s+mLmHHUK0GQEc9zthHWvS1r67Zqzm5t7v56PILHIVZ7kmFMaL2yQ==} + devtools-protocol@0.0.1521046: + resolution: {integrity: sha512-vhE6eymDQSKWUXwwA37NtTTVEzjtGVfDr3pRbsWEQ5onH/Snp2c+2xZHWJJawG/0hCCJLRGt4xVtEVUVILol4w==} - diff@7.0.0: - resolution: {integrity: sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw==} + diff@8.0.2: + resolution: {integrity: sha512-sSuxWU5j5SR9QQji/o2qMvqRNYRDOcBTgsJ/DeCf4iSN4gW+gNMXM7wFIP+fdXZxoNiAnHUTGjCr+TSWXdRDKg==} engines: {node: '>=0.3.1'} dir-glob@3.0.1: @@ -986,6 +1211,9 @@ packages: resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} engines: {node: '>=0.10.0'} + dompurify@3.1.7: + resolution: {integrity: sha512-VaTstWtsneJY8xzy7DekmYWEOZcmzIe3Qb3zPd4STve1OBTa+e+WmS1ITQec1fZYXI3HCsOZZiSMpG6oxoWMWQ==} + dot-case@3.0.4: resolution: {integrity: sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==} @@ -993,9 +1221,15 @@ packages: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} + eastasianwidth@0.2.0: + resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + emoji-regex@9.2.2: + resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + end-of-stream@1.4.4: resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==} @@ -1014,10 +1248,6 @@ packages: resolution: {integrity: sha512-py07lI0wjxAC/DcfK1S6G7iANonniZwTISvdPzk9hzeH0IZIshbuuFxLIU96OyF89Yb9hiqWn8M/bY83KY5vzA==} engines: {node: '>= 0.4'} - es-define-property@1.0.0: - resolution: {integrity: sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==} - engines: {node: '>= 0.4'} - es-define-property@1.0.1: resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} engines: {node: '>= 0.4'} @@ -1034,10 +1264,6 @@ packages: resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} engines: {node: '>= 0.4'} - es-set-tostringtag@2.0.3: - resolution: {integrity: sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==} - engines: {node: '>= 0.4'} - es-set-tostringtag@2.1.0: resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} engines: {node: '>= 0.4'} @@ -1055,6 +1281,11 @@ packages: engines: {node: '>=18'} hasBin: true + esbuild@0.27.0: + resolution: {integrity: sha512-jd0f4NHbD6cALCyGElNpGAOtWxSq46l9X/sWB0Nzd5er4Kz2YTm+Vl0qKFT9KUJvD8+fiO8AvoHhFvEatfVixA==} + engines: {node: '>=18'} + hasBin: true + escalade@3.1.2: resolution: {integrity: sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==} engines: {node: '>=6'} @@ -1112,8 +1343,8 @@ packages: '@typescript-eslint/parser': optional: true - eslint-plugin-react@7.37.4: - resolution: {integrity: sha512-BGP0jRmfYyvOyvMoRX/uoUeW+GqNj9y16bPQzqAHf3AYII/tDs+jMN0dBVkl88/OZwNGwrVFxE7riHsXVfy/LQ==} + eslint-plugin-react@7.37.5: + resolution: {integrity: sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==} engines: {node: '>=4'} peerDependencies: eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7 @@ -1128,8 +1359,8 @@ packages: peerDependencies: eslint: '>=5.0.0' - eslint-plugin-unused-imports@4.1.4: - resolution: {integrity: sha512-YptD6IzQjDardkl0POxnnRBhU1OEePMV0nd6siHaRBbd+lyh6NAhFEobiznKU7kTsSsDeSD62Pe7kAM1b7dAZQ==} + eslint-plugin-unused-imports@4.3.0: + resolution: {integrity: sha512-ZFBmXMGBYfHttdRtOG9nFFpmUvMtbHSjsKrS20vdWdbfiVYsO3yA2SGYy9i9XmZJDfMGBflZGBCm70SEnFQtOA==} peerDependencies: '@typescript-eslint/eslint-plugin': ^8.0.0-0 || ^7.0.0 || ^6.0.0 || ^5.0.0 eslint: ^9.0.0 || ^8.0.0 @@ -1137,20 +1368,20 @@ packages: '@typescript-eslint/eslint-plugin': optional: true - eslint-scope@8.3.0: - resolution: {integrity: sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==} + eslint-scope@8.4.0: + resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} eslint-visitor-keys@3.4.3: resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - eslint-visitor-keys@4.2.0: - resolution: {integrity: sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==} + eslint-visitor-keys@4.2.1: + resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - eslint@9.20.1: - resolution: {integrity: sha512-m1mM33o6dBUjxl2qb6wv6nGNwCAsns1eKtaQ4l/NPHeTvhiUPbtdfMyktxN4B3fgHIgsYh1VT3V9txblpQHq+g==} + eslint@9.39.1: + resolution: {integrity: sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} hasBin: true peerDependencies: @@ -1159,8 +1390,8 @@ packages: jiti: optional: true - espree@10.3.0: - resolution: {integrity: sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==} + espree@10.4.0: + resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} esprima@4.0.1: @@ -1224,8 +1455,8 @@ packages: fflate@0.8.2: resolution: {integrity: sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==} - file-entry-cache@10.0.7: - resolution: {integrity: sha512-txsf5fu3anp2ff3+gOJJzRImtrtm/oa9tYLN0iTuINZ++EyVR/nRrg2fKYwvG/pXDofcrvvb0scEbX3NyW/COw==} + file-entry-cache@10.1.4: + resolution: {integrity: sha512-5XRUFc0WTtUbjfGzEwXc42tiGxQHBmtbUG1h9L2apu4SulCGN3Hqm//9D6FAolf8MYNL7f/YlJl9vy08pj5JuA==} file-entry-cache@8.0.0: resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} @@ -1243,8 +1474,8 @@ packages: resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} engines: {node: '>=16'} - flat-cache@6.1.7: - resolution: {integrity: sha512-qwZ4xf1v1m7Rc9XiORly31YaChvKt6oNVHuqqZcoED/7O+ToyNVGobKsIAopY9ODcWpEDKEBAbrSOCBHtNQvew==} + flat-cache@6.1.19: + resolution: {integrity: sha512-l/K33newPTZMTGAnnzaiqSl6NnH7Namh8jBNjrgjprWxGmZUuxx/sJNIRaijOh3n7q7ESbhNZC+pvVZMFdeU4A==} flatted@3.3.3: resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} @@ -1253,13 +1484,14 @@ packages: resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} engines: {node: '>= 0.4'} + foreground-child@3.3.1: + resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} + engines: {node: '>=14'} + fs-extra@11.3.0: resolution: {integrity: sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew==} engines: {node: '>=14.14'} - fs.realpath@1.0.0: - resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} - fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} @@ -1279,14 +1511,6 @@ packages: resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} engines: {node: 6.* || 8.* || >= 10.*} - get-intrinsic@1.2.4: - resolution: {integrity: sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==} - engines: {node: '>= 0.4'} - - get-intrinsic@1.2.7: - resolution: {integrity: sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA==} - engines: {node: '>= 0.4'} - get-intrinsic@1.3.0: resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} engines: {node: '>= 0.4'} @@ -1322,9 +1546,10 @@ packages: resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} engines: {node: '>=10.13.0'} - glob@7.2.3: - resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} - deprecated: Glob versions prior to v9 are no longer supported + glob@11.0.3: + resolution: {integrity: sha512-2Nim7dha1KVkaiF4q6Dj+ngPPMdfvLJEOpZk/jKiUAkqKebpGAWQXAq9z1xu9HKu5lWfqw/FASuccEjyznjPaA==} + engines: {node: 20 || >=22} + hasBin: true global-modules@2.0.0: resolution: {integrity: sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==} @@ -1353,9 +1578,6 @@ packages: globjoin@0.1.4: resolution: {integrity: sha512-xYfnw62CKG8nLkZBfWbhWwDw02CHty86jfPcc2cr3ZfeuK9ysoVPPEUxf21bAD/rWAgk52SuBrLJlefNy8mvFg==} - gopd@1.0.1: - resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==} - gopd@1.2.0: resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} engines: {node: '>= 0.4'} @@ -1380,18 +1602,10 @@ packages: has-property-descriptors@1.0.2: resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} - has-proto@1.0.3: - resolution: {integrity: sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==} - engines: {node: '>= 0.4'} - has-proto@1.2.0: resolution: {integrity: sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==} engines: {node: '>= 0.4'} - has-symbols@1.0.3: - resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==} - engines: {node: '>= 0.4'} - has-symbols@1.1.0: resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} engines: {node: '>= 0.4'} @@ -1400,6 +1614,10 @@ packages: resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} engines: {node: '>= 0.4'} + hashery@1.2.0: + resolution: {integrity: sha512-43XJKpwle72Ik5Zpam7MuzRWyNdwwdf6XHlh8wCj2PggvWf+v/Dm5B0dxGZOmddidgeO6Ofu9As/o231Ti/9PA==} + engines: {node: '>=20'} + hasown@2.0.2: resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} engines: {node: '>= 0.4'} @@ -1408,8 +1626,8 @@ packages: resolution: {integrity: sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==} engines: {node: '>=12.0.0'} - hookified@1.8.1: - resolution: {integrity: sha512-GrO2l93P8xCWBSTBX9l2BxI78VU/MAAYag+pG8curS3aBGy0++ZlxrQ7PdUOUVMbn5BwkGb6+eRrnf43ipnFEA==} + hookified@1.13.0: + resolution: {integrity: sha512-6sPYUY8olshgM/1LDNW4QZQN0IqgKhtl/1C8koNZBJrKLBk3AZl6chQtNwpNztvfiApHMEwMHek5rv993PRbWw==} html-minifier-terser@7.2.0: resolution: {integrity: sha512-tXgn3QfqPIpGl9o+K5tpcj3/MN4SfLtsx2GWwBC3SSd0tXQGyF3gsSqad8loJgKZGM3ZxbYDd5yhiBIdWpmvLA==} @@ -1431,9 +1649,6 @@ packages: idb@8.0.3: resolution: {integrity: sha512-LtwtVyVYO5BqRvcsKuB2iUMnHwPVByPCXFXOpuU96IZPPoPN6xjOGxZQ74pgSVVLQWtUOYgyeL4GE98BY5D3wg==} - ieee754@1.2.1: - resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} - ignore@5.3.1: resolution: {integrity: sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==} engines: {node: '>= 4'} @@ -1442,6 +1657,10 @@ packages: resolution: {integrity: sha512-bAH5jbK/F3T3Jls4I0SO1hmPR0dKU0a7+SY6n1yzRtG54FLO8d6w/nxLFX2Nb7dBu6cCWXPaAME6cYqFUMmuCA==} engines: {node: '>= 4'} + ignore@7.0.5: + resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} + engines: {node: '>= 4'} + import-fresh@3.3.1: resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} engines: {node: '>=6'} @@ -1450,13 +1669,6 @@ packages: resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} engines: {node: '>=0.8.19'} - inflight@1.0.6: - resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} - deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. - - inherits@2.0.4: - resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} - ini@1.3.8: resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} @@ -1495,10 +1707,6 @@ packages: resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} engines: {node: '>= 0.4'} - is-core-module@2.15.0: - resolution: {integrity: sha512-Dd+Lb2/zvk9SKy1TGCt1wFJFo/MWBPMX5x7KcvLajWTGuomczdQX61PvY5yK6SVACwpoexWo81IfFyoKY2QnTA==} - engines: {node: '>= 0.4'} - is-core-module@2.16.1: resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} engines: {node: '>= 0.4'} @@ -1507,10 +1715,6 @@ packages: resolution: {integrity: sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==} engines: {node: '>= 0.4'} - is-date-object@1.0.5: - resolution: {integrity: sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==} - engines: {node: '>= 0.4'} - is-date-object@1.1.0: resolution: {integrity: sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==} engines: {node: '>= 0.4'} @@ -1567,10 +1771,6 @@ packages: resolution: {integrity: sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==} engines: {node: '>= 0.4'} - is-symbol@1.0.4: - resolution: {integrity: sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==} - engines: {node: '>= 0.4'} - is-symbol@1.1.1: resolution: {integrity: sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==} engines: {node: '>= 0.4'} @@ -1605,6 +1805,10 @@ packages: resolution: {integrity: sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==} engines: {node: '>= 0.4'} + jackspeak@4.1.1: + resolution: {integrity: sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ==} + engines: {node: 20 || >=22} + js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} @@ -1653,15 +1857,15 @@ packages: keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} - keyv@5.3.2: - resolution: {integrity: sha512-Lji2XRxqqa5Wg+CHLVfFKBImfJZ4pCSccu9eVWK6w4c2SDFLd8JAn1zqTuSFnsxb7ope6rMsnIHfp+eBbRBRZQ==} + keyv@5.5.4: + resolution: {integrity: sha512-eohl3hKTiVyD1ilYdw9T0OiB4hnjef89e3dMYKz+mVKDzj+5IteTseASUsOB+EU9Tf6VNTCjDePcP6wkDGmLKQ==} kind-of@6.0.3: resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==} engines: {node: '>=0.10.0'} - known-css-properties@0.35.0: - resolution: {integrity: sha512-a/RAk2BfKk+WFGhhOCAYqSiFLc34k8Mt/6NWRI4joER0EYUzXIcFivjjnoD3+XU1DggLn/tZc3DOAgke7l8a4A==} + known-css-properties@0.37.0: + resolution: {integrity: sha512-JCDrsP4Z1Sb9JwG0aJ8Eo2r7k4Ou5MwmThS/6lcIe1ICyb7UBJKGRIUUdqc2ASdE/42lgz6zFUnzAIhtXnBVrQ==} levn@0.4.1: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} @@ -1687,10 +1891,19 @@ packages: lower-case@2.0.2: resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==} + lru-cache@11.2.1: + resolution: {integrity: sha512-r8LA6i4LP4EeWOhqBaZZjDWwehd1xUJPCJd9Sv300H0ZmcUER4+JPh7bqqZeqs1o5pgtgvXm+d9UGrB5zZGDiQ==} + engines: {node: 20 || >=22} + lru-cache@7.18.3: resolution: {integrity: sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==} engines: {node: '>=12'} + marked@14.0.0: + resolution: {integrity: sha512-uIj4+faQ+MgHgwUW1l2PsPglZLOLOT1uErt06dAPtx2kjteLAkbsd/0FiYg/MGS+i7ZKLb7w2WClxHkzOOuryQ==} + engines: {node: '>= 18'} + hasBin: true + math-intrinsics@1.1.0: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} @@ -1713,6 +1926,10 @@ packages: resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} engines: {node: '>=8.6'} + minimatch@10.0.3: + resolution: {integrity: sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw==} + engines: {node: 20 || >=22} + minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} @@ -1723,17 +1940,18 @@ packages: minimist@1.2.8: resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + minipass@7.1.2: + resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} + engines: {node: '>=16 || 14 >=14.17'} + mitt@3.0.1: resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==} moment@2.30.1: resolution: {integrity: sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==} - monaco-editor@0.52.2: - resolution: {integrity: sha512-GEQWEZmfkOGLdd3XK8ryrfWz3AIP8YymVXiPHEdewrUq7mh0qrKrfHLNCXcbB6sTnMLnOZ3ztSiKcciFUkIJwQ==} - - ms@2.1.2: - resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} + monaco-editor@0.54.0: + resolution: {integrity: sha512-hx45SEUoLatgWxHKCmlLJH81xBo0uXP4sRkESUpmDQevfi+e7K1VuiSprK6UpQ8u4zOcKNiH0pMvHvlMWA/4cw==} ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} @@ -1743,8 +1961,8 @@ packages: engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true - nanoid@5.1.5: - resolution: {integrity: sha512-Ir/+ZpE9fDsNH0hQ3C68uyThDXzYcim2EqcZ8zn8Chtt1iylPT9xXJB0kPCnqzgcEGikO9RxSrh63MsmVCU7Fw==} + nanoid@5.1.6: + resolution: {integrity: sha512-c7+7RQ+dMB5dPwwCp4ee1/iV/q2P6aK1mTZcfr1BTuVlyW9hJYiMPybJCcnBlQtuSmTIWNeazm/zqNoZSSElBg==} engines: {node: ^18 || >=20} hasBin: true @@ -1774,10 +1992,6 @@ packages: resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} engines: {node: '>= 0.4'} - object.assign@4.1.5: - resolution: {integrity: sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==} - engines: {node: '>= 0.4'} - object.assign@4.1.7: resolution: {integrity: sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==} engines: {node: '>= 0.4'} @@ -1825,6 +2039,9 @@ packages: resolution: {integrity: sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==} engines: {node: '>= 14'} + package-json-from-dist@1.0.1: + resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} + pako@1.0.11: resolution: {integrity: sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==} @@ -1846,10 +2063,6 @@ packages: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} - path-is-absolute@1.0.1: - resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} - engines: {node: '>=0.10.0'} - path-key@3.1.1: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} engines: {node: '>=8'} @@ -1857,6 +2070,10 @@ packages: path-parse@1.0.7: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + path-scurry@2.0.0: + resolution: {integrity: sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==} + engines: {node: 20 || >=22} + path-type@4.0.0: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} engines: {node: '>=8'} @@ -1871,8 +2088,8 @@ packages: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} engines: {node: '>=8.6'} - picomatch@4.0.2: - resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==} + picomatch@4.0.3: + resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} engines: {node: '>=12'} possible-typed-array-names@1.1.0: @@ -1895,8 +2112,8 @@ packages: postcss-value-parser@4.2.0: resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} - postcss@8.5.3: - resolution: {integrity: sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==} + postcss@8.5.6: + resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} engines: {node: ^10 || ^12 || >=14} prelude-ls@1.2.1: @@ -1924,13 +2141,21 @@ packages: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} - puppeteer-core@24.4.0: - resolution: {integrity: sha512-eFw66gCnWo0X8Hyf9KxxJtms7a61NJVMiSaWfItsFPzFBsjsWdmcNlBdsA1WVwln6neoHhsG+uTVesKmTREn/g==} + puppeteer-core@24.30.0: + resolution: {integrity: sha512-2S3Smy0t0W4wJnNvDe7W0bE7wDmZjfZ3ljfMgJd6hn2Hq/f0jgN+x9PULZo2U3fu5UUIJ+JP8cNUGllu8P91Pg==} engines: {node: '>=18'} q@1.5.1: resolution: {integrity: sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw==} engines: {node: '>=0.6.0', teleport: '>=0.2.0'} + deprecated: |- + You or someone you depend on is using Q, the JavaScript Promise library that gave JavaScript developers strong feelings about promises. They can almost certainly migrate to the native JavaScript promise now. Thank you literally everyone for joining me in this bet against the odds. Be excellent to each other. + + (For a CapTP with native promises, see @endo/eventual-send and @endo/captp) + + qified@0.5.2: + resolution: {integrity: sha512-7gJ6mxcQb9vUBOtbKm5mDevbe2uRcOEVp1g4gb/Q+oLntB3HY8eBhOYRxFI2mlDFlY1e4DOSCptzxarXRvzxCA==} + engines: {node: '>=20'} queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} @@ -2005,6 +2230,16 @@ packages: engines: {node: '>=10'} hasBin: true + semver@7.7.2: + resolution: {integrity: sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==} + engines: {node: '>=10'} + hasBin: true + + semver@7.7.3: + resolution: {integrity: sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==} + engines: {node: '>=10'} + hasBin: true + set-function-length@1.2.2: resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} engines: {node: '>= 0.4'} @@ -2065,10 +2300,6 @@ packages: resolution: {integrity: sha512-D3YaD0aRxR3mEcqnidIs7ReYJFVzWdd6fXJYUM8ixcQcJRGTka/b3saV0KflYhyVJXKhb947GndU35SxYNResQ==} engines: {node: '>= 10.0.0', npm: '>= 3.0.0'} - source-map-js@1.2.0: - resolution: {integrity: sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==} - engines: {node: '>=0.10.0'} - source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} @@ -2093,6 +2324,10 @@ packages: resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} engines: {node: '>=8'} + string-width@5.1.2: + resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} + engines: {node: '>=12'} + string.prototype.matchall@4.0.12: resolution: {integrity: sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==} engines: {node: '>= 0.4'} @@ -2116,6 +2351,10 @@ packages: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} + strip-ansi@7.1.2: + resolution: {integrity: sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==} + engines: {node: '>=12'} + strip-bom@3.0.0: resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} engines: {node: '>=4'} @@ -2124,20 +2363,20 @@ packages: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} - stylelint-config-recommended@15.0.0: - resolution: {integrity: sha512-9LejMFsat7L+NXttdHdTq94byn25TD+82bzGRiV1Pgasl99pWnwipXS5DguTpp3nP1XjvLXVnEJIuYBfsRjRkA==} + stylelint-config-recommended@17.0.0: + resolution: {integrity: sha512-WaMSdEiPfZTSFVoYmJbxorJfA610O0tlYuU2aEwY33UQhSPgFbClrVJYWvy3jGJx+XW37O+LyNLiZOEXhKhJmA==} engines: {node: '>=18.12.0'} peerDependencies: - stylelint: ^16.13.0 + stylelint: ^16.23.0 - stylelint-config-standard@37.0.0: - resolution: {integrity: sha512-+6eBlbSTrOn/il2RlV0zYGQwRTkr+WtzuVSs1reaWGObxnxLpbcspCUYajVQHonVfxVw2U+h42azGhrBvcg8OA==} + stylelint-config-standard@39.0.1: + resolution: {integrity: sha512-b7Fja59EYHRNOTa3aXiuWnhUWXFU2Nfg6h61bLfAb5GS5fX3LMUD0U5t4S8N/4tpHQg3Acs2UVPR9jy2l1g/3A==} engines: {node: '>=18.12.0'} peerDependencies: - stylelint: ^16.13.0 + stylelint: ^16.23.0 - stylelint@16.17.0: - resolution: {integrity: sha512-I9OwVIWRMqVm2Br5iTbrfSqGRPWQUlvm6oXO1xZuYYu0Gpduy67N8wXOZv15p6E/JdlZiAtQaIoLKZEWk5hrjw==} + stylelint@16.25.0: + resolution: {integrity: sha512-Li0avYWV4nfv1zPbdnxLYBGq4z8DVZxbRgx4Kn6V+Uftz1rMoF1qiEI3oL4kgWqyYgCgs7gT5maHNZ82Gk03vQ==} engines: {node: '>=18.12.0'} hasBin: true @@ -2164,8 +2403,12 @@ packages: resolution: {integrity: sha512-9kY+CygyYM6j02t5YFHbNz2FN5QmYGv9zAjVp4lCDjlCw7amdckXlEt/bjMhUIfj4ThGRE4gCUH5+yGnNuPo5A==} engines: {node: '>=10.0.0'} - tar-fs@3.0.8: - resolution: {integrity: sha512-ZoROL70jptorGAlgAYiLoBLItEKw/fUxg9BSYK/dF/GAGYFJOJJJMvjPAKDJraCXFwadD456FCuvLWgfhMsPwg==} + tagged-tag@1.0.0: + resolution: {integrity: sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==} + engines: {node: '>=20'} + + tar-fs@3.1.1: + resolution: {integrity: sha512-LZA0oaPOc2fVo82Txf3gw+AkEd38szODlptMYejQUhndHMLQ9M059uXR+AfS7DNo0NpINvSqDsvyaCrBVkptWg==} tar-stream@3.1.7: resolution: {integrity: sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==} @@ -2209,6 +2452,11 @@ packages: engines: {node: '>=18.0.0'} hasBin: true + tsx@4.20.6: + resolution: {integrity: sha512-ytQKuwgmrrkDTFP4LjR0ToE2nqgy886GpvRSpU0JAnrdBYppuY5rLkRUYPU1yCryb24SsKBTL/hlDQAEFVwtZg==} + engines: {node: '>=18.0.0'} + hasBin: true + type-check@0.4.0: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} @@ -2221,6 +2469,10 @@ packages: resolution: {integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==} engines: {node: '>=16'} + type-fest@5.2.0: + resolution: {integrity: sha512-xxCJm+Bckc6kQBknN7i9fnP/xobQRsRQxR01CztFkp/h++yfVxUUcmMgfR2HttJx/dpWjS9ubVuyspJv24Q9DA==} + engines: {node: '>=20'} + typed-array-buffer@1.0.3: resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==} engines: {node: '>= 0.4'} @@ -2240,20 +2492,20 @@ packages: typed-query-selector@2.12.0: resolution: {integrity: sha512-SbklCd1F0EiZOyPiW192rrHZzZ5sBijB6xM+cpmrwDqObvdtunOHHIk9fCGsoK5JVIYXoyEp4iEdE3upFH3PAg==} - typescript-eslint@8.28.0: - resolution: {integrity: sha512-jfZtxJoHm59bvoCMYCe2BM0/baMswRhMmYhy+w6VfcyHrjxZ0OJe0tGasydCpIpA+A/WIJhTyZfb3EtwNC/kHQ==} + typescript-eslint@8.47.0: + resolution: {integrity: sha512-Lwe8i2XQ3WoMjua/r1PHrCTpkubPYJCAfOurtn+mtTzqB6jNd+14n9UN1bJ4s3F49x9ixAm0FLflB/JzQ57M8Q==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <5.9.0' + typescript: '>=4.8.4 <6.0.0' typescript-transform-paths@3.5.5: resolution: {integrity: sha512-RMK86wKe/4+ad+3kMT9SKAs3K0tUHLe7hF+MLbD6VpC9VUmFuKorhf3pHz+qO5GdS4mUp2ncNUo14j6ws9UvBQ==} peerDependencies: typescript: '>=3.6.5' - typescript@5.8.2: - resolution: {integrity: sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==} + typescript@5.9.3: + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} engines: {node: '>=14.17'} hasBin: true @@ -2264,6 +2516,9 @@ packages: undici-types@6.20.0: resolution: {integrity: sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==} + undici-types@7.16.0: + resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} + universalify@2.0.1: resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} engines: {node: '>= 10.0.0'} @@ -2287,6 +2542,9 @@ packages: vscode-textmate@5.2.0: resolution: {integrity: sha512-Uw5ooOQxRASHgu6C7GVvUxisKXfSgW4oFlO+aa+PAkgmH89O3CXxEEzNRNtHSqtXFTl0nAC1uYj0GMSH27uwtQ==} + webdriver-bidi-protocol@0.3.8: + resolution: {integrity: sha512-21Yi2GhGntMc671vNBCjiAeEVknXjVRoyu+k+9xOMShu+ZQfpGQwnBqbNz/Sv4GXZ6JmutlPAi2nIJcrymAWuQ==} + which-boxed-primitive@1.1.1: resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==} engines: {node: '>= 0.4'} @@ -2325,6 +2583,10 @@ packages: resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} engines: {node: '>=10'} + wrap-ansi@8.1.0: + resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} + engines: {node: '>=12'} + wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} @@ -2332,8 +2594,8 @@ packages: resolution: {integrity: sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - ws@8.18.1: - resolution: {integrity: sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w==} + ws@8.18.3: + resolution: {integrity: sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==} engines: {node: '>=10.0.0'} peerDependencies: bufferutil: ^4.0.1 @@ -2385,132 +2647,222 @@ snapshots: js-tokens: 4.0.0 picocolors: 1.1.1 - '@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3)': + '@cacheable/memory@2.0.5': dependencies: - '@csstools/css-tokenizer': 3.0.3 + '@cacheable/utils': 2.3.1 + '@keyv/bigmap': 1.3.0(keyv@5.5.4) + hookified: 1.13.0 + keyv: 5.5.4 - '@csstools/css-tokenizer@3.0.3': {} - - '@csstools/media-query-list-parser@4.0.2(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3)': + '@cacheable/utils@2.3.1': dependencies: - '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3) - '@csstools/css-tokenizer': 3.0.3 + hashery: 1.2.0 + keyv: 5.5.4 + + '@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4)': + dependencies: + '@csstools/css-tokenizer': 3.0.4 + + '@csstools/css-tokenizer@3.0.4': {} + + '@csstools/media-query-list-parser@4.0.3(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)': + dependencies: + '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) + '@csstools/css-tokenizer': 3.0.4 '@csstools/selector-specificity@5.0.0(postcss-selector-parser@7.1.0)': dependencies: postcss-selector-parser: 7.1.0 - '@dual-bundle/import-meta-resolve@4.1.0': {} + '@dual-bundle/import-meta-resolve@4.2.1': {} - '@electron/asar@3.2.10': + '@electron/asar@4.0.1': dependencies: - commander: 5.1.0 - glob: 7.2.3 - minimatch: 3.1.2 + commander: 13.1.0 + glob: 11.0.3 + minimatch: 10.0.3 '@esbuild/aix-ppc64@0.25.1': optional: true + '@esbuild/aix-ppc64@0.27.0': + optional: true + '@esbuild/android-arm64@0.25.1': optional: true + '@esbuild/android-arm64@0.27.0': + optional: true + '@esbuild/android-arm@0.25.1': optional: true + '@esbuild/android-arm@0.27.0': + optional: true + '@esbuild/android-x64@0.25.1': optional: true + '@esbuild/android-x64@0.27.0': + optional: true + '@esbuild/darwin-arm64@0.25.1': optional: true + '@esbuild/darwin-arm64@0.27.0': + optional: true + '@esbuild/darwin-x64@0.25.1': optional: true + '@esbuild/darwin-x64@0.27.0': + optional: true + '@esbuild/freebsd-arm64@0.25.1': optional: true + '@esbuild/freebsd-arm64@0.27.0': + optional: true + '@esbuild/freebsd-x64@0.25.1': optional: true + '@esbuild/freebsd-x64@0.27.0': + optional: true + '@esbuild/linux-arm64@0.25.1': optional: true + '@esbuild/linux-arm64@0.27.0': + optional: true + '@esbuild/linux-arm@0.25.1': optional: true + '@esbuild/linux-arm@0.27.0': + optional: true + '@esbuild/linux-ia32@0.25.1': optional: true + '@esbuild/linux-ia32@0.27.0': + optional: true + '@esbuild/linux-loong64@0.25.1': optional: true + '@esbuild/linux-loong64@0.27.0': + optional: true + '@esbuild/linux-mips64el@0.25.1': optional: true + '@esbuild/linux-mips64el@0.27.0': + optional: true + '@esbuild/linux-ppc64@0.25.1': optional: true + '@esbuild/linux-ppc64@0.27.0': + optional: true + '@esbuild/linux-riscv64@0.25.1': optional: true + '@esbuild/linux-riscv64@0.27.0': + optional: true + '@esbuild/linux-s390x@0.25.1': optional: true + '@esbuild/linux-s390x@0.27.0': + optional: true + '@esbuild/linux-x64@0.25.1': optional: true + '@esbuild/linux-x64@0.27.0': + optional: true + '@esbuild/netbsd-arm64@0.25.1': optional: true + '@esbuild/netbsd-arm64@0.27.0': + optional: true + '@esbuild/netbsd-x64@0.25.1': optional: true + '@esbuild/netbsd-x64@0.27.0': + optional: true + '@esbuild/openbsd-arm64@0.25.1': optional: true + '@esbuild/openbsd-arm64@0.27.0': + optional: true + '@esbuild/openbsd-x64@0.25.1': optional: true + '@esbuild/openbsd-x64@0.27.0': + optional: true + + '@esbuild/openharmony-arm64@0.27.0': + optional: true + '@esbuild/sunos-x64@0.25.1': optional: true + '@esbuild/sunos-x64@0.27.0': + optional: true + '@esbuild/win32-arm64@0.25.1': optional: true + '@esbuild/win32-arm64@0.27.0': + optional: true + '@esbuild/win32-ia32@0.25.1': optional: true + '@esbuild/win32-ia32@0.27.0': + optional: true + '@esbuild/win32-x64@0.25.1': optional: true - '@eslint-community/eslint-utils@4.5.1(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215))': + '@esbuild/win32-x64@0.27.0': + optional: true + + '@eslint-community/eslint-utils@4.9.0(eslint@9.39.1)': dependencies: - eslint: 9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215) + eslint: 9.39.1 eslint-visitor-keys: 3.4.3 '@eslint-community/regexpp@4.12.1': {} - '@eslint/config-array@0.19.2': + '@eslint/config-array@0.21.1': dependencies: - '@eslint/object-schema': 2.1.6 - debug: 4.4.0 + '@eslint/object-schema': 2.1.7 + debug: 4.4.1 minimatch: 3.1.2 transitivePeerDependencies: - supports-color - '@eslint/core@0.11.0': + '@eslint/config-helpers@0.4.2': dependencies: - '@types/json-schema': 7.0.15 + '@eslint/core': 0.17.0 - '@eslint/core@0.12.0': + '@eslint/core@0.17.0': dependencies: '@types/json-schema': 7.0.15 '@eslint/eslintrc@3.3.1': dependencies: ajv: 6.12.6 - debug: 4.3.6 - espree: 10.3.0 + debug: 4.4.1 + espree: 10.4.0 globals: 14.0.0 ignore: 5.3.1 import-fresh: 3.3.1 @@ -2520,13 +2872,13 @@ snapshots: transitivePeerDependencies: - supports-color - '@eslint/js@9.20.0': {} + '@eslint/js@9.39.1': {} - '@eslint/object-schema@2.1.6': {} + '@eslint/object-schema@2.1.7': {} - '@eslint/plugin-kit@0.2.7': + '@eslint/plugin-kit@0.4.1': dependencies: - '@eslint/core': 0.12.0 + '@eslint/core': 0.17.0 levn: 0.4.1 '@ffmpeg/ffmpeg@0.12.10': @@ -2552,6 +2904,21 @@ snapshots: '@intrnl/xxhash64@0.1.2': {} + '@isaacs/balanced-match@4.0.1': {} + + '@isaacs/brace-expansion@5.0.0': + dependencies: + '@isaacs/balanced-match': 4.0.1 + + '@isaacs/cliui@8.0.2': + dependencies: + string-width: 5.1.2 + string-width-cjs: string-width@4.2.3 + strip-ansi: 7.1.2 + strip-ansi-cjs: strip-ansi@6.0.1 + wrap-ansi: 8.1.0 + wrap-ansi-cjs: wrap-ansi@7.0.0 + '@jridgewell/gen-mapping@0.3.5': dependencies: '@jridgewell/set-array': 1.2.1 @@ -2574,9 +2941,13 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.4.15 - '@keyv/serialize@1.0.3': + '@keyv/bigmap@1.3.0(keyv@5.5.4)': dependencies: - buffer: 6.0.3 + hashery: 1.2.0 + hookified: 1.13.0 + keyv: 5.5.4 + + '@keyv/serialize@1.1.1': {} '@nodelib/fs.scandir@2.1.5': dependencies: @@ -2590,14 +2961,14 @@ snapshots: '@nodelib/fs.scandir': 2.1.5 fastq: 1.19.1 - '@puppeteer/browsers@2.8.0': + '@puppeteer/browsers@2.10.13': dependencies: - debug: 4.4.0 + debug: 4.4.3 extract-zip: 2.0.1 progress: 2.0.3 proxy-agent: 6.5.0 - semver: 7.7.1 - tar-fs: 3.0.8 + semver: 7.7.3 + tar-fs: 3.1.1 yargs: 17.7.2 transitivePeerDependencies: - bare-buffer @@ -2605,21 +2976,23 @@ snapshots: '@rtsao/scc@1.1.0': {} - '@stylistic/eslint-plugin@4.2.0(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215))(typescript@5.8.2)': + '@stylistic/eslint-plugin@5.6.0(eslint@9.39.1)': dependencies: - '@typescript-eslint/utils': 8.28.0(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215))(typescript@5.8.2) - eslint: 9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215) - eslint-visitor-keys: 4.2.0 - espree: 10.3.0 + '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1) + '@typescript-eslint/types': 8.47.0 + eslint: 9.39.1 + eslint-visitor-keys: 4.2.1 + espree: 10.4.0 estraverse: 5.3.0 - picomatch: 4.0.2 - transitivePeerDependencies: - - supports-color - - typescript + picomatch: 4.0.3 '@tootallnate/quickjs-emscripten@0.23.0': {} - '@types/chrome@0.0.312': + '@types/adm-zip@0.5.7': + dependencies: + '@types/node': 24.10.1 + + '@types/chrome@0.1.30': dependencies: '@types/filesystem': 0.0.36 '@types/har-format': 1.2.15 @@ -2649,19 +3022,21 @@ snapshots: dependencies: '@types/node': 22.13.13 - '@types/less@3.0.6(patch_hash=641e6c93bb737bac7fc283416857bd095cd85bcbcba63becb7a8bbcc78f73076)': {} + '@types/less@3.0.8(patch_hash=641e6c93bb737bac7fc283416857bd095cd85bcbcba63becb7a8bbcc78f73076)': {} '@types/lodash@4.17.15': {} - '@types/node@22.10.5': - dependencies: - undici-types: 6.20.0 + '@types/lodash@4.17.20': {} '@types/node@22.13.13': dependencies: undici-types: 6.20.0 - '@types/prop-types@15.7.14': {} + '@types/node@24.10.1': + dependencies: + undici-types: 7.16.0 + + '@types/prop-types@15.7.15': {} '@types/react-dom@18.3.1': dependencies: @@ -2673,7 +3048,7 @@ snapshots: '@types/react@18.3.1': dependencies: - '@types/prop-types': 15.7.14 + '@types/prop-types': 15.7.15 csstype: 3.1.3 '@types/react@19.0.12': @@ -2682,95 +3057,111 @@ snapshots: '@types/stylus@0.48.42': dependencies: - '@types/node': 22.10.5 + '@types/node': 24.10.1 '@types/tinycolor2@1.4.6': {} '@types/yauzl@2.10.3': dependencies: - '@types/node': 22.13.13 + '@types/node': 24.10.1 optional: true - '@types/yazl@2.4.6': + '@types/yazl@3.3.0': dependencies: - '@types/node': 22.13.13 + '@types/node': 24.10.1 - '@typescript-eslint/eslint-plugin@8.28.0(@typescript-eslint/parser@8.28.0(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215))(typescript@5.8.2))(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215))(typescript@5.8.2)': + '@typescript-eslint/eslint-plugin@8.47.0(@typescript-eslint/parser@8.47.0(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1)(typescript@5.9.3)': dependencies: '@eslint-community/regexpp': 4.12.1 - '@typescript-eslint/parser': 8.28.0(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215))(typescript@5.8.2) - '@typescript-eslint/scope-manager': 8.28.0 - '@typescript-eslint/type-utils': 8.28.0(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215))(typescript@5.8.2) - '@typescript-eslint/utils': 8.28.0(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215))(typescript@5.8.2) - '@typescript-eslint/visitor-keys': 8.28.0 - eslint: 9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215) + '@typescript-eslint/parser': 8.47.0(eslint@9.39.1)(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.47.0 + '@typescript-eslint/type-utils': 8.47.0(eslint@9.39.1)(typescript@5.9.3) + '@typescript-eslint/utils': 8.47.0(eslint@9.39.1)(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.47.0 + eslint: 9.39.1 graphemer: 1.4.0 - ignore: 5.3.1 + ignore: 7.0.3 natural-compare: 1.4.0 - ts-api-utils: 2.1.0(typescript@5.8.2) - typescript: 5.8.2 + ts-api-utils: 2.1.0(typescript@5.9.3) + typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.28.0(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215))(typescript@5.8.2)': + '@typescript-eslint/parser@8.47.0(eslint@9.39.1)(typescript@5.9.3)': dependencies: - '@typescript-eslint/scope-manager': 8.28.0 - '@typescript-eslint/types': 8.28.0 - '@typescript-eslint/typescript-estree': 8.28.0(typescript@5.8.2) - '@typescript-eslint/visitor-keys': 8.28.0 - debug: 4.4.0 - eslint: 9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215) - typescript: 5.8.2 + '@typescript-eslint/scope-manager': 8.47.0 + '@typescript-eslint/types': 8.47.0 + '@typescript-eslint/typescript-estree': 8.47.0(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.47.0 + debug: 4.4.1 + eslint: 9.39.1 + typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/scope-manager@8.28.0': + '@typescript-eslint/project-service@8.47.0(typescript@5.9.3)': dependencies: - '@typescript-eslint/types': 8.28.0 - '@typescript-eslint/visitor-keys': 8.28.0 - - '@typescript-eslint/type-utils@8.28.0(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215))(typescript@5.8.2)': - dependencies: - '@typescript-eslint/typescript-estree': 8.28.0(typescript@5.8.2) - '@typescript-eslint/utils': 8.28.0(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215))(typescript@5.8.2) - debug: 4.4.0 - eslint: 9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215) - ts-api-utils: 2.1.0(typescript@5.8.2) - typescript: 5.8.2 + '@typescript-eslint/tsconfig-utils': 8.47.0(typescript@5.9.3) + '@typescript-eslint/types': 8.47.0 + debug: 4.4.1 + typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/types@8.28.0': {} - - '@typescript-eslint/typescript-estree@8.28.0(typescript@5.8.2)': + '@typescript-eslint/scope-manager@8.47.0': dependencies: - '@typescript-eslint/types': 8.28.0 - '@typescript-eslint/visitor-keys': 8.28.0 - debug: 4.4.0 + '@typescript-eslint/types': 8.47.0 + '@typescript-eslint/visitor-keys': 8.47.0 + + '@typescript-eslint/tsconfig-utils@8.47.0(typescript@5.9.3)': + dependencies: + typescript: 5.9.3 + + '@typescript-eslint/type-utils@8.47.0(eslint@9.39.1)(typescript@5.9.3)': + dependencies: + '@typescript-eslint/types': 8.47.0 + '@typescript-eslint/typescript-estree': 8.47.0(typescript@5.9.3) + '@typescript-eslint/utils': 8.47.0(eslint@9.39.1)(typescript@5.9.3) + debug: 4.4.1 + eslint: 9.39.1 + ts-api-utils: 2.1.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/types@8.47.0': {} + + '@typescript-eslint/typescript-estree@8.47.0(typescript@5.9.3)': + dependencies: + '@typescript-eslint/project-service': 8.47.0(typescript@5.9.3) + '@typescript-eslint/tsconfig-utils': 8.47.0(typescript@5.9.3) + '@typescript-eslint/types': 8.47.0 + '@typescript-eslint/visitor-keys': 8.47.0 + debug: 4.4.1 fast-glob: 3.3.3 is-glob: 4.0.3 minimatch: 9.0.5 - semver: 7.7.1 - ts-api-utils: 2.1.0(typescript@5.8.2) - typescript: 5.8.2 + semver: 7.7.2 + ts-api-utils: 2.1.0(typescript@5.9.3) + typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.28.0(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215))(typescript@5.8.2)': + '@typescript-eslint/utils@8.47.0(eslint@9.39.1)(typescript@5.9.3)': dependencies: - '@eslint-community/eslint-utils': 4.5.1(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215)) - '@typescript-eslint/scope-manager': 8.28.0 - '@typescript-eslint/types': 8.28.0 - '@typescript-eslint/typescript-estree': 8.28.0(typescript@5.8.2) - eslint: 9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215) - typescript: 5.8.2 + '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1) + '@typescript-eslint/scope-manager': 8.47.0 + '@typescript-eslint/types': 8.47.0 + '@typescript-eslint/typescript-estree': 8.47.0(typescript@5.9.3) + eslint: 9.39.1 + typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/visitor-keys@8.28.0': + '@typescript-eslint/visitor-keys@8.47.0': dependencies: - '@typescript-eslint/types': 8.28.0 - eslint-visitor-keys: 4.2.0 + '@typescript-eslint/types': 8.47.0 + eslint-visitor-keys: 4.2.1 '@vap/core@0.0.12': dependencies: @@ -2782,12 +3173,22 @@ snapshots: vscode-oniguruma: 1.7.0 vscode-textmate: 5.2.0 - acorn-jsx@5.3.2(acorn@8.14.1): + '@vencord/discord-types@1.0.0(@types/react@18.3.1)': dependencies: - acorn: 8.14.1 + '@types/react': 18.3.1 + moment: 2.30.1 + type-fest: 4.41.0 + + acorn-jsx@5.3.2(acorn@8.15.0): + dependencies: + acorn: 8.15.0 acorn@8.14.1: {} + acorn@8.15.0: {} + + adm-zip@0.5.16: {} + agent-base@7.1.3: {} ajv@6.12.6: @@ -2806,6 +3207,8 @@ snapshots: ansi-regex@5.0.1: {} + ansi-regex@6.2.2: {} + ansi-styles@3.2.1: dependencies: color-convert: 1.9.3 @@ -2814,6 +3217,8 @@ snapshots: dependencies: color-convert: 2.0.1 + ansi-styles@6.2.3: {} + argparse@2.0.1: {} array-buffer-byte-length@1.0.2: @@ -2823,7 +3228,7 @@ snapshots: array-includes@3.1.8: dependencies: - call-bind: 1.0.7 + call-bind: 1.0.8 define-properties: 1.2.1 es-abstract: 1.23.9 es-object-atoms: 1.1.1 @@ -2834,7 +3239,7 @@ snapshots: array.prototype.findlast@1.2.5: dependencies: - call-bind: 1.0.7 + call-bind: 1.0.8 define-properties: 1.2.1 es-abstract: 1.23.9 es-errors: 1.3.0 @@ -2853,7 +3258,7 @@ snapshots: array.prototype.flat@1.3.2: dependencies: - call-bind: 1.0.7 + call-bind: 1.0.8 define-properties: 1.2.1 es-abstract: 1.23.9 es-shim-unscopables: 1.1.0 @@ -2867,7 +3272,7 @@ snapshots: array.prototype.tosorted@1.1.4: dependencies: - call-bind: 1.0.7 + call-bind: 1.0.8 define-properties: 1.2.1 es-abstract: 1.23.9 es-errors: 1.3.0 @@ -2928,16 +3333,14 @@ snapshots: bare-events: 2.5.4 optional: true - base64-js@1.5.1: {} - basic-ftp@5.0.5: {} - brace-expansion@1.1.11: + brace-expansion@1.1.12: dependencies: balanced-match: 1.0.2 concat-map: 0.0.1 - brace-expansion@2.0.1: + brace-expansion@2.0.2: dependencies: balanced-match: 1.0.2 @@ -2949,29 +3352,19 @@ snapshots: buffer-from@1.1.2: {} - buffer@6.0.3: + cacheable@2.2.0: dependencies: - base64-js: 1.5.1 - ieee754: 1.2.1 - - cacheable@1.8.9: - dependencies: - hookified: 1.8.1 - keyv: 5.3.2 + '@cacheable/memory': 2.0.5 + '@cacheable/utils': 2.3.1 + hookified: 1.13.0 + keyv: 5.5.4 + qified: 0.5.2 call-bind-apply-helpers@1.0.2: dependencies: es-errors: 1.3.0 function-bind: 1.1.2 - call-bind@1.0.7: - dependencies: - es-define-property: 1.0.0 - es-errors: 1.3.0 - function-bind: 1.1.2 - get-intrinsic: 1.2.4 - set-function-length: 1.2.2 - call-bind@1.0.8: dependencies: call-bind-apply-helpers: 1.0.2 @@ -3002,9 +3395,9 @@ snapshots: ansi-styles: 4.3.0 supports-color: 7.2.0 - chromium-bidi@2.1.2(devtools-protocol@0.0.1413902): + chromium-bidi@11.0.0(devtools-protocol@0.0.1521046): dependencies: - devtools-protocol: 0.0.1413902 + devtools-protocol: 0.0.1521046 mitt: 3.0.1 zod: 3.24.2 @@ -3034,20 +3427,20 @@ snapshots: commander@10.0.1: {} - commander@2.20.3: {} + commander@13.1.0: {} - commander@5.1.0: {} + commander@2.20.3: {} concat-map@0.0.1: {} - cosmiconfig@9.0.0(typescript@5.8.2): + cosmiconfig@9.0.0(typescript@5.9.3): dependencies: env-paths: 2.2.1 import-fresh: 3.3.1 js-yaml: 4.1.0 parse-json: 5.2.0 optionalDependencies: - typescript: 5.8.2 + typescript: 5.9.3 cross-spawn@7.0.6: dependencies: @@ -3060,7 +3453,7 @@ snapshots: css-tree@3.1.0: dependencies: mdn-data: 2.12.2 - source-map-js: 1.2.0 + source-map-js: 1.2.1 cssesc@3.0.0: {} @@ -3090,11 +3483,11 @@ snapshots: dependencies: ms: 2.1.3 - debug@4.3.6: + debug@4.4.1: dependencies: - ms: 2.1.2 + ms: 2.1.3 - debug@4.4.0: + debug@4.4.3: dependencies: ms: 2.1.3 @@ -3102,9 +3495,9 @@ snapshots: define-data-property@1.1.4: dependencies: - es-define-property: 1.0.0 + es-define-property: 1.0.1 es-errors: 1.3.0 - gopd: 1.0.1 + gopd: 1.2.0 define-properties@1.2.1: dependencies: @@ -3118,9 +3511,9 @@ snapshots: escodegen: 2.1.0 esprima: 4.0.1 - devtools-protocol@0.0.1413902: {} + devtools-protocol@0.0.1521046: {} - diff@7.0.0: {} + diff@8.0.2: {} dir-glob@3.0.1: dependencies: @@ -3130,6 +3523,8 @@ snapshots: dependencies: esutils: 2.0.3 + dompurify@3.1.7: {} + dot-case@3.0.4: dependencies: no-case: 3.0.4 @@ -3141,8 +3536,12 @@ snapshots: es-errors: 1.3.0 gopd: 1.2.0 + eastasianwidth@0.2.0: {} + emoji-regex@8.0.0: {} + emoji-regex@9.2.2: {} + end-of-stream@1.4.4: dependencies: once: 1.4.0 @@ -3209,10 +3608,6 @@ snapshots: unbox-primitive: 1.1.0 which-typed-array: 1.1.19 - es-define-property@1.0.0: - dependencies: - get-intrinsic: 1.2.4 - es-define-property@1.0.1: {} es-errors@1.3.0: {} @@ -3224,7 +3619,7 @@ snapshots: define-properties: 1.2.1 es-abstract: 1.23.9 es-errors: 1.3.0 - es-set-tostringtag: 2.0.3 + es-set-tostringtag: 2.1.0 function-bind: 1.1.2 get-intrinsic: 1.3.0 globalthis: 1.0.4 @@ -3240,12 +3635,6 @@ snapshots: dependencies: es-errors: 1.3.0 - es-set-tostringtag@2.0.3: - dependencies: - get-intrinsic: 1.2.7 - has-tostringtag: 1.0.2 - hasown: 2.0.2 - es-set-tostringtag@2.1.0: dependencies: es-errors: 1.3.0 @@ -3260,8 +3649,8 @@ snapshots: es-to-primitive@1.3.0: dependencies: is-callable: 1.2.7 - is-date-object: 1.0.5 - is-symbol: 1.0.4 + is-date-object: 1.1.0 + is-symbol: 1.1.1 esbuild@0.25.1: optionalDependencies: @@ -3291,6 +3680,35 @@ snapshots: '@esbuild/win32-ia32': 0.25.1 '@esbuild/win32-x64': 0.25.1 + esbuild@0.27.0: + optionalDependencies: + '@esbuild/aix-ppc64': 0.27.0 + '@esbuild/android-arm': 0.27.0 + '@esbuild/android-arm64': 0.27.0 + '@esbuild/android-x64': 0.27.0 + '@esbuild/darwin-arm64': 0.27.0 + '@esbuild/darwin-x64': 0.27.0 + '@esbuild/freebsd-arm64': 0.27.0 + '@esbuild/freebsd-x64': 0.27.0 + '@esbuild/linux-arm': 0.27.0 + '@esbuild/linux-arm64': 0.27.0 + '@esbuild/linux-ia32': 0.27.0 + '@esbuild/linux-loong64': 0.27.0 + '@esbuild/linux-mips64el': 0.27.0 + '@esbuild/linux-ppc64': 0.27.0 + '@esbuild/linux-riscv64': 0.27.0 + '@esbuild/linux-s390x': 0.27.0 + '@esbuild/linux-x64': 0.27.0 + '@esbuild/netbsd-arm64': 0.27.0 + '@esbuild/netbsd-x64': 0.27.0 + '@esbuild/openbsd-arm64': 0.27.0 + '@esbuild/openbsd-x64': 0.27.0 + '@esbuild/openharmony-arm64': 0.27.0 + '@esbuild/sunos-x64': 0.27.0 + '@esbuild/win32-arm64': 0.27.0 + '@esbuild/win32-ia32': 0.27.0 + '@esbuild/win32-x64': 0.27.0 + escalade@3.1.2: {} escape-string-regexp@1.0.5: {} @@ -3305,9 +3723,9 @@ snapshots: optionalDependencies: source-map: 0.6.1 - eslint-import-resolver-alias@1.1.2(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.28.0(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215))(typescript@5.8.2))(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215))): + eslint-import-resolver-alias@1.1.2(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.47.0(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1)): dependencies: - eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.28.0(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215))(typescript@5.8.2))(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215)) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.47.0(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1) eslint-import-resolver-node@0.3.9: dependencies: @@ -3317,17 +3735,17 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.0(@typescript-eslint/parser@8.28.0(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215))(typescript@5.8.2))(eslint-import-resolver-node@0.3.9)(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215)): + eslint-module-utils@2.12.0(@typescript-eslint/parser@8.47.0(eslint@9.39.1)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint@9.39.1): dependencies: debug: 3.2.7 optionalDependencies: - '@typescript-eslint/parser': 8.28.0(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215))(typescript@5.8.2) - eslint: 9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215) + '@typescript-eslint/parser': 8.47.0(eslint@9.39.1)(typescript@5.9.3) + eslint: 9.39.1 eslint-import-resolver-node: 0.3.9 transitivePeerDependencies: - supports-color - eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.28.0(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215))(typescript@5.8.2))(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215)): + eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.47.0(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.8 @@ -3336,9 +3754,9 @@ snapshots: array.prototype.flatmap: 1.3.3 debug: 3.2.7 doctrine: 2.1.0 - eslint: 9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215) + eslint: 9.39.1 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.28.0(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215))(typescript@5.8.2))(eslint-import-resolver-node@0.3.9)(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215)) + eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.47.0(eslint@9.39.1)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint@9.39.1) hasown: 2.0.2 is-core-module: 2.16.1 is-glob: 4.0.3 @@ -3350,13 +3768,13 @@ snapshots: string.prototype.trimend: 1.0.9 tsconfig-paths: 3.15.0 optionalDependencies: - '@typescript-eslint/parser': 8.28.0(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215))(typescript@5.8.2) + '@typescript-eslint/parser': 8.47.0(eslint@9.39.1)(typescript@5.9.3) transitivePeerDependencies: - eslint-import-resolver-typescript - eslint-import-resolver-webpack - supports-color - eslint-plugin-react@7.37.4(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215)): + eslint-plugin-react@7.37.5(eslint@9.39.1): dependencies: array-includes: 3.1.8 array.prototype.findlast: 1.2.5 @@ -3364,7 +3782,7 @@ snapshots: array.prototype.tosorted: 1.1.4 doctrine: 2.1.0 es-iterator-helpers: 1.2.1 - eslint: 9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215) + eslint: 9.39.1 estraverse: 5.3.0 hasown: 2.0.2 jsx-ast-utils: 3.3.5 @@ -3378,51 +3796,51 @@ snapshots: string.prototype.matchall: 4.0.12 string.prototype.repeat: 1.0.0 - eslint-plugin-simple-header@1.2.2(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215)): + eslint-plugin-simple-header@1.2.2(eslint@9.39.1): dependencies: - eslint: 9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215) + eslint: 9.39.1 - eslint-plugin-simple-import-sort@12.1.1(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215)): + eslint-plugin-simple-import-sort@12.1.1(eslint@9.39.1): dependencies: - eslint: 9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215) + eslint: 9.39.1 - eslint-plugin-unused-imports@4.1.4(@typescript-eslint/eslint-plugin@8.28.0(@typescript-eslint/parser@8.28.0(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215))(typescript@5.8.2))(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215))(typescript@5.8.2))(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215)): + eslint-plugin-unused-imports@4.3.0(@typescript-eslint/eslint-plugin@8.47.0(@typescript-eslint/parser@8.47.0(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1): dependencies: - eslint: 9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215) + eslint: 9.39.1 optionalDependencies: - '@typescript-eslint/eslint-plugin': 8.28.0(@typescript-eslint/parser@8.28.0(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215))(typescript@5.8.2))(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215))(typescript@5.8.2) + '@typescript-eslint/eslint-plugin': 8.47.0(@typescript-eslint/parser@8.47.0(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1)(typescript@5.9.3) - eslint-scope@8.3.0: + eslint-scope@8.4.0: dependencies: esrecurse: 4.3.0 estraverse: 5.3.0 eslint-visitor-keys@3.4.3: {} - eslint-visitor-keys@4.2.0: {} + eslint-visitor-keys@4.2.1: {} - eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215): + eslint@9.39.1: dependencies: - '@eslint-community/eslint-utils': 4.5.1(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215)) + '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1) '@eslint-community/regexpp': 4.12.1 - '@eslint/config-array': 0.19.2 - '@eslint/core': 0.11.0 + '@eslint/config-array': 0.21.1 + '@eslint/config-helpers': 0.4.2 + '@eslint/core': 0.17.0 '@eslint/eslintrc': 3.3.1 - '@eslint/js': 9.20.0 - '@eslint/plugin-kit': 0.2.7 + '@eslint/js': 9.39.1 + '@eslint/plugin-kit': 0.4.1 '@humanfs/node': 0.16.6 '@humanwhocodes/module-importer': 1.0.1 '@humanwhocodes/retry': 0.4.2 '@types/estree': 1.0.7 - '@types/json-schema': 7.0.15 ajv: 6.12.6 chalk: 4.1.2 cross-spawn: 7.0.6 - debug: 4.3.6 + debug: 4.4.1 escape-string-regexp: 4.0.0 - eslint-scope: 8.3.0 - eslint-visitor-keys: 4.2.0 - espree: 10.3.0 + eslint-scope: 8.4.0 + eslint-visitor-keys: 4.2.1 + espree: 10.4.0 esquery: 1.6.0 esutils: 2.0.3 fast-deep-equal: 3.1.3 @@ -3440,11 +3858,11 @@ snapshots: transitivePeerDependencies: - supports-color - espree@10.3.0: + espree@10.4.0: dependencies: - acorn: 8.14.1 - acorn-jsx: 5.3.2(acorn@8.14.1) - eslint-visitor-keys: 4.2.0 + acorn: 8.15.0 + acorn-jsx: 5.3.2(acorn@8.15.0) + eslint-visitor-keys: 4.2.1 esprima@4.0.1: {} @@ -3464,7 +3882,7 @@ snapshots: extract-zip@2.0.1: dependencies: - debug: 4.4.0 + debug: 4.4.3 get-stream: 5.2.0 yauzl: 2.10.0 optionalDependencies: @@ -3502,9 +3920,9 @@ snapshots: fflate@0.8.2: {} - file-entry-cache@10.0.7: + file-entry-cache@10.1.4: dependencies: - flat-cache: 6.1.7 + flat-cache: 6.1.19 file-entry-cache@8.0.0: dependencies: @@ -3524,11 +3942,11 @@ snapshots: flatted: 3.3.3 keyv: 4.5.4 - flat-cache@6.1.7: + flat-cache@6.1.19: dependencies: - cacheable: 1.8.9 + cacheable: 2.2.0 flatted: 3.3.3 - hookified: 1.8.1 + hookified: 1.13.0 flatted@3.3.3: {} @@ -3536,14 +3954,17 @@ snapshots: dependencies: is-callable: 1.2.7 + foreground-child@3.3.1: + dependencies: + cross-spawn: 7.0.6 + signal-exit: 4.1.0 + fs-extra@11.3.0: dependencies: graceful-fs: 4.2.11 jsonfile: 6.1.0 universalify: 2.0.1 - fs.realpath@1.0.0: {} - fsevents@2.3.3: optional: true @@ -3562,27 +3983,6 @@ snapshots: get-caller-file@2.0.5: {} - get-intrinsic@1.2.4: - dependencies: - es-errors: 1.3.0 - function-bind: 1.1.2 - has-proto: 1.0.3 - has-symbols: 1.0.3 - hasown: 2.0.2 - - get-intrinsic@1.2.7: - dependencies: - call-bind-apply-helpers: 1.0.2 - es-define-property: 1.0.1 - es-errors: 1.3.0 - es-object-atoms: 1.1.1 - function-bind: 1.1.2 - get-proto: 1.0.1 - gopd: 1.2.0 - has-symbols: 1.1.0 - hasown: 2.0.2 - math-intrinsics: 1.1.0 - get-intrinsic@1.3.0: dependencies: call-bind-apply-helpers: 1.0.2 @@ -3619,7 +4019,7 @@ snapshots: dependencies: basic-ftp: 5.0.5 data-uri-to-buffer: 6.0.2 - debug: 4.4.0 + debug: 4.4.3 fs-extra: 11.3.0 transitivePeerDependencies: - supports-color @@ -3634,14 +4034,14 @@ snapshots: dependencies: is-glob: 4.0.3 - glob@7.2.3: + glob@11.0.3: dependencies: - fs.realpath: 1.0.0 - inflight: 1.0.6 - inherits: 2.0.4 - minimatch: 3.1.2 - once: 1.4.0 - path-is-absolute: 1.0.1 + foreground-child: 3.3.1 + jackspeak: 4.1.1 + minimatch: 10.0.3 + minipass: 7.1.2 + package-json-from-dist: 1.0.1 + path-scurry: 2.0.0 global-modules@2.0.0: dependencies: @@ -3677,10 +4077,6 @@ snapshots: globjoin@0.1.4: {} - gopd@1.0.1: - dependencies: - get-intrinsic: 1.2.4 - gopd@1.2.0: {} graceful-fs@4.2.11: {} @@ -3695,21 +4091,21 @@ snapshots: has-property-descriptors@1.0.2: dependencies: - es-define-property: 1.0.0 - - has-proto@1.0.3: {} + es-define-property: 1.0.1 has-proto@1.2.0: dependencies: dunder-proto: 1.0.1 - has-symbols@1.0.3: {} - has-symbols@1.1.0: {} has-tostringtag@1.0.2: dependencies: - has-symbols: 1.0.3 + has-symbols: 1.1.0 + + hashery@1.2.0: + dependencies: + hookified: 1.13.0 hasown@2.0.2: dependencies: @@ -3717,7 +4113,7 @@ snapshots: highlight.js@11.11.1: {} - hookified@1.8.1: {} + hookified@1.13.0: {} html-minifier-terser@7.2.0: dependencies: @@ -3734,25 +4130,25 @@ snapshots: http-proxy-agent@7.0.2: dependencies: agent-base: 7.1.3 - debug: 4.4.0 + debug: 4.4.3 transitivePeerDependencies: - supports-color https-proxy-agent@7.0.6: dependencies: agent-base: 7.1.3 - debug: 4.4.0 + debug: 4.4.3 transitivePeerDependencies: - supports-color idb@8.0.3: {} - ieee754@1.2.1: {} - ignore@5.3.1: {} ignore@7.0.3: {} + ignore@7.0.5: {} + import-fresh@3.3.1: dependencies: parent-module: 1.0.1 @@ -3760,13 +4156,6 @@ snapshots: imurmurhash@0.1.4: {} - inflight@1.0.6: - dependencies: - once: 1.4.0 - wrappy: 1.0.2 - - inherits@2.0.4: {} - ini@1.3.8: {} ini@4.1.3: {} @@ -3809,10 +4198,6 @@ snapshots: is-callable@1.2.7: {} - is-core-module@2.15.0: - dependencies: - hasown: 2.0.2 - is-core-module@2.16.1: dependencies: hasown: 2.0.2 @@ -3823,10 +4208,6 @@ snapshots: get-intrinsic: 1.3.0 is-typed-array: 1.1.15 - is-date-object@1.0.5: - dependencies: - has-tostringtag: 1.0.2 - is-date-object@1.1.0: dependencies: call-bound: 1.0.4 @@ -3880,10 +4261,6 @@ snapshots: call-bound: 1.0.4 has-tostringtag: 1.0.2 - is-symbol@1.0.4: - dependencies: - has-symbols: 1.0.3 - is-symbol@1.1.1: dependencies: call-bound: 1.0.4 @@ -3920,6 +4297,10 @@ snapshots: has-symbols: 1.1.0 set-function-name: 2.0.2 + jackspeak@4.1.1: + dependencies: + '@isaacs/cliui': 8.0.2 + js-tokens@4.0.0: {} js-yaml@4.1.0: @@ -3956,7 +4337,7 @@ snapshots: dependencies: array-includes: 3.1.8 array.prototype.flat: 1.3.2 - object.assign: 4.1.5 + object.assign: 4.1.7 object.values: 1.2.1 jszip@2.7.0: @@ -3967,13 +4348,13 @@ snapshots: dependencies: json-buffer: 3.0.1 - keyv@5.3.2: + keyv@5.5.4: dependencies: - '@keyv/serialize': 1.0.3 + '@keyv/serialize': 1.1.1 kind-of@6.0.3: {} - known-css-properties@0.35.0: {} + known-css-properties@0.37.0: {} levn@0.4.1: dependencies: @@ -3998,8 +4379,12 @@ snapshots: dependencies: tslib: 2.6.2 + lru-cache@11.2.1: {} + lru-cache@7.18.3: {} + marked@14.0.0: {} + math-intrinsics@1.1.0: {} mathml-tag-names@2.1.3: {} @@ -4015,29 +4400,36 @@ snapshots: braces: 3.0.3 picomatch: 2.3.1 + minimatch@10.0.3: + dependencies: + '@isaacs/brace-expansion': 5.0.0 + minimatch@3.1.2: dependencies: - brace-expansion: 1.1.11 + brace-expansion: 1.1.12 minimatch@9.0.5: dependencies: - brace-expansion: 2.0.1 + brace-expansion: 2.0.2 minimist@1.2.8: {} + minipass@7.1.2: {} + mitt@3.0.1: {} moment@2.30.1: {} - monaco-editor@0.52.2: {} - - ms@2.1.2: {} + monaco-editor@0.54.0: + dependencies: + dompurify: 3.1.7 + marked: 14.0.0 ms@2.1.3: {} nanoid@3.3.11: {} - nanoid@5.1.5: {} + nanoid@5.1.6: {} natural-compare@1.4.0: {} @@ -4056,13 +4448,6 @@ snapshots: object-keys@1.1.1: {} - object.assign@4.1.5: - dependencies: - call-bind: 1.0.7 - define-properties: 1.2.1 - has-symbols: 1.0.3 - object-keys: 1.1.1 - object.assign@4.1.7: dependencies: call-bind: 1.0.8 @@ -4081,7 +4466,7 @@ snapshots: object.fromentries@2.0.8: dependencies: - call-bind: 1.0.7 + call-bind: 1.0.8 define-properties: 1.2.1 es-abstract: 1.23.9 es-object-atoms: 1.1.1 @@ -4130,7 +4515,7 @@ snapshots: dependencies: '@tootallnate/quickjs-emscripten': 0.23.0 agent-base: 7.1.3 - debug: 4.4.0 + debug: 4.4.3 get-uri: 6.0.3 http-proxy-agent: 7.0.2 https-proxy-agent: 7.0.6 @@ -4144,6 +4529,8 @@ snapshots: degenerator: 5.0.1 netmask: 2.0.2 + package-json-from-dist@1.0.1: {} + pako@1.0.11: {} param-case@3.0.4: @@ -4169,12 +4556,15 @@ snapshots: path-exists@4.0.0: {} - path-is-absolute@1.0.1: {} - path-key@3.1.1: {} path-parse@1.0.7: {} + path-scurry@2.0.0: + dependencies: + lru-cache: 11.2.1 + minipass: 7.1.2 + path-type@4.0.0: {} pend@1.2.0: {} @@ -4183,15 +4573,15 @@ snapshots: picomatch@2.3.1: {} - picomatch@4.0.2: {} + picomatch@4.0.3: {} possible-typed-array-names@1.1.0: {} postcss-resolve-nested-selector@0.1.6: {} - postcss-safe-parser@7.0.1(postcss@8.5.3): + postcss-safe-parser@7.0.1(postcss@8.5.6): dependencies: - postcss: 8.5.3 + postcss: 8.5.6 postcss-selector-parser@7.1.0: dependencies: @@ -4200,7 +4590,7 @@ snapshots: postcss-value-parser@4.2.0: {} - postcss@8.5.3: + postcss@8.5.6: dependencies: nanoid: 3.3.11 picocolors: 1.1.1 @@ -4219,7 +4609,7 @@ snapshots: proxy-agent@6.5.0: dependencies: agent-base: 7.1.3 - debug: 4.4.0 + debug: 4.4.3 http-proxy-agent: 7.0.2 https-proxy-agent: 7.0.6 lru-cache: 7.18.3 @@ -4238,14 +4628,15 @@ snapshots: punycode@2.3.1: {} - puppeteer-core@24.4.0: + puppeteer-core@24.30.0: dependencies: - '@puppeteer/browsers': 2.8.0 - chromium-bidi: 2.1.2(devtools-protocol@0.0.1413902) - debug: 4.4.0 - devtools-protocol: 0.0.1413902 + '@puppeteer/browsers': 2.10.13 + chromium-bidi: 11.0.0(devtools-protocol@0.0.1521046) + debug: 4.4.3 + devtools-protocol: 0.0.1521046 typed-query-selector: 2.12.0 - ws: 8.18.1 + webdriver-bidi-protocol: 0.3.8 + ws: 8.18.3 transitivePeerDependencies: - bare-buffer - bufferutil @@ -4254,6 +4645,10 @@ snapshots: q@1.5.1: {} + qified@0.5.2: + dependencies: + hookified: 1.13.0 + queue-microtask@1.2.3: {} react-is@16.13.1: {} @@ -4298,7 +4693,7 @@ snapshots: resolve@2.0.0-next.5: dependencies: - is-core-module: 2.15.0 + is-core-module: 2.16.1 path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 @@ -4331,6 +4726,10 @@ snapshots: semver@7.7.1: {} + semver@7.7.2: {} + + semver@7.7.3: {} + set-function-length@1.2.2: dependencies: define-data-property: 1.1.4 @@ -4402,7 +4801,7 @@ snapshots: socks-proxy-agent@8.0.5: dependencies: agent-base: 7.1.3 - debug: 4.4.0 + debug: 4.4.3 socks: 2.8.4 transitivePeerDependencies: - supports-color @@ -4412,8 +4811,6 @@ snapshots: ip-address: 9.0.5 smart-buffer: 4.2.0 - source-map-js@1.2.0: {} - source-map-js@1.2.1: {} source-map-support@0.5.21: @@ -4442,6 +4839,12 @@ snapshots: is-fullwidth-code-point: 3.0.0 strip-ansi: 6.0.1 + string-width@5.1.2: + dependencies: + eastasianwidth: 0.2.0 + emoji-regex: 9.2.2 + strip-ansi: 7.1.2 + string.prototype.matchall@4.0.12: dependencies: call-bind: 1.0.8 @@ -4482,7 +4885,7 @@ snapshots: string.prototype.trimstart@1.0.8: dependencies: - call-bind: 1.0.7 + call-bind: 1.0.8 define-properties: 1.2.1 es-object-atoms: 1.1.1 @@ -4490,51 +4893,55 @@ snapshots: dependencies: ansi-regex: 5.0.1 + strip-ansi@7.1.2: + dependencies: + ansi-regex: 6.2.2 + strip-bom@3.0.0: {} strip-json-comments@3.1.1: {} - stylelint-config-recommended@15.0.0(stylelint@16.17.0(typescript@5.8.2)): + stylelint-config-recommended@17.0.0(stylelint@16.25.0(typescript@5.9.3)): dependencies: - stylelint: 16.17.0(typescript@5.8.2) + stylelint: 16.25.0(typescript@5.9.3) - stylelint-config-standard@37.0.0(stylelint@16.17.0(typescript@5.8.2)): + stylelint-config-standard@39.0.1(stylelint@16.25.0(typescript@5.9.3)): dependencies: - stylelint: 16.17.0(typescript@5.8.2) - stylelint-config-recommended: 15.0.0(stylelint@16.17.0(typescript@5.8.2)) + stylelint: 16.25.0(typescript@5.9.3) + stylelint-config-recommended: 17.0.0(stylelint@16.25.0(typescript@5.9.3)) - stylelint@16.17.0(typescript@5.8.2): + stylelint@16.25.0(typescript@5.9.3): dependencies: - '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3) - '@csstools/css-tokenizer': 3.0.3 - '@csstools/media-query-list-parser': 4.0.2(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3) + '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) + '@csstools/css-tokenizer': 3.0.4 + '@csstools/media-query-list-parser': 4.0.3(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) '@csstools/selector-specificity': 5.0.0(postcss-selector-parser@7.1.0) - '@dual-bundle/import-meta-resolve': 4.1.0 + '@dual-bundle/import-meta-resolve': 4.2.1 balanced-match: 2.0.0 colord: 2.9.3 - cosmiconfig: 9.0.0(typescript@5.8.2) + cosmiconfig: 9.0.0(typescript@5.9.3) css-functions-list: 3.2.3 css-tree: 3.1.0 - debug: 4.4.0 + debug: 4.4.3 fast-glob: 3.3.3 fastest-levenshtein: 1.0.16 - file-entry-cache: 10.0.7 + file-entry-cache: 10.1.4 global-modules: 2.0.0 globby: 11.1.0 globjoin: 0.1.4 html-tags: 3.3.1 - ignore: 7.0.3 + ignore: 7.0.5 imurmurhash: 0.1.4 is-plain-object: 5.0.0 - known-css-properties: 0.35.0 + known-css-properties: 0.37.0 mathml-tag-names: 2.1.3 meow: 13.2.0 micromatch: 4.0.8 normalize-path: 3.0.0 picocolors: 1.1.1 - postcss: 8.5.3 + postcss: 8.5.6 postcss-resolve-nested-selector: 0.1.6 - postcss-safe-parser: 7.0.1(postcss@8.5.3) + postcss-safe-parser: 7.0.1(postcss@8.5.6) postcss-selector-parser: 7.1.0 postcss-value-parser: 4.2.0 resolve-from: 5.0.0 @@ -4572,7 +4979,9 @@ snapshots: string-width: 4.2.3 strip-ansi: 6.0.1 - tar-fs@3.0.8: + tagged-tag@1.0.0: {} + + tar-fs@3.1.1: dependencies: pump: 3.0.0 tar-stream: 3.1.7 @@ -4603,9 +5012,9 @@ snapshots: dependencies: is-number: 7.0.0 - ts-api-utils@2.1.0(typescript@5.8.2): + ts-api-utils@2.1.0(typescript@5.9.3): dependencies: - typescript: 5.8.2 + typescript: 5.9.3 ts-patch@3.3.0: dependencies: @@ -4636,6 +5045,13 @@ snapshots: optionalDependencies: fsevents: 2.3.3 + tsx@4.20.6: + dependencies: + esbuild: 0.25.1 + get-tsconfig: 4.10.0 + optionalDependencies: + fsevents: 2.3.3 + type-check@0.4.0: dependencies: prelude-ls: 1.2.1 @@ -4644,6 +5060,10 @@ snapshots: type-fest@4.41.0: {} + type-fest@5.2.0: + dependencies: + tagged-tag: 1.0.0 + typed-array-buffer@1.0.3: dependencies: call-bound: 1.0.4 @@ -4679,22 +5099,23 @@ snapshots: typed-query-selector@2.12.0: {} - typescript-eslint@8.28.0(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215))(typescript@5.8.2): + typescript-eslint@8.47.0(eslint@9.39.1)(typescript@5.9.3): dependencies: - '@typescript-eslint/eslint-plugin': 8.28.0(@typescript-eslint/parser@8.28.0(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215))(typescript@5.8.2))(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215))(typescript@5.8.2) - '@typescript-eslint/parser': 8.28.0(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215))(typescript@5.8.2) - '@typescript-eslint/utils': 8.28.0(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215))(typescript@5.8.2) - eslint: 9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215) - typescript: 5.8.2 + '@typescript-eslint/eslint-plugin': 8.47.0(@typescript-eslint/parser@8.47.0(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1)(typescript@5.9.3) + '@typescript-eslint/parser': 8.47.0(eslint@9.39.1)(typescript@5.9.3) + '@typescript-eslint/typescript-estree': 8.47.0(typescript@5.9.3) + '@typescript-eslint/utils': 8.47.0(eslint@9.39.1)(typescript@5.9.3) + eslint: 9.39.1 + typescript: 5.9.3 transitivePeerDependencies: - supports-color - typescript-transform-paths@3.5.5(typescript@5.8.2): + typescript-transform-paths@3.5.5(typescript@5.9.3): dependencies: minimatch: 9.0.5 - typescript: 5.8.2 + typescript: 5.9.3 - typescript@5.8.2: {} + typescript@5.9.3: {} unbox-primitive@1.1.0: dependencies: @@ -4705,6 +5126,8 @@ snapshots: undici-types@6.20.0: {} + undici-types@7.16.0: {} + universalify@2.0.1: {} uri-js@4.4.1: @@ -4721,6 +5144,8 @@ snapshots: vscode-textmate@5.2.0: {} + webdriver-bidi-protocol@0.3.8: {} + which-boxed-primitive@1.1.1: dependencies: is-bigint: 1.1.0 @@ -4782,6 +5207,12 @@ snapshots: string-width: 4.2.3 strip-ansi: 6.0.1 + wrap-ansi@8.1.0: + dependencies: + ansi-styles: 6.2.3 + string-width: 5.1.2 + strip-ansi: 7.1.2 + wrappy@1.0.2: {} write-file-atomic@5.0.1: @@ -4789,7 +5220,7 @@ snapshots: imurmurhash: 0.1.4 signal-exit: 4.1.0 - ws@8.18.1: {} + ws@8.18.3: {} y18n@5.0.8: {} diff --git a/scripts/build/build.mjs b/scripts/build/build.mjs index 925cfd952..748553645 100644 --- a/scripts/build/build.mjs +++ b/scripts/build/build.mjs @@ -21,10 +21,10 @@ import { createPackage } from "@electron/asar"; import { readdir, writeFile } from "fs/promises"; -import { dirname, join } from "path"; +import { dirname, join, resolve } from "path"; import { fileURLToPath } from "url"; -import { BUILD_TIMESTAMP, commonOpts, exists, globPlugins, IS_DEV, IS_REPORTER, IS_COMPANION_TEST, IS_STANDALONE, IS_UPDATER_DISABLED, resolvePluginName, VERSION, commonRendererPlugins, watch, buildOrWatchAll, stringifyValues } from "./common.mjs"; +import { BUILD_TIMESTAMP, commonOpts, exists, globPlugins, IS_DEV, IS_REPORTER, IS_COMPANION_TEST, IS_STANDALONE, IS_UPDATER_DISABLED, resolvePluginName, VERSION, commonRendererPlugins, watch, buildOrWatchAll, stringifyValues, IS_ANTI_CRASH_TEST } from "./common.mjs"; const defines = stringifyValues({ IS_STANDALONE, @@ -32,6 +32,7 @@ const defines = stringifyValues({ IS_REPORTER, IS_COMPANION_TEST, IS_UPDATER_DISABLED, + IS_ANTI_CRASH_TEST, IS_WEB: false, IS_EXTENSION: false, IS_USERSCRIPT: false, @@ -80,6 +81,10 @@ const globNativesPlugin = { let code = ""; let natives = "\n"; let i = 0; + /** + * @type {string[]} + */ + const watchFiles = []; for (const dir of pluginDirs) { const dirPath = join("src", dir); if (!await exists(dirPath)) continue; @@ -89,6 +94,8 @@ const globNativesPlugin = { const nativePath = join(dirPath, fileName, "native.ts"); const indexNativePath = join(dirPath, fileName, "native/index.ts"); + watchFiles.push(resolve(nativePath), resolve(indexNativePath)); + if (!(await exists(nativePath)) && !(await exists(indexNativePath))) continue; @@ -103,7 +110,9 @@ const globNativesPlugin = { code += `export default {${natives}};`; return { contents: code, - resolveDir: "./src" + resolveDir: "./src", + watchDirs: pluginDirs.map(d => resolve("src", d)), + watchFiles, }; }); } diff --git a/scripts/build/buildWeb.mjs b/scripts/build/buildWeb.mjs index 0436c3094..7d7d5518e 100644 --- a/scripts/build/buildWeb.mjs +++ b/scripts/build/buildWeb.mjs @@ -21,10 +21,10 @@ import { readFileSync } from "fs"; import { appendFile, mkdir, readdir, readFile, rm, writeFile } from "fs/promises"; -import { join } from "path"; -import Zip from "zip-local"; +import path, { join } from "path"; -import { BUILD_TIMESTAMP, commonOpts, globPlugins, IS_DEV, IS_REPORTER, IS_COMPANION_TEST, VERSION, commonRendererPlugins, buildOrWatchAll, stringifyValues } from "./common.mjs"; +import { BUILD_TIMESTAMP, commonOpts, globPlugins, IS_DEV, IS_REPORTER, IS_COMPANION_TEST, VERSION, commonRendererPlugins, buildOrWatchAll, stringifyValues, IS_ANTI_CRASH_TEST } from "./common.mjs"; +import AdmZip from "adm-zip"; /** * @type {import("esbuild").BuildOptions} @@ -48,6 +48,7 @@ const commonOptions = { IS_DEV, IS_REPORTER, IS_COMPANION_TEST, + IS_ANTI_CRASH_TEST, IS_DISCORD_DESKTOP: false, IS_VESKTOP: false, IS_EQUIBOP: false, @@ -104,7 +105,7 @@ const buildConfigs = [ IS_USERSCRIPT: "true", window: "unsafeWindow", }, - outfile: "dist/Vencord.user.js", + outfile: "dist/Equicord.user.js", banner: { js: readFileSync("browser/userscript.meta.js", "utf-8").replace("%version%", `${VERSION}.${new Date().getTime()}`) }, @@ -154,8 +155,8 @@ async function loadDir(dir, basePath = "") { */ async function buildExtension(target, files) { const entries = { - "dist/Vencord.js": await readFile("dist/browser/extension.js"), - "dist/Vencord.css": await readFile("dist/browser/extension.css"), + "dist/Equicord.js": await readFile("dist/browser/extension.js"), + "dist/Equicord.css": await readFile("dist/browser/extension.css"), ...await loadDir("dist/browser/vendor/monaco", "dist/browser/"), ...Object.fromEntries(await Promise.all(files.map(async f => { let content = await readFile(join("browser", f)); @@ -183,9 +184,9 @@ async function buildExtension(target, files) { console.info("Unpacked Extension written to dist/browser/" + target); } -const appendCssRuntime = readFile("dist/Vencord.user.css", "utf-8").then(content => { +const appendCssRuntime = readFile("dist/Equicord.user.css", "utf-8").then(content => { const cssRuntime = ` -;document.addEventListener("DOMContentLoaded", () => document.documentElement.appendChild( +;document.addEventListener("DOMContentLoaded", () => document.body.insertAdjacentElement("afterend", Object.assign(document.createElement("style"), { textContent: \`${content.replaceAll("`", "\\`")}\`, id: "vencord-css-core" @@ -193,7 +194,7 @@ const appendCssRuntime = readFile("dist/Vencord.user.css", "utf-8").then(content ), { once: true }); `; - return appendFile("dist/Vencord.user.js", cssRuntime); + return appendFile("dist/Equicord.user.js", cssRuntime); }); if (!process.argv.includes("--skip-extension")) { @@ -203,14 +204,15 @@ if (!process.argv.includes("--skip-extension")) { buildExtension("firefox-unpacked", ["background.js", "content.js", "manifestv2.json", "icon.png"]), ]); - Zip.zip("dist/browser/chromium-unpacked", (_err, zip) => { - zip.compress().save("dist/extension-chrome.zip"); - console.info("Packed Chromium Extension written to dist/extension-chrome.zip"); - }); - Zip.zip("dist/browser/firefox-unpacked", (_err, zip) => { - zip.compress().save("dist/extension-firefox.zip"); - console.info("Packed Firefox Extension written to dist/extension-firefox.zip"); - }); + const chromiumZip = new AdmZip(); + chromiumZip.addLocalFolder("dist/browser/chromium-unpacked"); + chromiumZip.writeZip(path.resolve("dist/extension-chrome.zip")); + console.info("Packed Chromium Extension written to dist/extension-chrome.zip"); + + const firefoxZip = new AdmZip(); + firefoxZip.addLocalFolder("dist/browser/firefox-unpacked"); + firefoxZip.writeZip(path.resolve("dist/extension-firefox.zip")); + console.info("Packed Firefox Extension written to dist/extension-firefox.zip"); } else { await appendCssRuntime; } diff --git a/scripts/build/common.mjs b/scripts/build/common.mjs index 87540a6e8..da858e2ec 100644 --- a/scripts/build/common.mjs +++ b/scripts/build/common.mjs @@ -26,7 +26,7 @@ import esbuild, { build, context } from "esbuild"; import { constants as FsConstants, readFileSync } from "fs"; import { access, readdir, readFile } from "fs/promises"; import { minify as minifyHtml } from "html-minifier-terser"; -import { dirname, join, relative } from "path"; +import { dirname, join, relative, resolve } from "path"; import { fileURLToPath } from "url"; import { promisify } from "util"; @@ -42,13 +42,14 @@ export const BUILD_TIMESTAMP = Number(process.env.SOURCE_DATE_EPOCH) || Date.now export const watch = process.argv.includes("--watch"); export const IS_DEV = watch || process.argv.includes("--dev"); export const IS_REPORTER = process.argv.includes("--reporter"); +export const IS_ANTI_CRASH_TEST = process.argv.includes("--anti-crash-test"); export const IS_STANDALONE = process.argv.includes("--standalone"); export const IS_COMPANION_TEST = IS_REPORTER && process.argv.includes("--companion-test"); if (!IS_COMPANION_TEST && process.argv.includes("--companion-test")) console.error("--companion-test must be run with --reporter for any effect"); export const IS_UPDATER_DISABLED = process.argv.includes("--disable-updater"); -export const gitHash = process.env.EQUICORD_HASH || execSync("git rev-parse --short=9 HEAD", { encoding: "utf-8" }).trim(); +export const gitHash = process.env.EQUICORD_HASH || execSync("git rev-parse HEAD", { encoding: "utf-8" }).trim(); export const banner = { js: ` @@ -146,7 +147,7 @@ export const globPlugins = kind => ({ }); build.onLoad({ filter, namespace: "import-plugins" }, async () => { - const pluginDirs = ["plugins/_api", "plugins/_core", "plugins", "userplugins", "equicordplugins"]; + const pluginDirs = ["plugins/_api", "plugins/_core", "plugins", "userplugins", "equicordplugins", "equicordplugins/_api"]; let code = ""; let pluginsCode = "\n"; let metaCode = "\n"; @@ -162,6 +163,7 @@ export const globPlugins = kind => ({ const fileName = file.name; if (fileName.startsWith("_") || fileName.startsWith(".")) continue; if (fileName === "index.ts") continue; + if (/\.(zip|rar|7z|tar|gz|bz2)/.test(fileName)) continue; const target = getPluginTarget(fileName); @@ -193,7 +195,8 @@ export const globPlugins = kind => ({ code += `export default {${pluginsCode}};export const PluginMeta={${metaCode}};export const ExcludedPlugins={${excludedCode}};`; return { contents: code, - resolveDir: "./src" + resolveDir: "./src", + watchDirs: pluginDirs.map(d => resolve("src", d)), }; }); } diff --git a/scripts/generateDevsList.ts b/scripts/generateDevsList.ts new file mode 100644 index 000000000..e810d66d2 --- /dev/null +++ b/scripts/generateDevsList.ts @@ -0,0 +1,121 @@ +/* + * Vencord, a modification for Discord's desktop app + * Copyright (c) 2023 Vendicated and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . +*/ + +import { readFileSync, writeFileSync } from "fs"; +import { BigIntLiteral, createSourceFile, Identifier, isCallExpression, isIdentifier, isObjectLiteralExpression, isPropertyAssignment, isSatisfiesExpression, isVariableStatement, NamedDeclaration, ObjectLiteralExpression, ScriptTarget, StringLiteral } from "typescript"; + +interface Dev { + name: string; + id: string; +} + +const devs = {} as Record; +const equicordDevs = {} as Record; + +function getName(node: NamedDeclaration) { + return node.name && isIdentifier(node.name) ? node.name.text : undefined; +} + +function hasName(node: NamedDeclaration, name: string) { + return getName(node) === name; +} + +function getObjectProp(node: ObjectLiteralExpression, name: string) { + const prop = node.properties.find(p => hasName(p, name)); + if (prop && isPropertyAssignment(prop)) return prop.initializer; + return prop; +} + +function parseDevs() { + const file = createSourceFile("constants.ts", readFileSync("src/utils/constants.ts", "utf8"), ScriptTarget.Latest); + + for (const child of file.getChildAt(0).getChildren()) { + if (!isVariableStatement(child)) continue; + + const devsDeclaration = child.declarationList.declarations.find(d => hasName(d, "Devs")); + if (!devsDeclaration?.initializer || !isCallExpression(devsDeclaration.initializer)) continue; + + const value = devsDeclaration.initializer.arguments[0]; + + if (!isSatisfiesExpression(value) || !isObjectLiteralExpression(value.expression)) throw new Error("Failed to parse devs: not an object literal"); + + for (const prop of value.expression.properties) { + const name = (prop.name as Identifier).text; + const value = isPropertyAssignment(prop) ? prop.initializer : prop; + + if (!isObjectLiteralExpression(value)) throw new Error(`Failed to parse devs: ${name} is not an object literal`); + + devs[name] = { + name: (getObjectProp(value, "name") as StringLiteral).text, + id: (getObjectProp(value, "id") as BigIntLiteral).text.slice(0, -1) + }; + } + + return; + } + + throw new Error("Could not find Devs constant"); +} + +function parseEquicordDevs() { + const file = createSourceFile("constants.ts", readFileSync("src/utils/constants.ts", "utf8"), ScriptTarget.Latest); + + for (const child of file.getChildAt(0).getChildren()) { + if (!isVariableStatement(child)) continue; + + const devsDeclaration = child.declarationList.declarations.find(d => hasName(d, "EquicordDevs")); + if (!devsDeclaration?.initializer || !isCallExpression(devsDeclaration.initializer)) continue; + + const value = devsDeclaration.initializer.arguments[0]; + + if (!isSatisfiesExpression(value) || !isObjectLiteralExpression(value.expression)) throw new Error("Failed to parse EquicordDevs: not an object literal"); + + for (const prop of value.expression.properties) { + const name = (prop.name as Identifier).text; + const value = isPropertyAssignment(prop) ? prop.initializer : prop; + + if (!isObjectLiteralExpression(value)) throw new Error(`Failed to parse EquicordDevs: ${name} is not an object literal`); + + equicordDevs[name] = { + name: (getObjectProp(value, "name") as StringLiteral).text, + id: (getObjectProp(value, "id") as BigIntLiteral).text.slice(0, -1) + }; + } + + return; + } + + throw new Error("Could not find EquicordDevs constant"); +} + +(async () => { + parseDevs(); + parseEquicordDevs(); + + const allDevs = { + vencord: devs, + equicord: equicordDevs, + }; + + const data = JSON.stringify(allDevs, null, 2); + if (process.argv.length > 2) { + writeFileSync(process.argv[2], data); + } else { + console.log(data); + } +})(); diff --git a/scripts/generateEquicordPluginList.ts b/scripts/generateEquicordPluginList.ts deleted file mode 100644 index 5b551a2a7..000000000 --- a/scripts/generateEquicordPluginList.ts +++ /dev/null @@ -1,283 +0,0 @@ -/* - * Vencord, a modification for Discord's desktop app - * Copyright (c) 2023 Vendicated and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . -*/ - -import { Dirent, readdirSync, readFileSync, writeFileSync } from "fs"; -import { access, readFile } from "fs/promises"; -import { join, sep } from "path"; -import { normalize as posixNormalize, sep as posixSep } from "path/posix"; -import { BigIntLiteral, createSourceFile, Identifier, isArrayLiteralExpression, isCallExpression, isExportAssignment, isIdentifier, isObjectLiteralExpression, isPropertyAccessExpression, isPropertyAssignment, isSatisfiesExpression, isStringLiteral, isVariableStatement, NamedDeclaration, NodeArray, ObjectLiteralExpression, PropertyAssignment, ScriptTarget, StringLiteral, SyntaxKind } from "typescript"; - -import { getPluginTarget } from "./utils.mjs"; - -interface Dev { - name: string; - id: string; -} - -interface Command { - name: string; - description: string; -} - -interface PluginData { - name: string; - description: string; - tags: string[]; - authors: Dev[]; - dependencies: string[]; - hasPatches: boolean; - hasCommands: boolean; - commands: Command[]; - required: boolean; - enabledByDefault: boolean; - target: "discordDesktop" | "vesktop" | "equibop" | "desktop" | "web" | "dev"; - filePath: string; -} - -const devs = {} as Record; -const equicordDevs = {} as Record; - -function getName(node: NamedDeclaration) { - return node.name && isIdentifier(node.name) ? node.name.text : undefined; -} - -function hasName(node: NamedDeclaration, name: string) { - return getName(node) === name; -} - -function getObjectProp(node: ObjectLiteralExpression, name: string) { - const prop = node.properties.find(p => hasName(p, name)); - if (prop && isPropertyAssignment(prop)) return prop.initializer; - return prop; -} - -function parseDevs() { - const file = createSourceFile("constants.ts", readFileSync("src/utils/constants.ts", "utf8"), ScriptTarget.Latest); - - for (const child of file.getChildAt(0).getChildren()) { - if (!isVariableStatement(child)) continue; - - const devsDeclaration = child.declarationList.declarations.find(d => hasName(d, "Devs")); - if (!devsDeclaration?.initializer || !isCallExpression(devsDeclaration.initializer)) continue; - - const value = devsDeclaration.initializer.arguments[0]; - - if (!isSatisfiesExpression(value) || !isObjectLiteralExpression(value.expression)) throw new Error("Failed to parse devs: not an object literal"); - - for (const prop of value.expression.properties) { - const name = (prop.name as Identifier).text; - const value = isPropertyAssignment(prop) ? prop.initializer : prop; - - if (!isObjectLiteralExpression(value)) throw new Error(`Failed to parse devs: ${name} is not an object literal`); - - devs[name] = { - name: (getObjectProp(value, "name") as StringLiteral).text, - id: (getObjectProp(value, "id") as BigIntLiteral).text.slice(0, -1) - }; - } - - return; - } - - throw new Error("Could not find Devs constant"); -} - -function parseEquicordDevs() { - const file = createSourceFile("constants.ts", readFileSync("src/utils/constants.ts", "utf8"), ScriptTarget.Latest); - - for (const child of file.getChildAt(0).getChildren()) { - if (!isVariableStatement(child)) continue; - - const devsDeclaration = child.declarationList.declarations.find(d => hasName(d, "EquicordDevs")); - if (!devsDeclaration?.initializer || !isCallExpression(devsDeclaration.initializer)) continue; - - const value = devsDeclaration.initializer.arguments[0]; - - if (!isSatisfiesExpression(value) || !isObjectLiteralExpression(value.expression)) throw new Error("Failed to parse EquicordDevs: not an object literal"); - - for (const prop of value.expression.properties) { - const name = (prop.name as Identifier).text; - const value = isPropertyAssignment(prop) ? prop.initializer : prop; - - if (!isObjectLiteralExpression(value)) throw new Error(`Failed to parse EquicordDevs: ${name} is not an object literal`); - - equicordDevs[name] = { - name: (getObjectProp(value, "name") as StringLiteral).text, - id: (getObjectProp(value, "id") as BigIntLiteral).text.slice(0, -1) - }; - } - - return; - } - - throw new Error("Could not find EquicordDevs constant"); -} - -async function parseFile(fileName: string) { - const file = createSourceFile(fileName, await readFile(fileName, "utf8"), ScriptTarget.Latest); - - const fail = (reason: string) => { - return new Error(`Invalid plugin ${fileName}, because ${reason}`); - }; - - for (const node of file.getChildAt(0).getChildren()) { - if (!isExportAssignment(node) || !isCallExpression(node.expression)) continue; - - const call = node.expression; - if (!isIdentifier(call.expression) || call.expression.text !== "definePlugin") continue; - - const pluginObj = node.expression.arguments[0]; - if (!isObjectLiteralExpression(pluginObj)) throw fail("no object literal passed to definePlugin"); - - const data = { - hasPatches: false, - hasCommands: false, - enabledByDefault: false, - required: false, - tags: [] as string[] - } as PluginData; - - for (const prop of pluginObj.properties) { - const key = getName(prop); - const value = isPropertyAssignment(prop) ? prop.initializer : prop; - - switch (key) { - case "name": - case "description": - if (!isStringLiteral(value)) throw fail(`${key} is not a string literal`); - data[key] = value.text; - break; - case "patches": - data.hasPatches = true; - break; - case "commands": - data.hasCommands = true; - if (!isArrayLiteralExpression(value)) throw fail("commands is not an array literal"); - data.commands = value.elements.map((e) => { - if (isObjectLiteralExpression(e)) { - const nameProperty = e.properties.find((p): p is PropertyAssignment => { - return isPropertyAssignment(p) && isIdentifier(p.name) && p.name.escapedText === 'name'; - }); - const descriptionProperty = e.properties.find((p): p is PropertyAssignment => { - return isPropertyAssignment(p) && isIdentifier(p.name) && p.name.escapedText === 'description'; - }); - if (!nameProperty || !descriptionProperty) throw fail("command missing required properties"); - const name = isStringLiteral(nameProperty.initializer) ? nameProperty.initializer.text : ''; - const description = isStringLiteral(descriptionProperty.initializer) ? descriptionProperty.initializer.text : ''; - return { name, description }; - } else if (isCallExpression(e) && isIdentifier(e.expression)) { - const [nameArg] = e.arguments; - if (!isStringLiteral(nameArg)) throw fail("first argument must be a string"); - const name = nameArg.text; - return { name, description: "" }; - } - throw fail("commands array contains invalid elements"); - }); - break; - case "authors": - if (!isArrayLiteralExpression(value)) throw fail("authors is not an array literal"); - data.authors = value.elements.map(e => { - if (!isPropertyAccessExpression(e)) throw fail("authors array contains non-property access expressions"); - const d = devs[getName(e)!] || equicordDevs[getName(e)!]; - if (!d) throw fail(`couldn't look up author ${getName(e)}`); - return d; - }); - break; - case "tags": - if (!isArrayLiteralExpression(value)) throw fail("tags is not an array literal"); - data.tags = value.elements.map(e => { - if (!isStringLiteral(e)) throw fail("tags array contains non-string literals"); - return e.text; - }); - break; - case "dependencies": - if (!isArrayLiteralExpression(value)) throw fail("dependencies is not an array literal"); - const { elements } = value; - if (elements.some(e => !isStringLiteral(e))) throw fail("dependencies array contains non-string elements"); - data.dependencies = (elements as NodeArray).map(e => e.text); - break; - case "required": - case "enabledByDefault": - data[key] = value.kind === SyntaxKind.TrueKeyword; - break; - } - } - - if (!data.name || !data.description || !data.authors) throw fail("name, description or authors are missing"); - - const target = getPluginTarget(fileName); - if (target) { - if (!["web", "discordDesktop", "vencordDesktop", "equibop", "desktop", "dev"].includes(target)) throw fail(`invalid target ${target}`); - data.target = target as any; - } - - data.filePath = posixNormalize(fileName) - .split(sep) - .join(posixSep) - .replace(/\/index\.([jt]sx?)$/, "") - .replace(/^src\/equicordplugins\//, ""); - - return [data] as const; - } - - throw fail("no default export called 'definePlugin' found"); -} - -async function getEntryPoint(dir: string, dirent: Dirent) { - const base = join(dir, dirent.name); - if (!dirent.isDirectory()) return base; - - for (const name of ["index.ts", "index.tsx"]) { - const full = join(base, name); - try { - await access(full); - return full; - } catch { } - } - - throw new Error(`${dirent.name}: Couldn't find entry point`); -} - -function isPluginFile({ name }: { name: string; }) { - if (name === "index.ts") return false; - return !name.startsWith("_") && !name.startsWith("."); -} - -(async () => { - parseDevs(); - parseEquicordDevs(); - - const plugins = [] as PluginData[]; - - await Promise.all(["src/equicordplugins"].flatMap(dir => - readdirSync(dir, { withFileTypes: true }) - .filter(isPluginFile) - .map(async dirent => { - const [data] = await parseFile(await getEntryPoint(dir, dirent)); - plugins.sort().push(data); - }) - )); - - const data = JSON.stringify(plugins); - - if (process.argv.length > 2) { - writeFileSync(process.argv[2], data); - } else { - console.log(data); - } -})(); diff --git a/scripts/generatePluginList.ts b/scripts/generatePluginList.ts index facefa711..76a03bd60 100644 --- a/scripts/generatePluginList.ts +++ b/scripts/generatePluginList.ts @@ -16,266 +16,47 @@ * along with this program. If not, see . */ -import { Dirent, readdirSync, readFileSync, writeFileSync } from "fs"; -import { access, readFile } from "fs/promises"; -import { join, sep } from "path"; -import { normalize as posixNormalize, sep as posixSep } from "path/posix"; -import { BigIntLiteral, createSourceFile, Identifier, isArrayLiteralExpression, isCallExpression, isExportAssignment, isIdentifier, isObjectLiteralExpression, isPropertyAccessExpression, isPropertyAssignment, isSatisfiesExpression, isStringLiteral, isVariableStatement, NamedDeclaration, NodeArray, ObjectLiteralExpression, PropertyAssignment, ScriptTarget, StringLiteral, SyntaxKind } from "typescript"; - -import { getPluginTarget } from "./utils.mjs"; - -interface Dev { - name: string; - id: string; -} - -interface Command { - name: string; - description: string; -} - -interface PluginData { - name: string; - description: string; - tags: string[]; - authors: Dev[]; - dependencies: string[]; - hasPatches: boolean; - hasCommands: boolean; - commands: Command[]; - required: boolean; - enabledByDefault: boolean; - target: "discordDesktop" | "vesktop" | "equibop" | "desktop" | "web" | "dev"; - filePath: string; -} - -const devs = {} as Record; -const equicordDevs = {} as Record; - -function getName(node: NamedDeclaration) { - return node.name && isIdentifier(node.name) ? node.name.text : undefined; -} - -function hasName(node: NamedDeclaration, name: string) { - return getName(node) === name; -} - -function getObjectProp(node: ObjectLiteralExpression, name: string) { - const prop = node.properties.find(p => hasName(p, name)); - if (prop && isPropertyAssignment(prop)) return prop.initializer; - return prop; -} - -function parseDevs() { - const file = createSourceFile("constants.ts", readFileSync("src/utils/constants.ts", "utf8"), ScriptTarget.Latest); - - for (const child of file.getChildAt(0).getChildren()) { - if (!isVariableStatement(child)) continue; - - const devsDeclaration = child.declarationList.declarations.find(d => hasName(d, "Devs")); - if (!devsDeclaration?.initializer || !isCallExpression(devsDeclaration.initializer)) continue; - - const value = devsDeclaration.initializer.arguments[0]; - - if (!isSatisfiesExpression(value) || !isObjectLiteralExpression(value.expression)) throw new Error("Failed to parse devs: not an object literal"); - - for (const prop of value.expression.properties) { - const name = (prop.name as Identifier).text; - const value = isPropertyAssignment(prop) ? prop.initializer : prop; - - if (!isObjectLiteralExpression(value)) throw new Error(`Failed to parse devs: ${name} is not an object literal`); - - devs[name] = { - name: (getObjectProp(value, "name") as StringLiteral).text, - id: (getObjectProp(value, "id") as BigIntLiteral).text.slice(0, -1) - }; - } - - return; - } - - throw new Error("Could not find Devs constant"); -} - -function parseEquicordDevs() { - const file = createSourceFile("constants.ts", readFileSync("src/utils/constants.ts", "utf8"), ScriptTarget.Latest); - - for (const child of file.getChildAt(0).getChildren()) { - if (!isVariableStatement(child)) continue; - - const devsDeclaration = child.declarationList.declarations.find(d => hasName(d, "EquicordDevs")); - if (!devsDeclaration?.initializer || !isCallExpression(devsDeclaration.initializer)) continue; - - const value = devsDeclaration.initializer.arguments[0]; - - if (!isSatisfiesExpression(value) || !isObjectLiteralExpression(value.expression)) throw new Error("Failed to parse EquicordDevs: not an object literal"); - - for (const prop of value.expression.properties) { - const name = (prop.name as Identifier).text; - const value = isPropertyAssignment(prop) ? prop.initializer : prop; - - if (!isObjectLiteralExpression(value)) throw new Error(`Failed to parse EquicordDevs: ${name} is not an object literal`); - - equicordDevs[name] = { - name: (getObjectProp(value, "name") as StringLiteral).text, - id: (getObjectProp(value, "id") as BigIntLiteral).text.slice(0, -1) - }; - } - - return; - } - - throw new Error("Could not find EquicordDevs constant"); -} - -async function parseFile(fileName: string) { - const file = createSourceFile(fileName, await readFile(fileName, "utf8"), ScriptTarget.Latest); - - const fail = (reason: string) => { - return new Error(`Invalid plugin ${fileName}, because ${reason}`); - }; - - for (const node of file.getChildAt(0).getChildren()) { - if (!isExportAssignment(node) || !isCallExpression(node.expression)) continue; - - const call = node.expression; - if (!isIdentifier(call.expression) || call.expression.text !== "definePlugin") continue; - - const pluginObj = node.expression.arguments[0]; - if (!isObjectLiteralExpression(pluginObj)) throw fail("no object literal passed to definePlugin"); - - const data = { - hasPatches: false, - hasCommands: false, - enabledByDefault: false, - required: false, - tags: [] as string[] - } as PluginData; - - for (const prop of pluginObj.properties) { - const key = getName(prop); - const value = isPropertyAssignment(prop) ? prop.initializer : prop; - - switch (key) { - case "name": - case "description": - if (!isStringLiteral(value)) throw fail(`${key} is not a string literal`); - data[key] = value.text; - break; - case "patches": - data.hasPatches = true; - break; - case "commands": - data.hasCommands = true; - if (!isArrayLiteralExpression(value)) throw fail("commands is not an array literal"); - data.commands = value.elements.map((e) => { - if (isObjectLiteralExpression(e)) { - const nameProperty = e.properties.find((p): p is PropertyAssignment => { - return isPropertyAssignment(p) && isIdentifier(p.name) && p.name.escapedText === 'name'; - }); - const descriptionProperty = e.properties.find((p): p is PropertyAssignment => { - return isPropertyAssignment(p) && isIdentifier(p.name) && p.name.escapedText === 'description'; - }); - if (!nameProperty || !descriptionProperty) throw fail("command missing required properties"); - const name = isStringLiteral(nameProperty.initializer) ? nameProperty.initializer.text : ''; - const description = isStringLiteral(descriptionProperty.initializer) ? descriptionProperty.initializer.text : ''; - return { name, description }; - } else if (isCallExpression(e) && isIdentifier(e.expression)) { - const [nameArg] = e.arguments; - if (!isStringLiteral(nameArg)) throw fail("first argument must be a string"); - const name = nameArg.text; - return { name, description: "" }; - } - throw fail("commands array contains invalid elements"); - }); - break; - case "authors": - if (!isArrayLiteralExpression(value)) throw fail("authors is not an array literal"); - data.authors = value.elements.map(e => { - if (!isPropertyAccessExpression(e)) throw fail("authors array contains non-property access expressions"); - const d = devs[getName(e)!] || equicordDevs[getName(e)!]; - if (!d) throw fail(`couldn't look up author ${getName(e)}`); - return d; - }); - break; - case "tags": - if (!isArrayLiteralExpression(value)) throw fail("tags is not an array literal"); - data.tags = value.elements.map(e => { - if (!isStringLiteral(e)) throw fail("tags array contains non-string literals"); - return e.text; - }); - break; - case "dependencies": - if (!isArrayLiteralExpression(value)) throw fail("dependencies is not an array literal"); - const { elements } = value; - if (elements.some(e => !isStringLiteral(e))) throw fail("dependencies array contains non-string elements"); - data.dependencies = (elements as NodeArray).map(e => e.text); - break; - case "required": - case "enabledByDefault": - data[key] = value.kind === SyntaxKind.TrueKeyword; - break; - } - } - - if (!data.name || !data.description || !data.authors) throw fail("name, description or authors are missing"); - - const target = getPluginTarget(fileName); - if (target) { - if (!["web", "discordDesktop", "vesktop", "equibop", "desktop", "dev"].includes(target)) throw fail(`invalid target ${target}`); - data.target = target as any; - } - - data.filePath = posixNormalize(fileName) - .split(sep) - .join(posixSep) - .replace(/\/index\.([jt]sx?)$/, ""); - - return [data] as const; - } - - throw fail("no default export called 'definePlugin' found"); -} - -async function getEntryPoint(dir: string, dirent: Dirent) { - const base = join(dir, dirent.name); - if (!dirent.isDirectory()) return base; - - for (const name of ["index.ts", "index.tsx"]) { - const full = join(base, name); - try { - await access(full); - return full; - } catch { } - } - - throw new Error(`${dirent.name}: Couldn't find entry point`); -} - -function isPluginFile({ name }: { name: string; }) { - if (name === "index.ts") return false; - return !name.startsWith("_") && !name.startsWith("."); -} +import { readdirSync, writeFileSync } from "fs"; +import { getEntryPoint, isPluginFile, parseDevs, parseEquicordDevs, parseFile, PluginData } from "./utils"; (async () => { parseDevs(); parseEquicordDevs(); + const args = process.argv.slice(2); + + const equicordFlag = args.includes("--equicord"); + const vencordFlag = args.includes("--vencord"); + + let dirs: string[]; + + if (equicordFlag) { + dirs = ["src/equicordplugins"]; + } else if (vencordFlag) { + dirs = ["src/plugins", "src/plugins/_core"]; + } else { + dirs = ["src/plugins", "src/plugins/_core", "src/equicordplugins"]; + } + + const outputPath = args.find(a => !a.startsWith("--")) ?? null; + const plugins = [] as PluginData[]; - await Promise.all(["src/plugins", "src/plugins/_core", "src/equicordplugins"].flatMap(dir => - readdirSync(dir, { withFileTypes: true }) - .filter(isPluginFile) - .map(async dirent => { - const [data] = await parseFile(await getEntryPoint(dir, dirent)); - plugins.sort().push(data); - }) - )); + await Promise.all( + dirs.flatMap(dir => + readdirSync(dir, { withFileTypes: true }) + .filter(isPluginFile) + .map(async dirent => { + const [data] = await parseFile(await getEntryPoint(dir, dirent)); + plugins.sort().push(data); + }) + ) + ); const data = JSON.stringify(plugins); - if (process.argv.length > 2) { - writeFileSync(process.argv[2], data); + if (outputPath) { + writeFileSync(outputPath, data); } else { console.log(data); } diff --git a/scripts/generateReport.ts b/scripts/generateReport.ts index 1e54aa161..d6373e99f 100644 --- a/scripts/generateReport.ts +++ b/scripts/generateReport.ts @@ -87,14 +87,17 @@ const IGNORED_DISCORD_ERRORS = [ function toCodeBlock(s: string, indentation = 0, isDiscord = false) { s = s.replace(/```/g, "`\u200B`\u200B`"); - const indentationStr = Array(!isDiscord ? indentation : 0).fill(" ").join(""); - return `\`\`\`\n${s.split("\n").map(s => indentationStr + s).join("\n")}\n${indentationStr}\`\`\``; + s = s.replace(/\\x([0-9A-Fa-f]{2})/g, (_, hex) => + String.fromCharCode(parseInt(hex, 16)) + ); + + const indentationStr = " ".repeat(!isDiscord ? indentation : 0); + const content = s.split("\n").map(s => indentationStr + s).join("\n"); + + return `\`\`\`\n${content}\n${indentationStr}\`\`\``; } async function printReport() { - const filePath = join("dist", (CANARY ? "reporter-canary.json" : "reporter-stable.json")); - writeFileSync(filePath, JSON.stringify(report, null, 2)); - console.log(); console.log("# Equicord Report" + (CANARY ? " (Canary)" : "")); @@ -343,7 +346,7 @@ page.on("console", async e => { }); page.on("error", e => logStderr("[Error]", e.message)); -page.on("pageerror", e => { +page.on("pageerror", (e: any) => { if (e.message.includes("Sentry successfully disabled")) return; if (!e.message.startsWith("Object") && !e.message.includes("Cannot find module") && !/^.{1,2}$/.test(e.message)) { diff --git a/scripts/generateVencordPluginList.ts b/scripts/utils.ts similarity index 74% rename from scripts/generateVencordPluginList.ts rename to scripts/utils.ts index 5f38509bf..701919c8a 100644 --- a/scripts/generateVencordPluginList.ts +++ b/scripts/utils.ts @@ -24,17 +24,17 @@ import { BigIntLiteral, createSourceFile, Identifier, isArrayLiteralExpression, import { getPluginTarget } from "./utils.mjs"; -interface Dev { +export interface Dev { name: string; id: string; } -interface Command { +export interface Command { name: string; description: string; } -interface PluginData { +export interface PluginData { name: string; description: string; tags: string[]; @@ -47,25 +47,28 @@ interface PluginData { enabledByDefault: boolean; target: "discordDesktop" | "vesktop" | "equibop" | "desktop" | "web" | "dev"; filePath: string; + dirName: string; + isModified: boolean; } -const devs = {} as Record; -const equicordDevs = {} as Record; -function getName(node: NamedDeclaration) { +export const devs = {} as Record; +export const equicordDevs = {} as Record; + +export function getName(node: NamedDeclaration) { return node.name && isIdentifier(node.name) ? node.name.text : undefined; } -function hasName(node: NamedDeclaration, name: string) { +export function hasName(node: NamedDeclaration, name: string) { return getName(node) === name; } -function getObjectProp(node: ObjectLiteralExpression, name: string) { +export function getObjectProp(node: ObjectLiteralExpression, name: string) { const prop = node.properties.find(p => hasName(p, name)); if (prop && isPropertyAssignment(prop)) return prop.initializer; return prop; } -function parseDevs() { +export function parseDevs() { const file = createSourceFile("constants.ts", readFileSync("src/utils/constants.ts", "utf8"), ScriptTarget.Latest); for (const child of file.getChildAt(0).getChildren()) { @@ -96,7 +99,7 @@ function parseDevs() { throw new Error("Could not find Devs constant"); } -function parseEquicordDevs() { +export function parseEquicordDevs() { const file = createSourceFile("constants.ts", readFileSync("src/utils/constants.ts", "utf8"), ScriptTarget.Latest); for (const child of file.getChildAt(0).getChildren()) { @@ -127,7 +130,7 @@ function parseEquicordDevs() { throw new Error("Could not find EquicordDevs constant"); } -async function parseFile(fileName: string) { +export async function parseFile(fileName: string) { const file = createSourceFile(fileName, await readFile(fileName, "utf8"), ScriptTarget.Latest); const fail = (reason: string) => { @@ -148,6 +151,7 @@ async function parseFile(fileName: string) { hasCommands: false, enabledByDefault: false, required: false, + isModified: false, tags: [] as string[] } as PluginData; @@ -166,27 +170,33 @@ async function parseFile(fileName: string) { break; case "commands": data.hasCommands = true; - if (!isArrayLiteralExpression(value)) throw fail("commands is not an array literal"); - data.commands = value.elements.map((e) => { - if (isObjectLiteralExpression(e)) { - const nameProperty = e.properties.find((p): p is PropertyAssignment => { - return isPropertyAssignment(p) && isIdentifier(p.name) && p.name.escapedText === 'name'; - }); - const descriptionProperty = e.properties.find((p): p is PropertyAssignment => { - return isPropertyAssignment(p) && isIdentifier(p.name) && p.name.escapedText === 'description'; - }); - if (!nameProperty || !descriptionProperty) throw fail("command missing required properties"); - const name = isStringLiteral(nameProperty.initializer) ? nameProperty.initializer.text : ''; - const description = isStringLiteral(descriptionProperty.initializer) ? descriptionProperty.initializer.text : ''; - return { name, description }; - } else if (isCallExpression(e) && isIdentifier(e.expression)) { - const [nameArg] = e.arguments; - if (!isStringLiteral(nameArg)) throw fail("first argument must be a string"); - const name = nameArg.text; - return { name, description: "" }; - } - throw fail("commands array contains invalid elements"); - }); + if (isArrayLiteralExpression(value)) { + data.commands = value.elements.map((e) => { + if (isObjectLiteralExpression(e)) { + const nameProperty = e.properties.find((p): p is PropertyAssignment => + isPropertyAssignment(p) && isIdentifier(p.name) && p.name.escapedText === "name" + ); + const descriptionProperty = e.properties.find((p): p is PropertyAssignment => + isPropertyAssignment(p) && isIdentifier(p.name) && p.name.escapedText === "description" + ); + if (!nameProperty || !descriptionProperty) throw fail("command missing required properties"); + const name = isStringLiteral(nameProperty.initializer) ? nameProperty.initializer.text : ""; + const description = isStringLiteral(descriptionProperty.initializer) ? descriptionProperty.initializer.text : ""; + return { name, description }; + } else if (isCallExpression(e) && isIdentifier(e.expression)) { + const [nameArg] = e.arguments; + if (!isStringLiteral(nameArg)) throw fail("first argument must be a string"); + return { name: nameArg.text, description: "" }; + } else if (e.kind === SyntaxKind.SpreadElement) { + return undefined; + } + throw fail("commands array contains invalid elements"); + }).filter((c): c is { name: string; description: string; } => Boolean(c)) as Command[]; + } else if (isIdentifier(value)) { + data.commands = []; + } else { + throw fail("commands is not an array literal or identifier"); + } break; case "authors": if (!isArrayLiteralExpression(value)) throw fail("authors is not an array literal"); @@ -211,6 +221,7 @@ async function parseFile(fileName: string) { data.dependencies = (elements as NodeArray).map(e => e.text); break; case "required": + case "isModified": case "enabledByDefault": data[key] = value.kind === SyntaxKind.TrueKeyword; break; @@ -226,10 +237,16 @@ async function parseFile(fileName: string) { } data.filePath = posixNormalize(fileName) + .split(sep) + .join(posixSep) + .replace(/\/index\.([jt]sx?)$/, ""); + + data.dirName = posixNormalize(fileName) .split(sep) .join(posixSep) .replace(/\/index\.([jt]sx?)$/, "") - .replace(/^src\/plugins\//, ""); + .replace(/^src\/plugins\//, "") + .replace(/^src\/equicordplugins\//, ""); return [data] as const; } @@ -237,7 +254,7 @@ async function parseFile(fileName: string) { throw fail("no default export called 'definePlugin' found"); } -async function getEntryPoint(dir: string, dirent: Dirent) { +export async function getEntryPoint(dir: string, dirent: Dirent) { const base = join(dir, dirent.name); if (!dirent.isDirectory()) return base; @@ -252,31 +269,7 @@ async function getEntryPoint(dir: string, dirent: Dirent) { throw new Error(`${dirent.name}: Couldn't find entry point`); } -function isPluginFile({ name }: { name: string; }) { +export function isPluginFile({ name }: { name: string; }) { if (name === "index.ts") return false; return !name.startsWith("_") && !name.startsWith("."); } - -(async () => { - parseDevs(); - parseEquicordDevs(); - - const plugins = [] as PluginData[]; - - await Promise.all(["src/plugins", "src/plugins/_core"].flatMap(dir => - readdirSync(dir, { withFileTypes: true }) - .filter(isPluginFile) - .map(async dirent => { - const [data] = await parseFile(await getEntryPoint(dir, dirent)); - plugins.sort().push(data); - }) - )); - - const data = JSON.stringify(plugins); - - if (process.argv.length > 2) { - writeFileSync(process.argv[2], data); - } else { - console.log(data); - } -})(); diff --git a/src/Vencord.ts b/src/Vencord.ts index d7c36ea0b..8fb08db06 100644 --- a/src/Vencord.ts +++ b/src/Vencord.ts @@ -20,36 +20,35 @@ import "~plugins"; export * as Api from "./api"; +export * as Plugins from "./api/PluginManager"; export * as Components from "./components"; -export * as Plugins from "./plugins"; export * as Util from "./utils"; -export * as QuickCss from "./utils/quickCss"; export * as Updater from "./utils/updater"; export * as Webpack from "./webpack"; export * as WebpackPatcher from "./webpack/patchWebpack"; export { PlainSettings, Settings }; -import "./utils/quickCss"; -import "./webpack/patchWebpack"; - -import { openUpdaterModal } from "@components/settings/tabs/updater"; +import { addVencordUiStyles } from "@components/css"; +import { openSettingsTabModal, UpdaterTab } from "@components/settings"; +import { debounce } from "@shared/debounce"; import { IS_WINDOWS } from "@utils/constants"; +import { createAndAppendStyle } from "@utils/css"; import { StartAt } from "@utils/types"; import { get as dsGet } from "./api/DataStore"; import { NotificationData, showNotification } from "./api/Notifications"; -import { PlainSettings, Settings } from "./api/Settings"; -import { patches, PMLogger, startAllPlugins } from "./plugins"; +import { initPluginManager, PMLogger, startAllPlugins } from "./api/PluginManager"; +import { PlainSettings, Settings, SettingsStore } from "./api/Settings"; +import { getCloudSettings, putCloudSettings } from "./api/SettingsSync/cloudSync"; import { localStorage } from "./utils/localStorage"; import { relaunch } from "./utils/native"; -import { getCloudSettings, putCloudSettings } from "./utils/settingsSync"; import { checkForUpdates, update, UpdateLogger } from "./utils/updater"; import { onceReady } from "./webpack"; -import { SettingsRouter } from "./webpack/common"; +import { openUserSettingsPanel } from "./webpack/common"; +import { patches } from "./webpack/patchWebpack"; if (IS_REPORTER) { require("./debug/runReporter"); - Settings.plugins.CharacterCounter.enabled = false; } async function syncSettings() { @@ -60,9 +59,9 @@ async function syncSettings() { // User switched to an account that isn't connected to cloud showNotification({ title: "Cloud Settings", - body: "Cloud sync was disabled because this account isn't connected to the Vencloud App. You can enable it again by connecting this account in Cloud Settings. (note: it will store your preferences separately)", + body: "Cloud sync was disabled because this account isn't connected to the cloud App. You can enable it again by connecting this account in Cloud Settings. (note: it will store your preferences separately)", color: "var(--yellow-360)", - onClick: () => SettingsRouter.open("VencordCloud") + onClick: () => openUserSettingsPanel("equicord_cloud") }); // Disable cloud sync globally Settings.cloud.authenticated = false; @@ -81,7 +80,7 @@ async function syncSettings() { body: "We've noticed you have cloud integrations enabled in another client! Due to limitations, you will " + "need to re-authenticate to continue using them. Click here to go to the settings page to do so!", color: "var(--yellow-360)", - onClick: () => SettingsRouter.open("EquicordCloud") + onClick: () => openUserSettingsPanel("equicord_cloud") }); return; } @@ -107,11 +106,25 @@ async function syncSettings() { }); } } + + const saveSettingsOnFrequentAction = debounce(async () => { + if (Settings.cloud.settingsSync && Settings.cloud.authenticated) { + await putCloudSettings(); + delete localStorage.Vencord_settingsDirty; + } + }, 60_000); + + SettingsStore.addGlobalChangeListener(() => { + localStorage.Vencord_settingsDirty = true; + saveSettingsOnFrequentAction(); + }); } let notifiedForUpdatesThisSession = false; async function runUpdateCheck() { + if (IS_UPDATER_DISABLED) return; + const notify = (data: NotificationData) => { if (notifiedForUpdatesThisSession) return; notifiedForUpdatesThisSession = true; @@ -142,7 +155,7 @@ async function runUpdateCheck() { notify({ title: "A Equicord update is available!", body: "Click here to view the update", - onClick: openUpdaterModal! + onClick: () => openSettingsTabModal(UpdaterTab!) }); } catch (err) { UpdateLogger.error("Failed to check for updates", err); @@ -155,7 +168,7 @@ async function init() { syncSettings(); - if (!IS_WEB && !IS_UPDATER_DISABLED) { + if (!IS_DEV && !IS_WEB && !IS_UPDATER_DISABLED) { runUpdateCheck(); // this tends to get really annoying, so only do this if the user has auto-update without notification enabled @@ -178,16 +191,17 @@ async function init() { } } +initPluginManager(); startAllPlugins(StartAt.Init); init(); document.addEventListener("DOMContentLoaded", () => { + addVencordUiStyles(); + startAllPlugins(StartAt.DOMContentLoaded); + // FIXME if (IS_DISCORD_DESKTOP && Settings.winNativeTitleBar && IS_WINDOWS) { - document.head.append(Object.assign(document.createElement("style"), { - id: "vencord-native-titlebar-style", - textContent: "[class*=titleBar]{display: none!important}" - })); + createAndAppendStyle("vencord-native-titlebar-style").textContent = "[class*=titleBar]{display: none!important}"; } }, { once: true }); diff --git a/src/api/AudioPlayer.ts b/src/api/AudioPlayer.ts new file mode 100644 index 000000000..a2ad4250d --- /dev/null +++ b/src/api/AudioPlayer.ts @@ -0,0 +1,260 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2025 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { findByCodeLazy, findLazy } from "webpack"; + +let defaultSounds: null | string[] = null; +const findDefaultSounds = findLazy(module => module.resolve && module.id && module.keys().some(key => key.endsWith(".mp3")), false); +const AudioPlayerConstructor = findByCodeLazy("could not play audio"); + +export type AudioProcessor = (data: PreprocessAudioData) => void; +export type AudioCallback = (() => void); +export type AudioErrorHandler = ((error: Error) => void); +export const audioProcessorFunctions: Record = {}; + +export enum AudioType { + /** An external URL that follows the Content Security Policy. */ + URL = "url", + /** A base64-encoded data URI. */ + DATA = "data-uri", + /** A Blob URI. */ + BLOB = "blob", + /** A file path. */ + PATH = "file-path", + /** An internal Discord audio filename (e.g. "discodo"). */ + DISCORD = "discord", + /** Any other unrecognized audio type. */ + OTHER = "other" +} + +export interface PreprocessAudioData { + /** The original audio string passed to the player. */ + audio: string; + /** The read-only type of audio of the original audio string. */ + readonly type: AudioType; + /** The volume of the original audio between 0 and 100. */ + volume: number; + /** The playback speed of the original audio between 0.0625 and 16. */ + speed: number; +} + +export interface AudioPlayerInternal { + preprocessDataOriginal: PreprocessAudioData; + preprocessDataPrevious: PreprocessAudioData | null; + preprocessDataCurrent: PreprocessAudioData; + audio: string; + _audio: null | Promise; + _volume: number; + _speed: number; + outputChannel: string; + type: AudioType; + preload: boolean; + persistent: boolean; + onEnded?: AudioCallback; + onError?: AudioErrorHandler; + processAudio: () => void; + ensureAudio(): Promise; + destroyAudio(): void; + loop(): void; + play(): void; + pause(): void; + stop(restart?: boolean): void; +} + +export interface AudioPlayerInterface { + /** The internal Discord audio filename (e.g. "discodo"), a data URI, or an external URL that follows the CSP. */ + audio: string; + /** The read-only type of audio determined during processing. */ + readonly type: AudioType; + /** The duration of the audio in seconds, or null if not yet loaded. */ + readonly duration: Promise | null; + /** The current time of the audio in seconds, or null if not yet loaded. */ + time: Promise | null; + /** The paused state of the audio, or null if not yet loaded. */ + paused: Promise | null; + /** The muted state of the audio, or null if not yet loaded. */ + muted: Promise | null; + /** The volume of the audio between 0 and 100. */ + volume: number; + /** The playback speed of the audio between 0.0625 and 16. */ + speed: number; + /** Whether to load the audio immediately. If persistent is false, this will only apply until the first playback. */ + preload: boolean; + /** Whether the audio element is persistent and not recreated for every playback. */ + persistent: boolean; + /** Preloads the audio before playback. Automatically called when persistent is true. */ + load(): void; + /** Sets the audio to loop until paused or stopped. */ + loop(): void; + /** Plays the audio. */ + play(): void; + /** Pauses the audio. */ + pause(): void; + /** Stops the audio. */ + stop(): void; + /** Plays the audio from the beginning. */ + restart(): void; + /** Seeks to a specific time in seconds. */ + seek(time: number): void; + /** Mutes the audio. */ + mute(): void; + /** Unmutes the audio. */ + unmute(): void; + /** Deletes the audio element. Necessary if persistent is true. */ + delete(): void; +} + +export interface AudioPlayerOptions { + /** The volume of the audio, between 0 and 100, defaulting to 100. */ + volume?: number; + /** The playback speed of the audio, between 0.0625 and 16, defaulting to 1. */ + speed?: number; + /** Whether to preload the audio as soon as the player is created. */ + preload?: boolean; + /** Whether the audio element is persistent and not recreated for every playback. If persistent, you must call delete() to free the memory. Defaults to false. */ + persistent?: boolean; + /** An optional callback that is called every time the audio finishes playing. */ + onEnded?: AudioCallback; + /** An optional error handler that is called when an error occurs during audio playback. */ + onError?: AudioErrorHandler; +} + +// Wrap the player to allow reprocessing the audio when properties are changed and to alleviate +// the confusion between the public API accepting 0-100 volume while the internal API uses 0-1 volume. +class AudioPlayerWrapper implements AudioPlayerInterface { + private internalPlayer: AudioPlayerInternal; + constructor(internalPlayer: AudioPlayerInternal) { this.internalPlayer = internalPlayer; } + + get audio(): string { return this.internalPlayer.audio; } + set audio(value: string) { this.internalPlayer.preprocessDataOriginal.audio = value; this.internalPlayer.processAudio(); } + + get volume(): number { return this.internalPlayer._volume * 100; } + set volume(value: number) { this.internalPlayer.preprocessDataOriginal.volume = Math.max(0, Math.min(1, value / 100)); this.internalPlayer.processAudio(); } + + get speed(): number { return this.internalPlayer._speed; } + set speed(value: number) { this.internalPlayer.preprocessDataOriginal.speed = Math.max(0.0625, Math.min(16, value)); this.internalPlayer.processAudio(); } + + get time(): Promise | null { return this.internalPlayer._audio?.then(audio => audio.currentTime) ?? null; } + set time(value: number) { this.internalPlayer.ensureAudio().then(audio => audio.currentTime = value); } + + get persistent(): boolean { return this.internalPlayer.persistent; } + set persistent(value: boolean) { this.internalPlayer.persistent = value; } + + get preload(): boolean { return this.internalPlayer.preload; } + set preload(value: boolean) { this.internalPlayer.preload = value; value && this.internalPlayer.ensureAudio(); } + + get muted(): Promise | null { return this.internalPlayer._audio?.then(audio => audio.muted) ?? null; } + set muted(value: boolean) { this.internalPlayer.ensureAudio().then(audio => audio.muted = value); } + + get paused(): Promise | null { return this.internalPlayer._audio?.then(audio => audio.paused) ?? null; } + set paused(value: boolean) { value ? this.internalPlayer.pause() : this.internalPlayer.play(); } + + get type(): AudioType { return this.internalPlayer.type; } + get duration(): Promise | null { return this.internalPlayer._audio?.then(audio => audio.duration) ?? null; } + + load(): void { this.internalPlayer.ensureAudio(); } + loop(): void { this.internalPlayer.loop(); } + play(): void { this.internalPlayer.play(); } + pause(): void { this.internalPlayer.pause(); } + stop(restart?: boolean): void { this.internalPlayer.stop(restart); } + restart(): void { this.internalPlayer.stop(true); } + seek(time: number): void { this.internalPlayer.ensureAudio().then(audio => audio.currentTime = time); } + mute(): void { this.internalPlayer.ensureAudio().then(audio => audio.muted = true); } + unmute(): void { this.internalPlayer.ensureAudio().then(audio => audio.muted = false); } + delete(): void { this.internalPlayer.destroyAudio(); } +} + +/** + * Creates an audio player. + * @param audio The internal Discord audio filename (e.g. "discodo"), a data URI, or an external URL that follows the CSP. + * @param options Additional options for the audio player. + * @param options.volume The volume of the audio, between 0 and 100, defaulting to 100. + * @param options.speed The playback speed of the audio, between 0.0625 and 16, defaulting to 1. + * @param options.preload Whether to load the audio immediately. If persistent is false, this will only apply until the first playback. + * @param options.persistent Whether the audio element is persistent and not recreated for every playback. If persistent, you must call delete() to free the memory. Defaults to false. + * @param options.onEnded An optional callback that is called every time the audio finishes playing. + * @param options.onError An optional error handler that is passed an Error object when an error occurs during audio playback. + * @return The created audio player. + */ +export function createAudioPlayer( + audio: string, + options: AudioPlayerOptions = {} +): AudioPlayerInterface { + const internalPlayer: AudioPlayerInternal = new AudioPlayerConstructor( + audio, + null, + null, + "default", + options + ); + + return new AudioPlayerWrapper(internalPlayer); +} + +/** + * Plays an audio instantly and returns the player. + * @param audio The internal Discord audio filename (e.g. "discodo"), a data URI, or an external URL that follows the CSP. + * @param options Additional options for the audio player. + * @param options.volume The volume of the audio, between 0 and 100, defaulting to 100. + * @param options.speed The playback speed of the audio, between 0.0625 and 16, defaulting to 1. + * @param options.preload Whether to load the audio immediately. If persistent is false, this will only apply until the first playback. + * @param options.persistent Whether the audio element is persistent and not recreated for every playback. If persistent, you must call delete() to free the memory. Defaults to false. + * @param options.onEnded An optional callback that is called every time the audio finishes playing. + * @param options.onError An optional error handler that is passed an Error object when an error occurs during audio playback. + * @return The created audio player. + */ +export function playAudio(audio: string, options: AudioPlayerOptions = {}): AudioPlayerInterface { + const player = createAudioPlayer(audio, options); + player.play(); + return player; +} + +/** + * Identifies the type of audio based on its string. + * @param audio The audio string to identify. + * @returns The identified AudioType. + */ +export function identifyAudioType(audio: string): AudioType { + if (defaultAudioNames().includes(audio)) return AudioType.DISCORD; + + try { + const url = new URL(audio); + if (url.protocol === "http:" || url.protocol === "https:") return AudioType.URL; + if (url.protocol === "data:") return AudioType.DATA; + if (url.protocol === "blob:") return AudioType.BLOB; + if (url.protocol === "file:") return AudioType.PATH; + return AudioType.OTHER; + } catch { + return AudioType.OTHER; + } +} + +/** + * Adds a function to process an audio before it is played. + * @param key A unique identifier for this audio processor. Plugin name is recommended. + * @param processor A function that takes an audio, volume (0-100), and type (AudioType), and returns the replacement audio and volume. + */ +export function addAudioProcessor(key: string, processor: AudioProcessor): void { + audioProcessorFunctions[key] = processor; +} + +/** + * Removes an audio processor by its key. + * @param key The unique identifier of the audio processor to remove. + */ +export function removeAudioProcessor(key: string): void { + delete audioProcessorFunctions[key]; +} + +/** Returns an array of all internal Discord audio filenames. */ +export function defaultAudioNames(): string[] { + defaultSounds ??= (findDefaultSounds.keys() || []).map(key => { + const match = key.match(/((?:\w|-)+)\.mp3$/); + return match ? match[1] : null; + }).filter(Boolean) as string[]; + + return defaultSounds; +} diff --git a/src/api/Badges.ts b/src/api/Badges.ts index 096f5206f..9ab0f5cb2 100644 --- a/src/api/Badges.ts +++ b/src/api/Badges.ts @@ -17,9 +17,12 @@ */ import ErrorBoundary from "@components/ErrorBoundary"; -import BadgeAPIPlugin from "plugins/_api/badges"; +import globalBadges from "@equicordplugins/globalBadges"; +import BadgeAPIPlugin from "@plugins/_api/badges"; import { ComponentType, HTMLProps } from "react"; +import { isPluginEnabled } from "./PluginManager"; + export const enum BadgePosition { START, END @@ -31,10 +34,12 @@ export interface ProfileBadge { /** Custom component for the badge (tooltip not included) */ component?: ComponentType; /** The custom image to use */ - image?: string; + iconSrc?: string; link?: string; /** Action to perform when you click the badge */ - onClick?(event: React.MouseEvent, props: BadgeUserArgs): void; + onClick?(event: React.MouseEvent, props: ProfileBadge & BadgeUserArgs): void; + /** Action to perform when you right click the badge */ + onContextMenu?(event: React.MouseEvent, props: BadgeUserArgs & BadgeUserArgs): void; /** Should the user display this badge? */ shouldShow?(userInfo: BadgeUserArgs): boolean; /** Optional props (e.g. style) for the badge, ignored for component badges */ @@ -76,23 +81,56 @@ export function removeProfileBadge(badge: ProfileBadge) { export function _getBadges(args: BadgeUserArgs) { const badges = [] as ProfileBadge[]; for (const badge of Badges) { - if (!badge.shouldShow || badge.shouldShow(args)) { - const b = badge.getBadges - ? badge.getBadges(args).map(b => { - b.component &&= ErrorBoundary.wrap(b.component, { noop: true }); - return b; - }) - : [{ ...badge, ...args }]; + if (badge.shouldShow && !badge.shouldShow(args)) { + continue; + } - badge.position === BadgePosition.START - ? badges.unshift(...b) - : badges.push(...b); + const b = badge.getBadges + ? badge.getBadges(args).map(badge => ({ + ...args, + ...badge, + component: badge.component && ErrorBoundary.wrap(badge.component, { noop: true }) + })) + : [{ ...args, ...badge }]; + + if (badge.position === BadgePosition.START) { + badges.unshift(...b); + } else { + badges.push(...b); } } + const donorBadges = BadgeAPIPlugin.getDonorBadges(args.userId); const equicordDonorBadges = BadgeAPIPlugin.getEquicordDonorBadges(args.userId); - if (donorBadges) badges.unshift(...donorBadges); - if (equicordDonorBadges) badges.unshift(...equicordDonorBadges); + const GlobalBadges = isPluginEnabled(globalBadges.name) ? globalBadges.getGlobalBadges(args.userId) : false; + + // do globalbadges first so it shows before the contrib badges but after donor badges + if (GlobalBadges) { + badges.unshift( + ...GlobalBadges.map(badge => ({ + ...args, + ...badge, + })) + ); + } + + if (donorBadges) { + badges.unshift( + ...donorBadges.map(badge => ({ + ...args, + ...badge, + })) + ); + } + + if (equicordDonorBadges) { + badges.unshift( + ...equicordDonorBadges.map(badge => ({ + ...args, + ...badge, + })) + ); + } return badges; } diff --git a/src/api/ChatButtons.tsx b/src/api/ChatButtons.tsx index 1cacd06b2..f4871141d 100644 --- a/src/api/ChatButtons.tsx +++ b/src/api/ChatButtons.tsx @@ -8,11 +8,16 @@ import "./ChatButton.css"; import ErrorBoundary from "@components/ErrorBoundary"; import { Logger } from "@utils/Logger"; +import { classes } from "@utils/misc"; +import { IconComponent } from "@utils/types"; import { Channel } from "@vencord/discord-types"; import { waitFor } from "@webpack"; -import { Button, ButtonWrapperClasses, Tooltip } from "@webpack/common"; +import { ButtonWrapperClasses, Clickable, Menu, Tooltip } from "@webpack/common"; import { HTMLProps, JSX, MouseEventHandler, ReactNode } from "react"; +import { addContextMenuPatch, findGroupChildrenByChildId } from "./ContextMenu"; +import { useSettings } from "./Settings"; + let ChannelTextAreaClasses: Record<"button" | "buttonContainer", string>; waitFor(["buttonContainer", "channelTextArea"], m => ChannelTextAreaClasses = m); @@ -74,57 +79,116 @@ export interface ChatBarProps { }; } -export type ChatBarButtonFactory = (props: ChatBarProps & { isMainChat: boolean; }) => JSX.Element | null; +export type ChatBarButtonFactory = (props: ChatBarProps & { isMainChat: boolean; isAnyChat: boolean; }) => JSX.Element | null; +export type ChatBarButtonData = { + render: ChatBarButtonFactory; + /** + * This icon is used only for Settings UI. Your render function must still render an icon, + * and it can be different from this one. + */ + icon: IconComponent; +}; -const buttonFactories = new Map(); +/** + * Don't use this directly, use {@link addChatBarButton} and {@link removeChatBarButton} instead. + */ +export const ChatBarButtonMap = new Map(); const logger = new Logger("ChatButtons"); +function VencordChatBarButtons(props: ChatBarProps) { + const { chatBarButtons } = useSettings(["uiElements.chatBarButtons.*"]).uiElements; + + const { analyticsName } = props.type; + return ( + <> + {Array.from(ChatBarButtonMap) + .filter(([key]) => chatBarButtons[key]?.enabled !== false) + .map(([key, { render: Button }]) => ( + logger.error(`Failed to render ${key}`, e.error)}> + + )} ); }, { noop: true }); + +addContextMenuPatch("textarea-context", (children, args) => { + const { chatBarButtons } = useSettings(["uiElements.chatBarButtons.*"]).uiElements; + + const buttons = Array.from(ChatBarButtonMap.entries()); + if (!buttons.length) return; + + const group = findGroupChildrenByChildId("submit-button", children); + if (!group) return; + + const idx = group.findIndex(c => c?.props?.id === "submit-button"); + if (idx === -1) return; + + group.splice(idx, 0, + + {buttons.map(([id]) => ( + { + const wasEnabled = chatBarButtons[id]?.enabled !== false; + + chatBarButtons[id] ??= {} as any; + chatBarButtons[id].enabled = !wasEnabled; + }} + /> + ))} + + ); +}); diff --git a/src/api/HeaderBar.tsx b/src/api/HeaderBar.tsx new file mode 100644 index 000000000..3425bff97 --- /dev/null +++ b/src/api/HeaderBar.tsx @@ -0,0 +1,204 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2025 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import ErrorBoundary from "@components/ErrorBoundary"; +import { Logger } from "@utils/Logger"; +import { findComponentByCodeLazy } from "@webpack"; +import { useEffect, useState } from "@webpack/common"; +import type { ComponentType, JSX, MouseEventHandler, ReactNode } from "react"; + +const logger = new Logger("HeaderBarAPI"); + +const HeaderBarIcon = findComponentByCodeLazy(".HEADER_BAR_BADGE_TOP:", '.iconBadge,"top"') as ComponentType; + +export interface HeaderBarButtonProps { + /** The icon component to render inside the button */ + icon: ComponentType; + /** Tooltip text shown on hover. Pass null to disable tooltip */ + tooltip: ReactNode; + /** Called when the button is clicked */ + onClick?: MouseEventHandler; + /** Called when the button is right-clicked */ + onContextMenu?: MouseEventHandler; + /** Additional CSS class names */ + className?: string; + /** CSS class name for the icon element */ + iconClassName?: string; + /** Tooltip position relative to the button */ + position?: "top" | "bottom" | "left" | "right"; + /** Whether the button appears in a selected/active state */ + selected?: boolean; + /** Whether the button is disabled */ + disabled?: boolean; + /** Whether to show a notification badge */ + showBadge?: boolean; + /** Position of the notification badge */ + badgePosition?: "top" | "bottom"; + /** Size of the icon in pixels */ + iconSize?: number; + /** Ref to the button element */ + ref?: React.RefObject; +} + +export type HeaderBarButtonFactory = () => JSX.Element | null; + +export interface HeaderBarButtonData { + /** Function that renders the button component */ + render: HeaderBarButtonFactory; + /** Icon component used for settings UI display */ + icon: ComponentType; + /** Higher priority buttons appear further right. Default: 0 */ + priority?: number; + /** Where to render the button. Default: "headerbar" */ + location?: "headerbar" | "channeltoolbar"; +} + +interface ButtonEntry { + render: HeaderBarButtonFactory; + priority: number; +} + +/** + * Button component for the top header bar (title bar area). + * + * @example + * console.log("clicked")} + * /> + */ +export const HeaderBarButton = HeaderBarIcon; + +/** + * Button component for the channel toolbar (below the search bar). + * Automatically handles selected state styling. + * + * @example + * setOpen(v => !v)} + * selected={isOpen} + * /> + */ +export function ChannelToolbarButton(props: HeaderBarButtonProps) { + return ; +} + +const headerBarButtons = new Map(); +const channelToolbarButtons = new Map(); + +const headerBarListeners = new Set<() => void>(); +const channelToolbarListeners = new Set<() => void>(); + +/** + * Adds a button to the header bar (title bar area). + * + * @param id - Unique identifier for the button (e.g., "my-plugin-button") + * @param render - Function that returns the button JSX + * @param priority - Higher values appear further right. Default: 0 + * + * @example + * addHeaderBarButton("my-button", () => ( + * + * )); + */ +export function addHeaderBarButton(id: string, render: HeaderBarButtonFactory, priority = 0) { + headerBarButtons.set(id, { render, priority }); + headerBarListeners.forEach(listener => listener()); +} + +/** + * Removes a button from the header bar. + * + * @param id - The identifier used when adding the button + */ +export function removeHeaderBarButton(id: string) { + headerBarButtons.delete(id); + headerBarListeners.forEach(listener => listener()); +} + +/** + * Adds a button to the channel toolbar (below the search bar, next to pins/members). + * + * @param id - Unique identifier for the button (e.g., "my-plugin-toolbar") + * @param render - Function that returns the button JSX + * @param priority - Higher values appear further right. Default: 0 + * + * @example + * addChannelToolbarButton("my-toolbar", () => ( + * + * )); + */ +export function addChannelToolbarButton(id: string, render: HeaderBarButtonFactory, priority = 0) { + channelToolbarButtons.set(id, { render, priority }); + channelToolbarListeners.forEach(listener => listener()); +} + +/** + * Removes a button from the channel toolbar. + * + * @param id - The identifier used when adding the button + */ +export function removeChannelToolbarButton(id: string) { + channelToolbarButtons.delete(id); + channelToolbarListeners.forEach(listener => listener()); +} + +function HeaderBarButtons() { + const [, forceUpdate] = useState(0); + + useEffect(() => { + const listener = () => forceUpdate(n => n + 1); + headerBarListeners.add(listener); + return () => { headerBarListeners.delete(listener); }; + }, []); + + return Array.from(headerBarButtons) + .sort(([, a], [, b]) => a.priority - b.priority) + .map(([id, { render: Button }]) => ( + logger.error(`Failed to render header bar button: ${id}`, e.error)}> + + ); +} + +export function LinkButton({ variant = "link", size = "medium", className, children, ...restProps }: LinkButtonProps) { + return ( + + {children} + + + ); +} + +export type TextButtonVariant = "primary" | "secondary" | "danger" | "link"; + +export type TextButtonProps = ComponentPropsWithRef<"button"> & { + variant?: TextButtonVariant; +}; + +export function TextButton({ variant = "primary", className, ...restProps }: TextButtonProps) { + return ( + - - {currentThemeLink && } - - -
- {onlineThemes?.map(rawLink => { - const { label, link } = (() => { - const match = /^@(light|dark) (.*)/.exec(rawLink.link); - if (!match) return { label: rawLink, link: rawLink }; - - const [, mode, link] = match; - return { label: `[${mode} mode only] ${link}`, link }; - })(); - - return onThemeLinkEnabledChange(rawLink.link, enabled)} - onDelete={async () => { - onThemeLinkEnabledChange(rawLink.link, false); - deleteThemeLink(rawLink.link); - }} - showDeleteButton - theme={rawLink} - />; - })} -
- - - ); + function togglePinTheme(themeId: string) { + if (settings.pinnedThemes.includes(themeId)) { + settings.pinnedThemes = settings.pinnedThemes.filter(f => f !== themeId); + } else { + settings.pinnedThemes = [...settings.pinnedThemes, themeId]; + } } - return ( - - - - Local Themes - - - Online Themes - - + async function refreshOnlineTheme(link: string) { + try { + const res = await fetch(link); + if (!res.ok) throw new Error(`Failed to fetch ${link}`); + const css = await res.text(); + const updatedTheme = { ...getThemeInfo(css, link), link }; + setOnlineThemes(prev => + prev?.map(t => t.link === link ? updatedTheme : t) ?? null + ); + showToast("Theme refreshed!", Toasts.Type.SUCCESS); + } catch { + showToast("Failed to refresh theme", Toasts.Type.FAILURE); + } + } + + async function downloadTheme(link: string, name: string) { + try { + const res = await fetch(link); + if (!res.ok) throw new Error(`Failed to fetch ${link}`); + const css = await res.text(); + const fileName = name.replace(/[^a-z0-9]/gi, "-") + ".css"; + + if (IS_DISCORD_DESKTOP) { + DiscordNative.fileManager.saveWithDialog(new TextEncoder().encode(css), fileName); + } else { + const blob = new Blob([css], { type: "text/css" }); + const url = URL.createObjectURL(blob); + const a = document.createElement("a"); + a.href = url; + a.download = fileName; + a.click(); + URL.revokeObjectURL(url); + } + } catch { + showToast("Failed to download theme", Toasts.Type.FAILURE); + } + } + + const allThemes = useMemo((): UnifiedTheme[] => { + const themes: UnifiedTheme[] = []; + + for (const theme of onlineThemes ?? []) { + const customName = themeNames[theme.link] ?? null; + themes.push({ + type: "online", + themeType: "other", + name: customName ?? theme.name ?? theme.fileName, + enabled: settings.enabledThemeLinks.includes(theme.link), + header: { ...theme, customName }, + link: theme.link + }); + } + + for (const { type, header } of userThemes ?? []) { + const name = type === "usercss" + ? (header as UserstyleHeader).name ?? "Unknown" + : (header as UserThemeHeader).name ?? (header as UserThemeHeader).fileName; + + themes.push({ + type: "local", + themeType: type, + name, + enabled: settings.enabledThemes.includes(header.fileName), + header + }); + } + + return themes; + }, [onlineThemes, userThemes, themeNames, settings.enabledThemeLinks, settings.enabledThemes]); + + const filteredThemes = useMemo(() => { + let themes = allThemes; + + if (searchQuery.trim()) { + const query = searchQuery.toLowerCase(); + themes = themes.filter(t => t.name.toLowerCase().includes(query)); + } + + switch (filter) { + case ThemeFilter.Online: + themes = themes.filter(t => t.type === "online"); + break; + case ThemeFilter.Local: + themes = themes.filter(t => t.type === "local"); + break; + case ThemeFilter.Enabled: + themes = themes.filter(t => t.enabled); + break; + case ThemeFilter.Disabled: + themes = themes.filter(t => !t.enabled); + break; + } + + const getThemeId = (t: UnifiedTheme) => t.type === "online" ? t.link! : (t.header as UserThemeHeader).fileName; + themes.sort((a, b) => { + const aId = getThemeId(a); + const bId = getThemeId(b); + const aPinIndex = settings.pinnedThemes.indexOf(aId); + const bPinIndex = settings.pinnedThemes.indexOf(bId); + const aIsPinned = aPinIndex !== -1; + const bIsPinned = bPinIndex !== -1; + + if (aIsPinned && !bIsPinned) return -1; + if (!aIsPinned && bIsPinned) return 1; + if (aIsPinned && bIsPinned) return aPinIndex - bPinIndex; + return 0; + }); + + return themes; + }, [allThemes, searchQuery, filter, settings.pinnedThemes]); + + const localCount = allThemes.filter(t => t.type === "local").length; + const onlineCount = allThemes.filter(t => t.type === "online").length; + const enabledCount = allThemes.filter(t => t.enabled).length; + + return ( + - {currentTab === ThemeTab.LOCAL && } - {currentTab === ThemeTab.ONLINE && } + + Theme Management + + Customize Discord's appearance with themes. Add local .css files or load themes directly from URLs. Themes with a cog wheel icon have customizable settings you can modify. + + + Quick Actions + + Shortcuts for managing your themes. Open your themes folder to add new themes, use QuickCSS for quick style tweaks, or reload themes after making changes. + + + + {IS_WEB ? ( + + Upload Theme + + + } + Icon={PlusIcon} + /> + ) : ( + VencordNative.themes.openFolder()} + Icon={FolderIcon} + /> + )} + + VencordNative.quickCss.openEditor()} + Icon={PaintbrushIcon} + /> + {Settings.plugins.ClientTheme.enabled && ( + openPluginModal(Plugins.ClientTheme)} + Icon={PencilIcon} + /> + )} + + + + + Online Themes + + Load themes directly from URLs instead of local files. Online themes auto-update when the source changes, so you always have the latest version without manual downloads. + + { + settings.enableOnlineThemes = value; + if (!value) { + settings.enabledThemeLinks = []; + } + }} + /> + + + Looking for themes? Check out BetterDiscord Themes or search on GitHub. When downloading from BetterDiscord, click "Download" and place the .theme.css file into your themes folder. + + +
+ + +
+ {currentThemeLink && ( +
+ +
+ )} + + + + Installed Themes + + Manage your themes here. Local themes load from your themes folder, online themes from URLs. Themes with a cog wheel icon have customizable settings. + + + {allThemes.length} theme{allThemes.length !== 1 ? "s" : ""} installed ({localCount} local, {onlineCount} online) · {enabledCount} enabled + + +
+ +
+ v === themeSettings[name]} serialize={identity} /> - + ); } diff --git a/src/components/ThemeSettings/components/SettingTextComponent.tsx b/src/components/ThemeSettings/components/SettingTextComponent.tsx index be3ccf42e..9fe8bb1b0 100644 --- a/src/components/ThemeSettings/components/SettingTextComponent.tsx +++ b/src/components/ThemeSettings/components/SettingTextComponent.tsx @@ -4,7 +4,8 @@ * SPDX-License-Identifier: GPL-3.0-or-later */ -import { Forms, TextInput } from "@webpack/common"; +import { Heading } from "@components/Heading"; +import { TextInput } from "@webpack/common"; interface Props { label: string; @@ -18,13 +19,13 @@ export function SettingTextComponent({ label, name, themeSettings }: Props) { } return ( - - {label} +
+ {label} - +
); } diff --git a/src/components/ThemeSettings/components/colorStyles.css b/src/components/ThemeSettings/components/colorStyles.css index 8b7bfc518..2c770bda3 100644 --- a/src/components/ThemeSettings/components/colorStyles.css +++ b/src/components/ThemeSettings/components/colorStyles.css @@ -11,9 +11,9 @@ overflow: hidden; margin-top: 0; margin-bottom: 0; - color: var(--header-primary); + color: var(--text-strong); line-height: 24px; font-size: 16px; font-weight: 500; - word-wrap: break-word; -} + overflow-wrap: break-word; +} \ No newline at end of file diff --git a/src/components/ThemeSettings/components/styles.css b/src/components/ThemeSettings/components/styles.css new file mode 100644 index 000000000..422bcd940 --- /dev/null +++ b/src/components/ThemeSettings/components/styles.css @@ -0,0 +1,3 @@ +.vc-settings-boolean-switch { + margin-bottom: 0.5em; +} \ No newline at end of file diff --git a/src/components/ThemeSettings/themesStyles.css b/src/components/ThemeSettings/themesStyles.css index 372d500e4..ff902783b 100644 --- a/src/components/ThemeSettings/themesStyles.css +++ b/src/components/ThemeSettings/themesStyles.css @@ -1,49 +1,82 @@ .vc-settings-theme-grid { display: grid; - grid-gap: 16px; + gap: 8px; grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); } -.vc-settings-theme-card { +.vc-settings-theme-link-row { + display: grid; + height: 40px; + gap: 8px; + grid-template-columns: 1fr auto; +} + +.vc-settings-theme-filter-row { + display: grid; + height: 40px; + gap: 8px; + grid-template-columns: 1fr 150px; +} + +.vc-settings-theme-error-card { + padding: 16px; +} + +.vc-settings-csp-list { display: flex; flex-direction: column; - background-color: var(--background-base-lower-alt); - color: var(--interactive-active); - border-radius: 8px; - padding: 1em; - width: 100%; - transition: 0.1s ease-out; - transition-property: box-shadow, transform, background, opacity; -} - -.vc-settings-theme-card-text { - text-overflow: ellipsis; - height: 1.2em; - margin-bottom: 2px; - white-space: nowrap; - overflow: hidden; -} - -.vc-settings-theme-author::before { - content: "by "; -} - -.vc-settings-theme-link-input { - width: 100%; -} - -.vc-settings-theme-add-card { - padding: 1em; - margin-bottom: 16px; + gap: 8px; } .vc-settings-usercss-ie-buttons>div { - color: var(--interactive-normal); - opacity: .5; + color: var(--interactive-icon-default, var(--interactive-normal)); + opacity: 0.5; padding: 4px; + cursor: pointer; } .vc-settings-usercss-ie-buttons>div:hover { - color: var(--interactive-hover); + color: var(--interactive-icon-hover, var(--interactive-hover)); opacity: 1; -} \ No newline at end of file +} + +.vc-settings-csp-row { + display: flex; + justify-content: space-between; + align-items: center; + gap: 8px; + padding: 8px 0; +} + +.vc-settings-csp-row a { + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + flex: 1; + min-width: 0; +} + +.vc-settings-theme-menu-button { + cursor: pointer; + color: var(--interactive-icon-default, var(--interactive-normal)); + display: flex; + align-items: center; + height: 24px; +} + +.vc-settings-theme-menu-button:hover { + color: var(--interactive-icon-hover, var(--interactive-hover)); +} + +.vc-settings-theme-footer-pin-icon { + display: flex; +} + +.vc-settings-theme-footer-pin-icon svg { + width: 16px; + height: 16px; +} + +.vc-settings-theme-footer-pin-icon svg path { + fill: var(--text-muted); +} diff --git a/src/components/css.ts b/src/components/css.ts new file mode 100644 index 000000000..51151b1fe --- /dev/null +++ b/src/components/css.ts @@ -0,0 +1,15 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2025 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { createAndAppendStyle } from "@utils/css"; + +import { generateTextCss } from "./BaseText"; +import { generateMarginCss } from "./margins"; + +export function addVencordUiStyles() { + createAndAppendStyle("vencord-text", document.head).textContent = generateTextCss(); + createAndAppendStyle("vencord-margins").textContent = generateMarginCss(); +} diff --git a/src/components/iconStyles.css b/src/components/iconStyles.css index 9b139b325..3787211be 100644 --- a/src/components/iconStyles.css +++ b/src/components/iconStyles.css @@ -1,12 +1,3 @@ -.vc-open-external-icon { - transform: rotate(45deg); -} - .vc-owner-crown-icon { color: var(--status-warning); -} - -.vc-heart-icon { - margin-right: 0.5em; - translate: 0 2px; -} +} \ No newline at end of file diff --git a/src/components/index.ts b/src/components/index.ts index 9265acfb5..a384f977e 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -4,14 +4,24 @@ * SPDX-License-Identifier: GPL-3.0-or-later */ +export * from "./Alert"; +export * from "./BaseText"; +export * from "./Button"; +export * from "./Card"; export * from "./CheckedTextInput"; export * from "./CodeBlock"; +export * from "./Divider"; export { default as ErrorBoundary } from "./ErrorBoundary"; export * from "./ErrorCard"; export * from "./Flex"; +export * from "./FormSwitch"; export * from "./Grid"; +export * from "./Heading"; export * from "./Heart"; export * from "./Icons"; export * from "./Link"; +export * from "./margins"; +export * from "./Paragraph"; export * from "./settings"; - +export * from "./Span"; +export * from "./Switch"; diff --git a/src/plugins/voiceMessages/utils.ts b/src/components/margins.ts similarity index 54% rename from src/plugins/voiceMessages/utils.ts rename to src/components/margins.ts index dcfd6f26c..24138d2a8 100644 --- a/src/plugins/voiceMessages/utils.ts +++ b/src/components/margins.ts @@ -18,4 +18,26 @@ import { classNameFactory } from "@api/Styles"; -export const cl = classNameFactory("vc-vmsg-"); +const marginCls = classNameFactory("vc-margin-"); + +const Directions = ["top", "bottom", "left", "right"] as const; +const Sizes = [8, 16, 20] as const; + +export type MarginDirection = (typeof Directions)[number]; +export type MarginSize = (typeof Sizes)[number]; + +export const Margins: Record<`${MarginDirection}${MarginSize}`, string> = {} as any; + +export function generateMarginCss() { + let css = ""; + + for (const direction of Directions) { + for (const size of Sizes) { + const cl = marginCls(`${direction}-${size}`); + Margins[`${direction}${size}`] = cl; + css += `.${cl}{margin-${direction}:${size}px;}`; + } + } + + return css; +} diff --git a/src/components/settings/AddonCard.css b/src/components/settings/AddonCard.css index d5e3b4d6b..ec0f7b326 100644 --- a/src/components/settings/AddonCard.css +++ b/src/components/settings/AddonCard.css @@ -1,6 +1,6 @@ .vc-addon-card { - background-color: var(--card-primary-bg); - color: var(--interactive-active); + background-color: var(--card-background-default); + color: var(--interactive-icon-active); border: 1px solid var(--border-subtle); border-radius: 8px; display: block; @@ -41,6 +41,7 @@ -webkit-box-orient: vertical; /* stylelint-disable-next-line property-no-unknown */ box-orient: vertical; + color: var(--text-default); } .vc-addon-name-author { @@ -52,7 +53,6 @@ width: 100%; align-items: center; flex-grow: 1; - gap: 8px; } .vc-addon-author { @@ -94,4 +94,15 @@ .vc-addon-title:hover { overflow: visible; animation: vc-addon-title var(--duration) linear infinite; -} \ No newline at end of file +} + +.vc-addon-editable { + border: none; + background: transparent; + color: inherit; + font: inherit; +} + +.vc-addon-footer { + font-size: small; +} diff --git a/src/components/settings/AddonCard.tsx b/src/components/settings/AddonCard.tsx index 6cdbfe8db..f5798afd6 100644 --- a/src/components/settings/AddonCard.tsx +++ b/src/components/settings/AddonCard.tsx @@ -19,13 +19,12 @@ import "./AddonCard.css"; import { classNameFactory } from "@api/Styles"; +import { BaseText } from "@components/BaseText"; import { AddonBadge } from "@components/settings/PluginBadge"; -import { Switch } from "@components/settings/Switch"; -import { Text, useRef } from "@webpack/common"; +import { Switch } from "@components/Switch"; +import { Tooltip, useRef } from "@webpack/common"; import type { MouseEventHandler, ReactNode } from "react"; -import { useTruncatedText } from "./tabs/plugins/components/truncateText"; - const cl = classNameFactory("vc-addon-"); interface Props { @@ -35,6 +34,8 @@ interface Props { setEnabled: (enabled: boolean) => void; disabled?: boolean; isNew?: boolean; + sourceBadge?: ReactNode; + tooltip?: string; onMouseEnter?: MouseEventHandler; onMouseLeave?: MouseEventHandler; @@ -43,10 +44,9 @@ interface Props { author?: ReactNode; } -export function AddonCard({ disabled, isNew, name, infoButton, footer, author, enabled, setEnabled, description, onMouseEnter, onMouseLeave }: Props) { +export function AddonCard({ disabled, isNew, sourceBadge, tooltip, name, infoButton, footer, author, enabled, setEnabled, description, onMouseEnter, onMouseLeave }: Props) { const titleRef = useRef(null); const titleContainerRef = useRef(null); - const { truncated, containerRef } = useTruncatedText(description ? description.toString() : ""); return (
- +
{isNew && } - + {!!author && ( - + {author} - + )}
+ + {({ onMouseEnter, onMouseLeave }) => ( +
+ {sourceBadge} +
+ )} +
+ {infoButton}
- {truncated} + {description}
- {footer} + {footer &&
{footer}
}
); } diff --git a/src/components/settings/DonateButton.tsx b/src/components/settings/DonateButton.tsx index 819ea9ca9..4a8e96dc8 100644 --- a/src/components/settings/DonateButton.tsx +++ b/src/components/settings/DonateButton.tsx @@ -16,42 +16,27 @@ * along with this program. If not, see . */ +import { Button } from "@components/Button"; import { Heart } from "@components/Heart"; +import { OpenExternalIcon } from "@components/Icons"; import { openInviteModal } from "@utils/discord"; import { ButtonProps } from "@vencord/discord-types"; -import { Button, showToast } from "@webpack/common"; - -export function VCDonateButton({ - look = Button.Looks.LINK, - color = Button.Colors.TRANSPARENT, - ...props -}: Partial) { - return ( - - ); -} +import { showToast } from "@webpack/common"; export function DonateButton({ - look = Button.Looks.LINK, - color = Button.Colors.TRANSPARENT, + equicord = false, + className, ...props -}: Partial) { +}: Partial & { equicord?: boolean; }) { + const link = equicord ? "https://github.com/sponsors/thororen1234" : "https://github.com/sponsors/Vendicated"; return ( ); } diff --git a/src/components/settings/EditableText.tsx b/src/components/settings/EditableText.tsx new file mode 100644 index 000000000..c53dc1d26 --- /dev/null +++ b/src/components/settings/EditableText.tsx @@ -0,0 +1,56 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2025 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { BaseText } from "@components/BaseText"; +import { React, useEffect, useRef, useState } from "@webpack/common"; + +interface EditableTextProps { + value: string; + onChange: (newValue: string) => void; + className?: string; +} + +export function EditableText({ value, onChange, className }: EditableTextProps) { + const [editing, setEditing] = useState(false); + const [tempValue, setTempValue] = useState(value); + const inputRef = useRef(null); + + useEffect(() => { + if (editing && inputRef.current) { + inputRef.current.focus(); + } + }, [editing]); + + return editing ? ( + setTempValue(e.target.value)} + onBlur={() => { + setEditing(false); + onChange(tempValue); + }} + onKeyDown={e => { + if (e.key === "Enter") { + setEditing(false); + onChange(tempValue); + } else if (e.key === "Escape") { + setEditing(false); + setTempValue(value); + } + }} + /> + ) : ( + setEditing(true)} + style={{ cursor: "pointer" }} + > + {value} + + ); +} diff --git a/src/components/settings/OnlineThemeCard.tsx b/src/components/settings/OnlineThemeCard.tsx new file mode 100644 index 000000000..abbbc8bc6 --- /dev/null +++ b/src/components/settings/OnlineThemeCard.tsx @@ -0,0 +1,135 @@ +/* + * Vencord, a modification for Discord's desktop app + * Copyright (c) 2023 Vendicated and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . +*/ + +import "./AddonCard.css"; + +import { classNameFactory } from "@api/Styles"; +import { BaseText } from "@components/BaseText"; +import { Switch } from "@components/settings"; +import { AddonBadge } from "@components/settings/PluginBadge"; +import { useRef } from "@webpack/common"; +import type { MouseEventHandler, ReactNode } from "react"; + +import { EditableText } from "./EditableText"; +import { useTruncatedText } from "./tabs/plugins/components/truncateText"; + +const cl = classNameFactory("vc-addon-"); + +interface Props { + name: ReactNode; + description: ReactNode; + enabled: boolean; + setEnabled: (enabled: boolean) => void; + disabled?: boolean; + isNew?: boolean; + onMouseEnter?: MouseEventHandler; + onMouseLeave?: MouseEventHandler; + + infoButton?: ReactNode; + footer?: ReactNode; + author?: ReactNode; + + customName?: string; + customDescription?: string; + onEditName?: (newName: string) => void; + onEditDescription?: (newDescription: string) => void; +} + +export function OnlineThemeCard({ + disabled, + isNew, + name, + infoButton, + footer, + author, + enabled, + setEnabled, + description, + onMouseEnter, + onMouseLeave, + customName, + onEditName, +}: Props) { + const titleRef = useRef(null); + const titleContainerRef = useRef(null); + const { truncated, containerRef } = useTruncatedText(description ? description.toString() : ""); + + return ( +
+
+
+ +
+
{ + const title = titleRef.current!; + const titleContainer = titleContainerRef.current!; + + title.style.setProperty("--offset", `${titleContainer.clientWidth - title.scrollWidth}px`); + title.style.setProperty("--duration", `${Math.max(0.5, (title.scrollWidth - titleContainer.clientWidth) / 7)}s`); + }} + > + {onEditName ? ( + + ) : ( + customName || name + )} +
+
+ {isNew && } +
+ + {!!author && ( + + {author} + + )} +
+ + {infoButton} + + +
+ +
+ {truncated} +
+ + {footer &&
{footer}
} +
+ ); +} diff --git a/src/components/settings/QuickAction.css b/src/components/settings/QuickAction.css index 8b94bd2d4..ad7a7d7f5 100644 --- a/src/components/settings/QuickAction.css +++ b/src/components/settings/QuickAction.css @@ -14,29 +14,33 @@ .vc-settings-quickActions-pill { all: unset; - background: var(--button-secondary-background); - color: var(--header-secondary); + background: var(--redesign-button-secondary-background); + color: var(--text-default); display: flex; align-items: center; + justify-content: center; gap: 0.5em; - padding: 8px 9px; + padding: 8px 12px 8px 8px; border-radius: 8px; transition: 0.1s ease-out; box-sizing: border-box; + line-height: 1; + cursor: pointer; } .vc-settings-quickActions-pill:hover { - background: var(--button-secondary-background-hover); + background: var(--redesign-button-secondary-pressed-background); transform: translateY(-1px); box-shadow: var(--elevation-high); } .vc-settings-quickActions-pill:focus-visible { - outline: 2px solid var(--focus-primary); + outline: 2px solid var(--border-focus); outline-offset: 2px; } .vc-settings-quickActions-img { width: 24px; height: 24px; -} \ No newline at end of file + flex-shrink: 0; +} diff --git a/src/components/settings/QuickAction.tsx b/src/components/settings/QuickAction.tsx index d1a8f52c4..a0cd0bcd3 100644 --- a/src/components/settings/QuickAction.tsx +++ b/src/components/settings/QuickAction.tsx @@ -7,7 +7,7 @@ import "./QuickAction.css"; import { classNameFactory } from "@api/Styles"; -import { Card } from "@webpack/common"; +import { Card } from "@components/Card"; import type { ComponentType, PropsWithChildren, ReactNode } from "react"; const cl = classNameFactory("vc-settings-quickActions-"); @@ -30,9 +30,9 @@ export function QuickAction(props: QuickActionProps) { ); } -export function QuickActionCard(props: PropsWithChildren) { +export function QuickActionCard(props: PropsWithChildren<{ columns?: number; }>) { return ( - + {props.children} ); diff --git a/src/components/settings/SpecialCard.css b/src/components/settings/SpecialCard.css index 30c8357d9..f037aadbd 100644 --- a/src/components/settings/SpecialCard.css +++ b/src/components/settings/SpecialCard.css @@ -1,12 +1,71 @@ +.vc-invite-button, .vc-donate-button { overflow: visible !important; + color: black; + background-color: color-mix(in srgb, #ED87A9 25%, pink); } -.vc-donate-button .vc-heart-icon { +.vc-invite-button:hover, +.vc-donate-button:hover { + overflow: visible !important; + color: black; + background-color: color-mix(in srgb, #ED87A9 50%, pink); +} + +.vc-invite-support-button, +.vc-donate-support-button { + overflow: visible !important; + color: black; + background-color: color-mix(in srgb, #c3a3ce 25%, rgb(240 178 240)); +} + +.vc-invite-support-button:hover, +.vc-donate-support-button:hover { + overflow: visible !important; + color: black; + background-color: color-mix(in srgb, #c3a3ce 50%, rgb(240 178 240)); +} + +.vc-invite-link { + width: 16px; + height: 16px; + margin-left: 8px; + flex-shrink: 0; +} + +.vc-contrib-button { + overflow: visible !important; + margin-top: 1em; + color: black; + background-color: color-mix(in srgb, #EDCC87 60%, lightyellow); +} + +.vc-contrib-button:hover { + color: black; + background-color: color-mix(in srgb, #EDCC87 75%, lightyellow); +} + +.vc-contrib-github { + width: 24px; + height: 24px; + flex-shrink: 0; transition: transform 0.3s; } -.vc-donate-button:hover .vc-heart-icon { +.vc-invite-button .vc-invite-link, +.vc-contrib-button .vc-contrib-github, +.vc-donate-button .vc-heart-icon, +.vc-invite-support-button .vc-invite-link, +.vc-donate-support-button .vc-heart-icon { + transition: transform 0.3s; + margin-right: 0.5em; +} + +.vc-invite-button:hover .vc-invite-link, +.vc-contrib-button:hover .vc-contrib-github, +.vc-donate-button:hover .vc-heart-icon, +.vc-invite-support-button:hover .vc-invite-link, +.vc-donate-support-button:hover .vc-heart-icon { transform: scale(1.1); z-index: 10; position: relative; @@ -83,4 +142,4 @@ .vc-special-image { width: 65%; -} +} \ No newline at end of file diff --git a/src/components/settings/SpecialCard.tsx b/src/components/settings/SpecialCard.tsx index 28f83bd7a..3388f9604 100644 --- a/src/components/settings/SpecialCard.tsx +++ b/src/components/settings/SpecialCard.tsx @@ -19,7 +19,11 @@ import "./SpecialCard.css"; import { classNameFactory } from "@api/Styles"; -import { Card, Clickable, Forms, React } from "@webpack/common"; +import { Card } from "@components/Card"; +import { Divider } from "@components/Divider"; +import { Heading } from "@components/Heading"; +import { Paragraph } from "@components/Paragraph"; +import { Clickable } from "@webpack/common"; import type { PropsWithChildren } from "react"; const cl = classNameFactory("vc-special-"); @@ -45,9 +49,9 @@ export function SpecialCard({ title, subtitle, description, cardImage, backgroun
- {title} - {subtitle} - {description} + {title} + {subtitle} + {description} {children}
@@ -64,10 +68,11 @@ export function SpecialCard({ title, subtitle, description, cardImage, backgroun
{buttonTitle && ( <> + - + {buttonTitle} - + )} diff --git a/src/components/settings/Switch.css b/src/components/settings/Switch.css deleted file mode 100644 index e6dcf5626..000000000 --- a/src/components/settings/Switch.css +++ /dev/null @@ -1,3 +0,0 @@ -.vc-switch-slider { - transition: 100ms transform ease-in-out; -} diff --git a/src/components/settings/VencordTab.css b/src/components/settings/VencordTab.css deleted file mode 100644 index 2ccd0c25c..000000000 --- a/src/components/settings/VencordTab.css +++ /dev/null @@ -1,49 +0,0 @@ -:root{ - --vc-donatebutton: #ffc1cbc2; - --vc-donatebutton-secondary: #f7536fe5; -} - -.vc-donatebutton{ - background-color: var(--background-color) !important; - border-color: var(--vc-donatebutton) !important; - filter: brightness(1); - border-width: .1rem !important; - border-style: solid !important; -} - -.vc-donatebutton:hover{ - background-color: var(--vc-donatebutton) !important; - border-color: transparent !important; -} - - -.vc-joindiscordbutton{ - background-color: var(--button-secondary-background) !important; -} - -.vc-joindiscordbutton:hover{ - background-color: var(--button-secondary-background-hover) !important; -} - -.vc-settings-card-buttons{ - display: flex; - gap: 1em !important; - margin-left: .2rem; - margin-bottom: .2rem; -} - -.vc-settingbuttons{ - border-radius: 5px !important; -} - -.form-switch-warning { - font-size: 16px; - background-color: rgb(240 71 71 / 10%); - color: rgb(240 71 71); - border: 1px solid rgb(240 71 71 / 60%); - border-radius: 4px; - font-weight: 500; - padding: 6px 10px; - text-align: center; - margin-top: 10px; -} diff --git a/src/components/settings/index.ts b/src/components/settings/index.ts index c793ef38c..647721219 100644 --- a/src/components/settings/index.ts +++ b/src/components/settings/index.ts @@ -4,10 +4,10 @@ * SPDX-License-Identifier: GPL-3.0-or-later */ +export * from "../Switch"; export * from "./AddonCard"; export * from "./DonateButton"; export * from "./PluginBadge"; export * from "./QuickAction"; export * from "./SpecialCard"; -export * from "./Switch"; export * from "./tabs"; diff --git a/src/components/settings/tabs/BaseTab.tsx b/src/components/settings/tabs/BaseTab.tsx index 9ef95463c..b4c0ebb53 100644 --- a/src/components/settings/tabs/BaseTab.tsx +++ b/src/components/settings/tabs/BaseTab.tsx @@ -18,32 +18,39 @@ import ErrorBoundary from "@components/ErrorBoundary"; import { handleComponentFailed } from "@components/handleComponentFailed"; -import { Margins } from "@utils/margins"; +import { ModalCloseButton, ModalContent, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal"; import { onlyOnce } from "@utils/onlyOnce"; -import { Forms, Text } from "@webpack/common"; import type { ComponentType, PropsWithChildren } from "react"; -export function SettingsTab({ title, children }: PropsWithChildren<{ title: string; }>) { +export function SettingsTab({ children }: PropsWithChildren) { return ( - - - {title} - - - {children} - +
{children}
); } export const handleSettingsTabError = onlyOnce(handleComponentFailed); export function wrapTab(component: ComponentType, tab: string) { - return ErrorBoundary.wrap(component, { + const wrapped = ErrorBoundary.wrap(component, { + displayName: `${tab}SettingsTab`, message: `Failed to render the ${tab} tab. If this issue persists, try using the installer to reinstall!`, onError: handleSettingsTabError, }); + + return wrapped; +} + +export function openSettingsTabModal(Tab: ComponentType) { + try { + openModal(wrapTab((modalProps: ModalProps) => ( + + + + + + + ), Tab.displayName || "Settings Tab")); + } catch { + handleSettingsTabError(); + } } diff --git a/src/components/settings/tabs/changelog/NewPluginsSection.tsx b/src/components/settings/tabs/changelog/NewPluginsSection.tsx new file mode 100644 index 000000000..fc3a3eba4 --- /dev/null +++ b/src/components/settings/tabs/changelog/NewPluginsSection.tsx @@ -0,0 +1,283 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2025 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { useSettings } from "@api/Settings"; +import { classNameFactory } from "@api/Styles"; +import { Button } from "@components/Button"; +import { Card } from "@components/Card"; +import { Heading } from "@components/Heading"; +import { Paragraph } from "@components/Paragraph"; +import { PluginCard } from "@components/settings/tabs/plugins/PluginCard"; +import { ChangeList } from "@utils/ChangeList"; +import { Margins } from "@utils/margins"; +import { useForceUpdater } from "@utils/react"; +import { React, Tooltip } from "@webpack/common"; + +import Plugins from "~plugins"; + +const cl = classNameFactory("vc-changelog-"); + +interface NewPluginsSectionProps { + newPlugins: string[]; + onPluginToggle?: (pluginName: string, enabled: boolean) => void; +} + +export function NewPluginsSection({ + newPlugins, + onPluginToggle, +}: NewPluginsSectionProps) { + const settings = useSettings(); + const changes = React.useMemo(() => new ChangeList(), []); + const forceUpdate = useForceUpdater(); + + const depMap = React.useMemo(() => { + const o = {} as Record; + for (const plugin in Plugins) { + const deps = Plugins[plugin].dependencies; + if (deps) { + for (const dep of deps) { + o[dep] ??= []; + o[dep].push(plugin); + } + } + } + return o; + }, []); + + const mapPlugins = (array: string[]) => + array + .map(pn => Plugins[pn]) + .filter(p => p && !p.hidden) + .sort((a, b) => a.name.localeCompare(b.name)); + + const sortedPlugins = React.useMemo( + () => mapPlugins(newPlugins), + [newPlugins], + ); + + if (sortedPlugins.length === 0) { + return null; + } + + const makeDependencyList = (deps: string[]) => { + if (!deps) return null; + return ( + + This plugin is required by: + {deps.map((dep: string) => ( + + {dep} + + ))} + + ); + }; + + return ( +
+ + New Plugins ({sortedPlugins.length}) + + + + The following plugins have been added in recent updates: + + +
+ {sortedPlugins.map(plugin => { + const isRequired = + plugin.required || + depMap[plugin.name]?.some( + d => settings.plugins[d].enabled, + ) || + plugin.name.endsWith("API"); + const tooltipText = plugin.required + ? "This plugin is required for Equicord to function." + : makeDependencyList( + depMap[plugin.name]?.filter( + d => settings.plugins[d].enabled, + ), + ); + + if (isRequired) { + return ( + + {({ onMouseLeave, onMouseEnter }) => ( + + { + changes.handleChange(name); + forceUpdate(); + }} + disabled={true} + plugin={plugin} + isNew={true} + /> + + )} + + ); + } + + return ( + + { + changes.handleChange(name); + forceUpdate(); + }} + disabled={false} + plugin={plugin} + isNew={true} + /> + + ); + })} +
+ + {changes.hasChanges && ( +
+ + The following plugins require a restart: +
+
    + {changes.map(p => ( +
  • {p}
  • + ))} +
+ + } + > + {tooltipProps => ( + + )} + +
+ )} +
+ ); +} + +interface NewPluginsCompactProps { + newPlugins: string[]; + maxDisplay?: number; +} + +function CompactPluginCard({ + pluginName, + depMap, + settings, +}: { + pluginName: string; + depMap: Record; + settings: any; +}) { + const plugin = Plugins[pluginName]; + if (!plugin || plugin.hidden) return null; + + const isRequired = + plugin.required || + depMap[plugin.name]?.some(d => settings.plugins[d].enabled); + + const tooltipText = plugin.required + ? "This plugin is required for Equicord to function." + : depMap[plugin.name]?.length > 0 + ? `This plugin is required by: ${depMap[plugin.name] + ?.filter(d => settings.plugins[d].enabled) + .join(", ")}` + : null; + + return ( +
+
+ + {plugin.name} + {isRequired && " *"} + + + {plugin.authors?.[0]?.name || "Unknown"} + +
+
+ {plugin.description || "No description available"} +
+ {tooltipText && ( +
{tooltipText}
+ )} +
+ ); +} + +export function NewPluginsCompact({ + newPlugins, + maxDisplay = 20, +}: NewPluginsCompactProps) { + const settings = useSettings(); + + const depMap = React.useMemo(() => { + const o = {} as Record; + for (const plugin in Plugins) { + const deps = Plugins[plugin].dependencies; + if (deps) { + for (const dep of deps) { + o[dep] ??= []; + o[dep].push(plugin); + } + } + } + return o; + }, []); + + if (newPlugins.length === 0) { + return null; + } + + const displayPlugins = newPlugins.slice(0, maxDisplay); + const hasMore = newPlugins.length > maxDisplay; + + return ( +
+
+ {displayPlugins.map(pluginName => ( + + ))} + + {hasMore && ( +
+
+ +{newPlugins.length - maxDisplay} more plugins +
+
+ )} +
+
+ ); +} diff --git a/src/components/settings/tabs/changelog/changelogManager.ts b/src/components/settings/tabs/changelog/changelogManager.ts new file mode 100644 index 000000000..c7ea9730c --- /dev/null +++ b/src/components/settings/tabs/changelog/changelogManager.ts @@ -0,0 +1,490 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2025 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { DataStore } from "@api/index"; + +import gitHash from "~git-hash"; +import plugins from "~plugins"; + +export interface ChangelogEntry { + hash: string; + author: string; + message: string; + timestamp?: number; +} + +export interface UpdateSession { + id: string; + timestamp: number; + fromHash: string; + toHash: string; + commits: ChangelogEntry[]; + newPlugins: string[]; + updatedPlugins: string[]; + newSettings?: Map | Record; + type: "update" | "repository_fetch"; +} + +export type ChangelogHistory = UpdateSession[]; + +const CHANGELOG_HISTORY_KEY = "EquicordChangelog_History"; +const LAST_SEEN_HASH_KEY = "EquicordChangelog_LastSeenHash"; +const KNOWN_PLUGINS_KEY = "EquicordChangelog_KnownPlugins"; +const KNOWN_SETTINGS_KEY = "EquicordChangelog_KnownSettings"; +const LAST_REPO_CHECK_KEY = "EquicordChangelog_LastRepoCheck"; +const GITHUB_COMPARE_ENDPOINT = "https://api.github.com/repos"; + +type KnownPluginSettingsMap = Map>; + +function normalizeRepoUrl(repoUrl: string | null | undefined): string | null { + if (!repoUrl) return null; + try { + const normalized = repoUrl.replace(/^git\+/, ""); + const url = new URL(normalized); + if (!url.hostname.endsWith("github.com")) return null; + const segments = url.pathname.replace(/\.git$/, "").split("/").filter(Boolean); + if (segments.length < 2) return null; + return `${segments[0]}/${segments[1]}`; + } catch { + return null; + } +} + +async function fetchCommitsBetween( + repoSlug: string, + fromHash: string, + toHash: string, +): Promise { + if (!repoSlug || typeof fetch !== "function") return []; + try { + const res = await fetch( + `${GITHUB_COMPARE_ENDPOINT}/${repoSlug}/compare/${fromHash}...${toHash}`, + { + headers: { + Accept: "application/vnd.github+json", + "Cache-Control": "no-cache", + }, + }, + ); + + if (!res.ok) return []; + const data = await res.json(); + if (!data || !Array.isArray(data.commits)) return []; + + return data.commits.map((commit: any) => { + const message: string = commit?.commit?.message ?? ""; + const summary = message.split("\n")[0] || "No message"; + const authorName = + commit?.commit?.author?.name || + commit?.author?.login || + "Unknown"; + const timestamp = commit?.commit?.author?.date + ? Date.parse(commit.commit.author.date) + : undefined; + + return { + hash: commit?.sha || "", + author: authorName, + message: summary, + timestamp: Number.isNaN(timestamp) ? undefined : timestamp, + } as ChangelogEntry; + }); + } catch (err) { + console.warn("Failed to fetch commits between hashes", err); + return []; + } +} + +function toStringSet(value: unknown): Set { + const result = new Set(); + + const addValue = (entry: unknown) => { + if (entry === undefined || entry === null) return; + result.add(typeof entry === "string" ? entry : String(entry)); + }; + + if (value instanceof Set) { + value.forEach(addValue); + } else if (value instanceof Map) { + value.forEach(addValue); + } else if (Array.isArray(value)) { + value.forEach(addValue); + } else if (typeof value === "string") { + addValue(value); + } else if (value && typeof value === "object") { + Object.values(value as Record).forEach(addValue); + } + + return result; +} + +function normalizeKnownSettings(value: unknown): KnownPluginSettingsMap { + const map: KnownPluginSettingsMap = new Map(); + + const assign = (plugin: unknown, settings: unknown) => { + if (plugin === undefined || plugin === null) return; + map.set(String(plugin), toStringSet(settings)); + }; + + if (!value) { + return map; + } + + if (value instanceof Map) { + value.forEach((settings, plugin) => assign(plugin, settings)); + return map; + } + + if (Array.isArray(value)) { + value.forEach(entry => { + if (Array.isArray(entry) && entry.length > 0) { + assign(entry[0], entry[1]); + } + }); + return map; + } + + if (typeof value === "object") { + Object.entries(value as Record).forEach( + ([plugin, settings]) => assign(plugin, settings), + ); + } + + return map; +} + +function serializeKnownSettings( + map: KnownPluginSettingsMap, +): Record { + return Object.fromEntries( + Array.from(map.entries()).map(([plugin, settings]) => [ + plugin, + Array.from(settings), + ]), + ); +} + +async function persistKnownSettings( + map: KnownPluginSettingsMap, +): Promise { + await DataStore.set(KNOWN_SETTINGS_KEY, serializeKnownSettings(map)); +} + +function isMapLike(value: any): value is Map { + return ( + value && + typeof value.get === "function" && + typeof value.size === "number" + ); +} + +export function getNewSettingsSize( + newSettings: Map | Record | undefined, +): number { + if (!newSettings) return 0; + if (isMapLike(newSettings)) return newSettings.size; + return Object.keys(newSettings).length; +} + +export function getNewSettingsEntries( + newSettings: Map | Record | undefined, +): [string, string[]][] { + if (!newSettings) return []; + if (isMapLike(newSettings)) return Array.from(newSettings.entries()); + return Object.entries(newSettings); +} + +export async function getChangelogHistory(): Promise { + const history = (await DataStore.get( + CHANGELOG_HISTORY_KEY, + )) as ChangelogHistory; + + if (history) { + history.forEach(session => { + if (session.newSettings && !(session.newSettings instanceof Map)) { + session.newSettings = new Map( + Object.entries(session.newSettings), + ); + } + }); + } + + return history || []; +} + +export async function saveUpdateSession( + commits: ChangelogEntry[], + newPlugins: string[], + updatedPlugins: string[], + newSettings: Map, + forceLog: boolean = false, +): Promise { + const history = await getChangelogHistory(); + const lastSeenHash = await getLastSeenHash(); + const currentHash = gitHash; + + // For repository fetches, check if we already have this exact state logged (to prevent duplicate logs) + if (forceLog) { + const lastRepoCheck = await getLastRepositoryCheckHash(); + const latestRepoHash = + commits.length > 0 ? commits[0].hash : currentHash; + + if (lastRepoCheck === latestRepoHash) { + // if the state hasn't changed last check, do NOT make a new log + return; + } + } + + // Don't save if no changes, unless explicitly forcing the log (for example repository fetch) + if ( + !forceLog && + commits.length === 0 && + newPlugins.length === 0 && + updatedPlugins.length === 0 && + getNewSettingsSize(newSettings) === 0 + ) { + return; + } + + // Determine session type and hash logic + const sessionType = forceLog ? "repository_fetch" : "update"; + let fromHash = currentHash; + let toHash = currentHash; + + if (forceLog) { + // This is a repository fetch - show current vs repository state + if (commits.length > 0) { + // Repository has newer commits + toHash = commits[0].hash; // Latest repository hash + } + // If no commits, fromHash === toHash (up to date) + } else { + // This is an actual update session + // Is there a better way to do this? + fromHash = lastSeenHash || "unknown"; + toHash = currentHash; + } + + const session: UpdateSession = { + id: crypto.randomUUID(), + timestamp: Date.now(), + fromHash: fromHash, + toHash: toHash, + commits, + newPlugins, + updatedPlugins, + newSettings: + getNewSettingsSize(newSettings) > 0 + ? Object.fromEntries(newSettings) + : undefined, + type: sessionType, + }; + + // Add to beginning of history (most recent first) + history.unshift(session); + + // Keep only last 50 sessions to prevent storage bloat + if (history.length > 50) { + history.splice(50); + } + + await DataStore.set(CHANGELOG_HISTORY_KEY, history); + + if (!forceLog) { + await setLastSeenHash(currentHash); + } else { + // for fetches, check the latest repo hash to make sure its not the same + const latestRepoHash = + commits.length > 0 ? commits[0].hash : currentHash; + await setLastRepositoryCheckHash(latestRepoHash); + } + + await updateKnownPlugins(); + await updateKnownSettings(); +} + +export async function getLastSeenHash(): Promise { + return (await DataStore.get(LAST_SEEN_HASH_KEY)) as string | null; +} + +export async function setLastSeenHash(hash: string): Promise { + await DataStore.set(LAST_SEEN_HASH_KEY, hash); +} + +export async function getKnownPlugins(): Promise> { + const known = (await DataStore.get(KNOWN_PLUGINS_KEY)) as string[]; + return new Set(known || []); +} + +export async function updateKnownPlugins(): Promise { + const currentPlugins = Object.keys(plugins); + await DataStore.set(KNOWN_PLUGINS_KEY, currentPlugins); +} + +function getSettingsSetForPlugin(plugin: string): Set { + const settings = plugins[plugin]?.settings?.def || {}; + return new Set( + Object.keys(settings).filter(setting => setting !== "enabled"), + ); +} + +function getCurrentSettings(pluginList: string[]): KnownPluginSettingsMap { + return new Map( + pluginList.map(name => [name, getSettingsSetForPlugin(name)]), + ); +} + +export async function getKnownSettings(): Promise { + const mapData = (await DataStore.get(KNOWN_SETTINGS_KEY)) as any; + if (mapData === undefined) { + const knownPlugins = await getKnownPlugins(); + const pluginNames = [ + ...new Set([ + ...Object.keys(plugins), + ...Array.from(knownPlugins), + ]), + ]; + const initialMap = getCurrentSettings(pluginNames); + await persistKnownSettings(initialMap); + return initialMap; + } + + const normalized = normalizeKnownSettings(mapData); + + if ( + mapData instanceof Map || + Array.isArray(mapData) || + (mapData && + typeof mapData === "object" && + Object.values(mapData).some(value => + value instanceof Set || value instanceof Map, + )) + ) { + await persistKnownSettings(normalized); + } + + return normalized; +} + +export async function getNewSettings(): Promise> { + const map = getCurrentSettings(Object.keys(plugins)); + const knownSettings = await getKnownSettings(); + const newSettings = new Map(); + + map.forEach((settings, plugin) => { + const known = knownSettings.get(plugin); + if (!known) return; + + const filteredSettings = [...settings].filter( + setting => !known.has(setting), + ); + if (filteredSettings.length > 0) { + newSettings.set(plugin, filteredSettings); + } + }); + + return newSettings; +} + +export async function getCommitsSinceLastSeen( + repoUrl: string, +): Promise { + const lastSeenHash = await getLastSeenHash(); + if (!lastSeenHash || lastSeenHash === "unknown" || lastSeenHash === gitHash) + return []; + + const repoSlug = normalizeRepoUrl(repoUrl); + if (!repoSlug) return []; + + return fetchCommitsBetween(repoSlug, lastSeenHash, gitHash); +} + +export async function updateKnownSettings(): Promise { + const currentSettings = getCurrentSettings(Object.keys(plugins)); + const knownSettings = await getKnownSettings(); + const mergedSettings: KnownPluginSettingsMap = new Map(); + + new Set([...currentSettings.keys(), ...knownSettings.keys()]).forEach( + plugin => { + mergedSettings.set( + plugin, + new Set([ + ...(knownSettings.get(plugin) || []), + ...(currentSettings.get(plugin) || []), + ]), + ); + }, + ); + + await persistKnownSettings(mergedSettings); +} + +export async function getNewPlugins(): Promise { + const currentPlugins = Object.keys(plugins); + const knownPlugins = await getKnownPlugins(); + + return currentPlugins.filter( + plugin => + !knownPlugins.has(plugin) && + !plugins[plugin].hidden && + !plugins[plugin].required, + ); +} + +export async function getUpdatedPlugins(): Promise { + // This is a placeholder - in a real implementation, you'd track plugin version changes + // For now, we'll return empty array since plugin version tracking would need to be implemented + return []; +} + +export async function clearChangelogHistory(): Promise { + await DataStore.del(CHANGELOG_HISTORY_KEY); + await DataStore.del(LAST_SEEN_HASH_KEY); + await DataStore.del(KNOWN_SETTINGS_KEY); +} + +export async function clearIndividualLog(logId: string): Promise { + const history = await getChangelogHistory(); + const filteredHistory = history.filter(log => log.id !== logId); + await DataStore.set(CHANGELOG_HISTORY_KEY, filteredHistory); +} + +export async function initializeChangelog(): Promise { + // Initialize with current state if first time + const lastSeenHash = await getLastSeenHash(); + if (!lastSeenHash) { + await setLastSeenHash(gitHash); + await updateKnownPlugins(); + await updateKnownSettings(); + } +} + +export async function getLastRepositoryCheckHash(): Promise { + return (await DataStore.get(LAST_REPO_CHECK_KEY)) as string | null; +} + +export async function setLastRepositoryCheckHash(hash: string): Promise { + await DataStore.set(LAST_REPO_CHECK_KEY, hash); +} + +export function formatTimestamp(timestamp: number): string { + const date = new Date(timestamp); + const now = new Date(); + const diffMs = now.getTime() - date.getTime(); + const diffMinutes = Math.floor(diffMs / (1000 * 60)); + const diffHours = Math.floor(diffMs / (1000 * 60 * 60)); + const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24)); + + if (diffMinutes < 60) { + return `${diffMinutes} minute${diffMinutes !== 1 ? "s" : ""} ago`; + } else if (diffHours < 24) { + return `${diffHours} hour${diffHours !== 1 ? "s" : ""} ago`; + } else if (diffDays < 7) { + return `${diffDays} day${diffDays !== 1 ? "s" : ""} ago`; + } else { + return date.toLocaleDateString(); + } +} diff --git a/src/components/settings/tabs/changelog/index.tsx b/src/components/settings/tabs/changelog/index.tsx new file mode 100644 index 000000000..c630700e9 --- /dev/null +++ b/src/components/settings/tabs/changelog/index.tsx @@ -0,0 +1,690 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2025 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import "./styles.css"; + +import { Button } from "@components/Button"; +import { Card } from "@components/Card"; +import { Divider } from "@components/Divider"; +import { ErrorCard } from "@components/ErrorCard"; +import { Heading } from "@components/Heading"; +import { DeleteIcon } from "@components/Icons"; +import { Link } from "@components/Link"; +import { Paragraph } from "@components/Paragraph"; +import { SettingsTab, wrapTab } from "@components/settings/tabs/BaseTab"; +import { HashLink } from "@components/settings/tabs/updater/Components"; +import { Margins } from "@utils/margins"; +import { useAwaiter } from "@utils/react"; +import { getRepo, UpdateLogger } from "@utils/updater"; +import { Alerts, React, Toasts } from "@webpack/common"; + +import gitHash from "~git-hash"; + +import { + ChangelogEntry, + ChangelogHistory, + clearChangelogHistory, + clearIndividualLog, + formatTimestamp, + getChangelogHistory, + getCommitsSinceLastSeen, + getLastRepositoryCheckHash, + getNewPlugins, + getNewSettings, + getNewSettingsEntries, + getNewSettingsSize, + getUpdatedPlugins, + initializeChangelog, + saveUpdateSession, + UpdateSession, +} from "./changelogManager"; +import { NewPluginsCompact, NewPluginsSection } from "./NewPluginsSection"; + +function ChangelogCard({ + entry, + repo, + repoPending, +}: { + entry: ChangelogEntry; + repo: string; + repoPending: boolean; +}) { + return ( + +
+
+ + + + + by {entry.author} + +
+
+ {entry.message} +
+
+
+ ); +} + +function UpdateLogCard({ + log, + repo, + repoPending, + isExpanded, + onToggleExpand, + onClearLog, +}: { + log: UpdateSession; + repo: string; + repoPending: boolean; + isExpanded: boolean; + onToggleExpand: () => void; + onClearLog: (logId: string) => void; +}) { + const isRepositoryFetch = + log.type === "repository_fetch" || + (log.type === undefined && + log.fromHash === log.toHash && + log.commits.length === 0); + const isUpToDate = log.fromHash === log.toHash; + + return ( + +
+
+
+ + {isRepositoryFetch + ? isUpToDate + ? `Repository check: ${log.fromHash.slice(0, 7)} (up to date)` + : `Repository check: ${log.fromHash.slice(0, 7)} → ${log.toHash.slice(0, 7)}` + : `Update: ${log.fromHash.slice(0, 7)} → ${log.toHash.slice(0, 7)}`} + + +
+
+ {formatTimestamp(log.timestamp)} + {log.commits.length > 0 && + ` • ${log.commits.length} commits available`} + {log.commits.length === 0 && " • No new commits"} + {log.newPlugins.length > 0 && + ` • ${log.newPlugins.length} new plugins`} + {log.updatedPlugins.length > 0 && + ` • ${log.updatedPlugins.length} updated plugins`} + {log.newSettings && + getNewSettingsSize(log.newSettings) > 0 && + ` • ${getNewSettingsEntries(log.newSettings).reduce((sum, [, arr]) => sum + arr.length, 0)} new settings`} +
+
+
+ ▼ +
+
+ + {isExpanded && ( +
+ {log.newPlugins.length > 0 && ( +
+ +
+ )} + + {log.updatedPlugins.length > 0 && ( +
+ + Updated Plugins + + +
+ )} + + {log.newSettings && + getNewSettingsSize(log.newSettings) > 0 && ( +
+ + New Settings + +
+ {getNewSettingsEntries(log.newSettings).map( + ([pluginName, settings]) => + settings.map(setting => ( + + {pluginName}.{setting} + + )), + )} +
+
+ )} + + {log.commits.length > 0 && ( +
+
+ {log.commits.map(entry => ( + + ))} +
+
+ )} +
+ )} +
+ ); +} + +function ChangelogContent() { + const [repo, repoErr, repoPending] = useAwaiter(getRepo, { + fallbackValue: "Loading...", + }); + const [changelog, setChangelog] = React.useState([]); + const [changelogHistory, setChangelogHistory] = + React.useState([]); + const [newPlugins, setNewPlugins] = React.useState([]); + const [updatedPlugins, setUpdatedPlugins] = React.useState([]); + const [isLoading, setIsLoading] = React.useState(true); + const [error, setError] = React.useState(null); + const [expandedLogs, setExpandedLogs] = React.useState>( + new Set(), + ); + const [showHistory, setShowHistory] = React.useState(false); + const [recentlyChecked, setRecentlyChecked] = React.useState(false); + + React.useEffect(() => { + const init = async () => { + try { + await initializeChangelog(); + await loadChangelogHistory(); + } catch (err) { + console.error("Failed to initialize changelog:", err); + } + }; + init(); + }, []); + + React.useEffect(() => { + if (repoErr) { + UpdateLogger.error("Failed to retrieve repo", repoErr); + setError("Failed to retrieve repository information"); + } + }, [repoErr]); + + const loadChangelogHistory = React.useCallback(async () => { + try { + const history = await getChangelogHistory(); + setChangelogHistory(history); + } catch (err) { + console.error("Failed to load changelog history:", err); + } + }, []); + + const loadNewPlugins = React.useCallback(async () => { + try { + const newPlgs = await getNewPlugins(); + const updatedPlgs = await getUpdatedPlugins(); + setNewPlugins(newPlgs); + setUpdatedPlugins(updatedPlgs); + } catch (err) { + console.error("Failed to load new plugins:", err); + } + }, []); + + const ensureLocalUpdateLogged = React.useCallback(async () => { + if (repoPending || repoErr) return false; + const repoUrl = repo; + if (!repoUrl) return false; + + try { + const commits = await getCommitsSinceLastSeen(repoUrl); + if (commits.length === 0) return false; + + const newPlgs = await getNewPlugins(); + const updatedPlgs = await getUpdatedPlugins(); + const newSettings = await getNewSettings(); + + await saveUpdateSession(commits, newPlgs, updatedPlgs, newSettings); + + setChangelog(commits); + setNewPlugins(newPlgs); + setUpdatedPlugins(updatedPlgs); + await loadChangelogHistory(); + return true; + } catch (err) { + console.error("Failed to log local update:", err); + return false; + } + }, [repo, repoErr, repoPending, loadChangelogHistory]); + + // check if the repository was recently refreshed + React.useEffect(() => { + const checkRecentStatus = async () => { + try { + const lastRepoCheck = await getLastRepositoryCheckHash(); + const updates = await VencordNative.updater.getUpdates(); + + if (updates.ok) { + const currentRepoHash = + updates.value.length > 0 + ? updates.value[0].hash + : gitHash; + setRecentlyChecked(lastRepoCheck === currentRepoHash); + } + } catch (err) { + // ignore errors (hopefully there are none lol) + setRecentlyChecked(false); + } + }; + + if (!repoPending && !repoErr) { + checkRecentStatus(); + } + }, [repoPending, repoErr]); + + const fetchChangelog = React.useCallback(async () => { + if (repoPending || repoErr) return; + + setIsLoading(true); + setError(null); + + try { + // check if the repository was recently refreshed and that nothing has changed + const updates = await VencordNative.updater.getUpdates(); + const lastRepoCheck = await getLastRepositoryCheckHash(); + const currentRepoHash = + updates.ok && updates.value.length > 0 + ? updates.value[0].hash + : gitHash; + + // If repository state hasn't changed since last check + if (lastRepoCheck === currentRepoHash) { + setIsLoading(false); + setRecentlyChecked(true); + const logged = await ensureLocalUpdateLogged(); + if (!logged) { + setChangelog([]); + Toasts.show({ + message: "Already up to date with repository", + id: Toasts.genId(), + type: Toasts.Type.MESSAGE, + options: { + position: Toasts.Position.BOTTOM, + }, + }); + } + return; + } + + if (updates.ok && updates.value) { + if (updates.value.length > 0) { + setChangelog(updates.value); + + const newPlgs = await getNewPlugins(); + const updatedPlgs = await getUpdatedPlugins(); + const newSettings = await getNewSettings(); + setNewPlugins(newPlgs); + setUpdatedPlugins(updatedPlgs); + + await saveUpdateSession( + updates.value, + newPlgs, + updatedPlgs, + newSettings, + true, + ); + await loadChangelogHistory(); + setRecentlyChecked(true); + + Toasts.show({ + message: `Found ${updates.value.length} commit${updates.value.length === 1 ? "" : "s"} from repository`, + id: Toasts.genId(), + type: Toasts.Type.SUCCESS, + options: { + position: Toasts.Position.BOTTOM, + }, + }); + } else { + const logged = await ensureLocalUpdateLogged(); + setRecentlyChecked(true); + Toasts.show({ + message: logged + ? "Logged commits from your latest update" + : "Repository is up to date with your local copy", + id: Toasts.genId(), + type: logged ? Toasts.Type.SUCCESS : Toasts.Type.MESSAGE, + options: { + position: Toasts.Position.BOTTOM, + }, + }); + if (!logged) { + setChangelog([]); + } + } + } else if (!updates.ok) { + throw new Error( + updates.error?.message || "Failed to fetch from repository", + ); + } + } catch (err: any) { + UpdateLogger.error("Failed to fetch commits from repository", err); + const errorMessage = + err?.message || + "Failed to connect to repository. Check your internet connection."; + setError(errorMessage); + + // funny little error toast hopefully doesn't happen! + Toasts.show({ + message: "Could not fetch commits from repository", + id: Toasts.genId(), + type: Toasts.Type.FAILURE, + options: { + position: Toasts.Position.BOTTOM, + }, + }); + } finally { + setIsLoading(false); + } + }, [repoPending, repoErr, loadNewPlugins, loadChangelogHistory]); + + React.useEffect(() => { + const loadInitialData = async () => { + if (!repoPending && !repoErr) { + await loadNewPlugins(); + const logged = await ensureLocalUpdateLogged(); + if (!logged) { + await fetchChangelog(); + } else { + setIsLoading(false); + } + } else if (!repoPending) { + // perseverance + await loadNewPlugins(); + setIsLoading(false); + } + }; + loadInitialData(); + }, [ + repoPending, + repoErr, + fetchChangelog, + loadNewPlugins, + ensureLocalUpdateLogged, + ]); + + const toggleLogExpanded = (logId: string) => { + const newExpanded = new Set(expandedLogs); + if (newExpanded.has(logId)) { + newExpanded.delete(logId); + } else { + newExpanded.add(logId); + } + setExpandedLogs(newExpanded); + }; + + const hasCurrentChanges = + changelog.length > 0 || + newPlugins.length > 0 || + updatedPlugins.length > 0; + + return ( + <> + Fetch Changes + + Check the repository for new commits, plugin updates, and code changes. This will compare your current version with the latest available and show you what's new. + + +
+ + + {changelogHistory.length > 0 && ( + <> + + + + )} +
+ + {error && ( + + {error} + + Make sure you have an internet connection and try again. + + + )} + + + + Repository + + This is the GitHub repository where Equicord fetches updates from. + + + {repoPending ? ( + repo + ) : repoErr ? ( + "Failed to retrieve - check console" + ) : ( + + {repo.split("/").slice(-2).join("/")} + + )} + {" "}() + + + {hasCurrentChanges && ( + <> + + + Recent Changes + + These are the new commits and plugin updates since your last version. You can see what features were added, bugs were fixed, and which plugins received updates. + + + {newPlugins.length > 0 && ( +
+ { }} + /> +
+ )} + + {updatedPlugins.length > 0 && ( +
+ + Updated Plugins ({updatedPlugins.length}) + + +
+ )} + + {changelog.length > 0 && ( +
+ + Code Changes ({changelog.length} {changelog.length === 1 ? "commit" : "commits"}) + +
+ {changelog.map(entry => ( + + ))} +
+
+ )} + + )} + + {!hasCurrentChanges && !isLoading && !error && ( + <> + + Recent Changes + + No commits available ahead of your current version. Click "Fetch from Repository" to check for new changes. + + + )} + + {showHistory && changelogHistory.length > 0 && ( + <> + + + + Update Logs ({changelogHistory.length} {changelogHistory.length === 1 ? "log" : "logs"}) + + + A history of your previous update sessions with their commit history and plugin changes. Click on a log to expand it and see the details. + + +
+ {changelogHistory.map(log => ( + toggleLogExpanded(log.id)} + onClearLog={logId => { + Alerts.show({ + title: "Clear Log", + body: "Are you sure you would like to clear this log? This can't be undone.", + confirmText: "Clear Log", + confirmColor: "danger", + cancelText: "Cancel", + onConfirm: async () => { + await clearIndividualLog(logId); + await loadChangelogHistory(); + setExpandedLogs( + new Set( + Array.from(expandedLogs).filter(id => id !== logId), + ), + ); + Toasts.show({ + message: "Log has been cleared", + id: Toasts.genId(), + type: Toasts.Type.SUCCESS, + options: { + position: Toasts.Position.BOTTOM, + }, + }); + }, + }); + }} + /> + ))} +
+ + )} + + ); +} + +function ChangelogTab() { + return ( + + + + ); +} + +export default wrapTab(ChangelogTab, "Changelog"); diff --git a/src/components/settings/tabs/changelog/styles.css b/src/components/settings/tabs/changelog/styles.css new file mode 100644 index 000000000..b10fe91c9 --- /dev/null +++ b/src/components/settings/tabs/changelog/styles.css @@ -0,0 +1,571 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2025 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +/* Existing changelog styles */ +.vc-changelog-entry { + padding: 0.75em; + margin-bottom: 0.5em; + background-color: var(--background-base-lower-alt); + border-radius: 8px; + transition: 0.1s ease-out; + transition-property: box-shadow, transform, background, opacity; +} + +.vc-changelog-entry:hover { + background-color: var(--background-base-low); + transform: translateY(-1px); + box-shadow: var(--elevation-high); +} + +.vc-changelog-entry.required { + border-left: 4px solid var(--status-warning); + background-color: var(--background-base-lower-alt); + padding-left: calc(0.75em - 4px); +} + +.vc-changelog-entry.required:hover { + background-color: var(--background-base-low); + border-left-color: var(--status-warning-background); +} + +.vc-changelog-entry-header { + display: flex; + align-items: center; + gap: 0.5em; + margin-bottom: 0.25em; +} + +.vc-changelog-entry-hash { + font-size: 0.875em; + font-family: var(--font-code); + color: var(--text-subtle); +} + +.vc-changelog-entry-author { + font-size: 0.875em; + color: var(--text-subtle); + font-weight: 500; +} + +.vc-changelog-entry-message { + color: var(--text-default); + font-size: 0.95em; + line-height: 1.4; +} + +.vc-changelog-entry.required .vc-changelog-entry-hash { + color: var(--text-subtle); + font-weight: 600; +} + +.vc-changelog-loading { + text-align: center; + padding: 2em; + color: var(--text-subtle); +} + +.vc-changelog-empty { + text-align: center; + padding: 2em; + background-color: var(--background-base-lower-alt); + border-radius: 8px; + color: var(--text-subtle); +} + +.vc-changelog-controls { + display: flex; + align-items: center; + flex-wrap: wrap; + gap: 0.5em; +} + +.vc-changelog-repo-info { + display: flex; + align-items: center; + gap: 0.5em; + margin-bottom: 1em; +} + +.vc-changelog-current-hash { + font-family: var(--font-code); + background-color: var(--background-base-lowest); + padding: 0.125em 0.25em; + border-radius: 3px; + font-size: 0.875em; +} + +/* New Plugins Section Styles */ +.vc-changelog-new-plugins-section { + margin-bottom: 1.5em; + padding: 1em; + background-color: var(--background-base-lower-alt); + border-radius: 8px; + border-left: 4px solid var(--brand); +} + +.vc-changelog-new-plugins-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); + gap: 0.75em; +} + +.vc-changelog-new-plugin-card { + background-color: var(--background-base-low); + border-radius: 6px; + padding: 0.5em; + transition: all 0.1s ease-out; +} + +.vc-changelog-new-plugin-card:hover { + background-color: var(--background-base-lowest); + transform: translateY(-1px); + box-shadow: var(--elevation-medium); +} + +.vc-changelog-new-plugin-card.required { + border-left: 4px solid var(--status-warning); + background-color: var(--background-base-lower-alt); +} + +.vc-changelog-new-plugin-card.required:hover { + background-color: var(--background-base-low); + border-left-color: var(--status-warning-background); +} + +.vc-changelog-new-plugins-compact { + display: flex; + flex-wrap: wrap; + gap: 0.5em; + margin-bottom: 0; + padding-top: 0; +} + +.vc-changelog-new-plugins-list { + display: flex; + flex-wrap: wrap; + gap: 0.5em; +} + +.vc-changelog-new-plugin-tag { + display: inline-flex; + align-items: center; + padding: 0.25em 0.5em; + background-color: var(--brand); + color: var(--white); + border-radius: 4px; + font-size: 0.875em; + font-weight: 500; + text-decoration: none; + transition: background-color 0.1s ease; +} + +.vc-changelog-new-plugin-tag:hover { + background-color: var(--brand-700); +} + +.vc-changelog-new-plugin-tag.more { + background-color: var(--background-base-lower-alt); + color: var(--text-subtle); +} + +.vc-changelog-new-plugin-tag.required { + background-color: var(--status-warning); + color: var(--white); + border: 1px solid var(--status-warning-background); +} + +.vc-changelog-new-plugin-tag.required:hover { + background-color: var(--status-warning-background); +} + +.vc-changelog-restart-notice { + margin-top: 1em; + padding: 0.75em; + background-color: var(--background-feedback-warning); + border-radius: 6px; + border-left: 4px solid var(--status-warning); +} + +/* Update Logs Styles */ +.vc-changelog-history { + margin-top: 1.5em; +} + +.vc-changelog-history-list { + display: flex; + flex-direction: column; + gap: 0.75em; + padding-right: 0.5em; + min-height: auto !important; + max-height: none !important; + height: auto !important; +} + +.vc-changelog-log { + background-color: var(--background-base-lower-alt); + border-radius: 8px; + overflow: hidden; + transition: all 0.1s ease-out; +} + +.vc-changelog-log:hover { + box-shadow: var(--elevation-medium); +} + +.vc-changelog-log-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 1em; + cursor: pointer; + transition: background-color 0.1s ease; + user-select: none; + gap: 0.5em; + position: relative; +} + +.vc-changelog-log-header:hover { + background-color: var(--background-base-low); +} + +.vc-changelog-log-info { + flex: 1; +} + +.vc-changelog-log-title { + font-weight: 600; + font-size: 0.95em; + color: var(--text-default); + margin-bottom: 0.25em; + font-family: var(--font-code); + display: flex; + align-items: center; + gap: 8px; + flex: 1; +} + +.vc-changelog-log-meta { + font-size: 0.875em; + color: var(--text-subtle); + line-height: 1.3; +} + +.vc-changelog-log-toggle { + transition: transform 0.2s ease; + color: var(--text-subtle); + font-size: 0.75em; + flex-shrink: 0; +} + +.vc-changelog-log-toggle.expanded { + transform: rotate(180deg); +} + +.vc-changelog-log-content { + padding: 0 1em 1em; + border-top: 1px solid var(--background-base-lowest); + animation: expand-content 0.2s ease-out; + + /* allow content to expand naturally (fixes cramming of elements) */ + min-height: auto !important; + max-height: none !important; + overflow: visible !important; +} + +.vc-changelog-log-plugins { + margin-bottom: 1em; + padding-top: 0.75em; +} + +.vc-changelog-log-plugins:last-child { + margin-bottom: 0; +} + +.vc-changelog-log-commits { + margin-bottom: 1em; + padding-top: 0.75em; +} + +.vc-changelog-log-commits:last-child { + margin-bottom: 0; +} + +.vc-changelog-log-commits-list { + display: flex; + flex-direction: column; + gap: 0.5em; + padding-right: 0.5em; + padding-top: 0; +} + +/* fix plugin entry padding to match commit entries (this took 3 different tries bro) */ +.vc-changelog-plugins-list .vc-changelog-entry { + padding: 0.75em; + margin-bottom: 0.5em; +} + +/* Visual refresh support */ +.visual-refresh .vc-changelog-entry { + background-color: var(--background-surface-high); + border: 1px solid var(--border-subtle); +} + +.vc-changelog-log-commits-list .vc-changelog-entry:last-child { + margin-bottom: 0; +} + +.vc-changelog-plugins-list { + display: flex; + flex-direction: column; + gap: 0.5em; + padding-right: 0.5em; + padding-top: 0; +} + +.visual-refresh .vc-changelog-entry.required { + border-left: 4px solid var(--status-warning); + background-color: var(--background-surface-high); + border: 1px solid var(--border-subtle); + padding-left: calc(0.75em - 4px); +} + +.vc-changelog-plugins-list .vc-changelog-entry:last-child { + margin-bottom: 0; +} + +/* consistent padding for required plugin entries in logs */ +.vc-changelog-plugins-list .vc-changelog-entry.required { + padding-left: calc(0.75em - 4px); + margin-bottom: 0.5em; +} + +.vc-changelog-plugins-list .vc-changelog-entry.required:last-child { + margin-bottom: 0; +} + +.vc-changelog-log-commits-list::-webkit-scrollbar { + width: 6px; +} + +.vc-changelog-log-commits-list::-webkit-scrollbar-track { + background: var(--background-base-lower-alt); + border-radius: 3px; +} + +.vc-changelog-log-commits-list::-webkit-scrollbar-thumb { + background: var(--text-subtle); + border-radius: 3px; +} + +.vc-changelog-log-commits-list::-webkit-scrollbar-thumb:hover { + background: var(--text-default); +} + +/* Current changes section */ +.vc-changelog-current { + margin-bottom: 1.5em; +} + +.vc-changelog-commits-list { + display: flex; + flex-direction: column; + gap: 0.5em; +} + +/* Scrollbar styling for history list and log content */ +.vc-changelog-history-list::-webkit-scrollbar, +.vc-changelog-log-content::-webkit-scrollbar { + width: 6px; +} + +.vc-changelog-history-list::-webkit-scrollbar-track, +.vc-changelog-log-content::-webkit-scrollbar-track { + background: var(--background-base-lower-alt); + border-radius: 3px; +} + +.vc-changelog-history-list::-webkit-scrollbar-thumb, +.vc-changelog-log-content::-webkit-scrollbar-thumb { + background: var(--text-subtle); + border-radius: 3px; +} + +.vc-changelog-history-list::-webkit-scrollbar-thumb:hover, +.vc-changelog-log-content::-webkit-scrollbar-thumb:hover { + background: var(--text-default); +} + +.visual-refresh .vc-changelog-entry:hover { + background-color: var(--background-surface-high); + box-shadow: 0 2px 8px rgb(0 0 0 / 10%); +} + +.visual-refresh .vc-changelog-empty { + background-color: var(--background-surface-high); + border: 1px solid var(--border-subtle); +} + +.visual-refresh .vc-changelog-current-hash { + background-color: var(--input-background); + border: 1px solid var(--border-subtle); +} + +.visual-refresh .vc-changelog-new-plugins-section { + background-color: var(--background-surface-high); + border: 1px solid var(--border-subtle); + border-left: 4px solid var(--brand); +} + +.visual-refresh .vc-changelog-new-plugin-card { + background-color: var(--card-secondary-bg); + border: 1px solid var(--border-subtle); +} + +.visual-refresh .vc-changelog-log { + background-color: var(--background-surface-high); + border: 1px solid var(--border-subtle); +} + +.visual-refresh .vc-changelog-log-header:hover { + background-color: var(--card-secondary-bg); +} + +.visual-refresh .vc-changelog-log-content { + border-top: 1px solid var(--border-subtle); +} + +.visual-refresh .vc-changelog-restart-notice { + background-color: var(--background-surface-high); + border: 1px solid var(--status-warning); + border-left: 4px solid var(--status-warning); +} + +/* Responsive design */ +@media (width <=768px) { + .vc-changelog-new-plugins-grid { + grid-template-columns: 1fr; + } + + .vc-changelog-controls { + flex-direction: column; + align-items: stretch; + } + + .vc-changelog-session-header { + flex-direction: column; + align-items: flex-start; + gap: 0.5em; + } + + .vc-changelog-log-toggle { + align-self: flex-end; + margin-top: -1.5em; + } + + .vc-changelog-log-title { + flex-wrap: wrap; + gap: 4px; + } + + .vc-changelog-delete-button { + order: 1; + } +} + +/* Animation for expanding/collapsing logs */ + +@keyframes expand-content { + from { + opacity: 0; + transform: translateY(-10px); + } + + to { + opacity: 1; + transform: translateY(0); + } +} + +/* Badge styles for plugin counts */ +.vc-changelog-badge { + display: inline-flex; + align-items: center; + padding: 0.125em 0.375em; + background-color: var(--brand); + color: var(--white); + border-radius: 12px; + font-size: 0.75em; + font-weight: 600; + margin-left: 0.5em; +} + +.vc-changelog-badge.warning { + background-color: var(--status-warning); +} + +.vc-changelog-badge.success { + background-color: var(--status-positive); +} + +/* Required plugin styling */ +.vc-changelog-dep-text { + font-size: 0.875em; + color: var(--text-subtle); + margin-left: 1em; + font-style: italic; +} + +/* Visual refresh support for required plugins */ +.visual-refresh .vc-changelog-new-plugin-tag.required { + background-color: var(--status-warning); + border: 1px solid var(--status-warning-background); +} + +.visual-refresh .vc-changelog-new-plugin-card.required { + border-left: 4px solid var(--status-warning); + background-color: var(--card-secondary-bg); +} + +/* Global overrides for changelog in settings modal */ +.vc-settings-tab [class*="changelog"] { + min-height: auto !important; + max-height: none !important; + overflow: visible !important; +} + +/* Ensure Forms.FormSection doesn't constrain changelog */ +.vc-settings-tab .vc-changelog-history, +.vc-settings-tab .vc-changelog-history-list, +.vc-settings-tab .vc-changelog-log-content { + min-height: auto !important; + max-height: none !important; + height: auto !important; + overflow: visible !important; +} + +.visual-refresh .vc-changelog-entry.required:hover { + background-color: var(--card-secondary-bg); +} + +/* Trash icon styling */ +.vc-changelog-delete-button { + transition: + opacity 0.2s ease, + color 0.2s ease, + background-color 0.2s ease; + border-radius: 4px; + flex-shrink: 0; +} + +.vc-changelog-delete-button:hover { + opacity: 1 !important; + color: var(--status-danger) !important; + background-color: var(--background-mod-subtle); +} + +.vc-changelog-log-header:hover .vc-changelog-delete-button { + opacity: 0.8; +} \ No newline at end of file diff --git a/src/components/settings/tabs/index.ts b/src/components/settings/tabs/index.ts index bad044984..6b5cd2432 100644 --- a/src/components/settings/tabs/index.ts +++ b/src/components/settings/tabs/index.ts @@ -8,11 +8,12 @@ import "./styles.css"; export { default as ThemesTab } from "../../ThemeSettings/ThemesTab"; export * from "./BaseTab"; +export { default as ChangelogTab } from "./changelog"; export { default as PatchHelperTab } from "./patchHelper"; export { default as PluginsTab } from "./plugins"; export { openContributorModal } from "./plugins/ContributorModal"; export { openPluginModal } from "./plugins/PluginModal"; export { default as BackupAndRestoreTab } from "./sync/BackupAndRestoreTab"; export { default as CloudTab } from "./sync/CloudTab"; -export { openUpdaterModal, default as UpdaterTab } from "./updater"; +export { default as UpdaterTab } from "./updater"; export { default as VencordTab } from "./vencord"; diff --git a/src/components/settings/tabs/patchHelper/FullPatchInput.tsx b/src/components/settings/tabs/patchHelper/FullPatchInput.tsx index f4db51a19..3943dac3a 100644 --- a/src/components/settings/tabs/patchHelper/FullPatchInput.tsx +++ b/src/components/settings/tabs/patchHelper/FullPatchInput.tsx @@ -4,9 +4,9 @@ * SPDX-License-Identifier: GPL-3.0-or-later */ -import { Margins } from "@utils/margins"; +import { Paragraph } from "@components/Paragraph"; import { Patch, ReplaceFn } from "@utils/types"; -import { Forms, TextArea, useEffect, useRef, useState } from "@webpack/common"; +import { TextArea, useEffect, useRef, useState } from "@webpack/common"; export interface FullPatchInputProps { setFind(v: string): void; @@ -46,7 +46,7 @@ export function FullPatchInput({ setFind, setParsedFind, setMatch, setReplacemen } if (!replacement.match) throw new Error("No 'replacement.match' field"); - if (!replacement.replace) throw new Error("No 'replacement.replace' field"); + if (replacement.replace == null) throw new Error("No 'replacement.replace' field"); setFind(find instanceof RegExp ? `/${find.source}/` : find); setParsedFind(find); @@ -68,16 +68,13 @@ export function FullPatchInput({ setFind, setParsedFind, setMatch, setReplacemen return ( <> - - Paste your full JSON patch here to fill out the fields -