diff --git a/.forgejo/FUNDING.yml b/.forgejo/FUNDING.yml deleted file mode 100644 index 74a3258..0000000 --- a/.forgejo/FUNDING.yml +++ /dev/null @@ -1,18 +0,0 @@ - -# These are supported funding model platforms - - -github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] -patreon: # Replace with a single Patreon username -open_collective: # Replace with a single Open Collective username -ko_fi: cocobo1 -tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel -community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry -liberapay: # Replace with a single Liberapay username -issuehunt: # Replace with a single IssueHunt username -lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry -polar: # Replace with a single Polar username -buy_me_a_coffee: # Replace with a single Buy Me a Coffee username -thanks_dev: # Replace with a single thanks.dev username -custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] - diff --git a/.forgejo/ISSUE_TEMPLATE/bug-report.yml b/.forgejo/ISSUE_TEMPLATE/bug-report.yml deleted file mode 100644 index 987c6d6..0000000 --- a/.forgejo/ISSUE_TEMPLATE/bug-report.yml +++ /dev/null @@ -1,161 +0,0 @@ -name: ๐Ÿž Bug report -description: Report a bug or an issue. -title: 'bug: ' -labels: ['Bug report'] -body: - - type: markdown - attributes: - value: | - > [!IMPORTANT] - > ## Before you submit this issue... - > We might reject any issues that can't be solved within the repository, or reports that are too general. For example: - > - >
- > "Kettu crashes upon starting..." - > > There are lots of different things that could be causing this, but it's probably not related to this repository. - >
- >
- > "Kettu freezes on splash screen..." - > > The issue is too general and should be reported only when it occurs after an update to a specific Discord version or after a specific action or configuration has been made within Kettu. - >
- >
- > "Kettu refuses to install/stuck on downloading..." - > > This is an external issue (sideloading) that does not have a place in this repository. If it's a problem with Kettu Manager, report it to the appropriate repository. - >
- > - > If you think that your problem belongs to one of these, you should avoid submitting this report unless you are told to do so. - > - > Please report the problem or ask for help in the appropriate place. For example, if the problem occurs in Kettu Manager, report it in the Kettu Manager repository. - - - type: checkboxes - id: checklist - attributes: - label: Checklist - description: You should ensure the completion of the task before proceeding to check it off the checklist. Neglecting to do so may impede the efficiency of the issue resolution process. The developer has the right to delete the issue directly if you check the list blindly. - options: - - label: I am able to reproduce the bug with the latest version of Kettu and Discord. - required: true - - label: I've checked that there is no open or closed issue about this bug. - required: true - - label: The title of this issue accurately describes the bug. - required: true - - label: This is not a feature request or a general question. - required: true - - label: This bug can be reproduced consistently and is not caused by a plugin. - required: true - - label: I have read the notes above and ensured that my issue is clear and within this repository. - required: true - - - type: textarea - attributes: - label: Bug description - description: | - - Describe your bug in detail - - Add steps to reproduce the bug if possible (Step 1. ... Step 2. ...) - - Add images and videos if possible - validations: - required: true - - type: textarea - attributes: - label: Stack trace - description: If this bug causes a JS crash, please paste the stack trace here. - render: shell - - type: textarea - attributes: - label: Component stack trace - description: If this bug causes a JS crash, please paste the component stack trace here. - render: shell - - type: textarea - attributes: - label: Native crash trace - description: If this bug causes a native crash, please paste the crash trace here. On Android, this can be accessed by doing `logcat | grep AndroidRuntime`. - render: shell - - - type: input - id: discord-version - attributes: - label: Discord version - description: | - The version of Discord you are using. You can find this in the settings. You may leave both the version number and the build number. - placeholder: | - Example: - "253.2 (66074)" - validations: - required: true - - - type: input - id: version - attributes: - label: Version - description: | - The version of Kettu the issue is occuring on. You can find this in the settings. Kettu version is in formatted like the following: `v0.0.5` and NOT like this: `beaf689-main`. - placeholder: | - Example: - "v0.0.4" - - - type: input - id: loader-version - attributes: - label: Loader version - description: | - The version of the loader you are using. You can find this in the settings. - placeholder: | - Example: - "KettuXposed (v0.0.2)" - validations: - required: true - - - type: input - id: device - attributes: - label: Device - description: | - The device model you are using. - placeholder: | - Example: - "Samsung A35 5G" - validations: - required: true - - - type: input - id: os-version - attributes: - label: OS version - description: | - The version of the operating system you are using. - placeholder: | - Example: - "Android 12/Oneui 4" - validations: - required: true - - - type: textarea - attributes: - label: Debug information - description: | - Debug information is important for the developer to understand the environment in which the bug occurred. You can get this information by sending `/debug` in the chat. - placeholder: | - Example: - Kettu: v0.6.7 (KettuXposed v0.2.4) - Discord: 255.2 (255202) - React: 18.3.1 (RN 0.74.5-discord-11) - Hermes: for RN 0.74.5-discord-11 (bcv96) - System: Android 13 (SDK 33) - Device: M2101K7BG (secret) - - - type: textarea - attributes: - label: Additional context - description: Add additional context here. - - type: checkboxes - id: acknowledgements - attributes: - label: Acknowledgements - description: Your bug report will be closed if you don't follow the checklist below. - options: - - label: I have checked all open and closed bug reports and this is not a duplicate. - required: true - - label: I have chosen an appropriate title. - required: true - - label: All requested information has been provided properly. - required: true diff --git a/.forgejo/workflows/build.yaml b/.forgejo/workflows/build.yaml deleted file mode 100644 index 907ca99..0000000 --- a/.forgejo/workflows/build.yaml +++ /dev/null @@ -1,61 +0,0 @@ -name: Node.js CI / CD (Bun + Forgejo) - -on: - push: - branches: [ "main" ] - -jobs: - buildAndRelease: - runs-on: codeberg-tiny - - steps: - - uses: actions/checkout@v3 - - - name: Install Bun - run: | - curl -fsSL https://bun.sh/install | bash - export BUN_INSTALL="$HOME/.bun" - export PATH="$BUN_INSTALL/bin:$PATH" - echo "$BUN_INSTALL/bin" >> $GITHUB_PATH - - - name: Install dependencies - run: | - export BUN_INSTALL="$HOME/.bun" - export PATH="$BUN_INSTALL/bin:$PATH" - bun install - - - name: Build project - run: | - export BUN_INSTALL="$HOME/.bun" - export PATH="$BUN_INSTALL/bin:$PATH" - bun run build --release-branch=${GITHUB_REF_NAME:-${CI_COMMIT_REF_NAME}} --build-minify - - - name: Push JS files to dist branch - run: | - git config --local user.email "actions@forgejo.local" - git config --local user.name "Forgejo Actions" - - mkdir -p temp_dist - find dist -name "*.js" -exec cp {} temp_dist/ \; - - git checkout --orphan dist-temp - git rm -rf --cached . - - cp -r temp_dist/* . - rm -rf temp_dist - - git add *.js - git commit -m "Update build $GITHUB_SHA" || exit 0 - git push -f origin HEAD:dist - - git checkout -f ${GITHUB_REF_NAME:-${CI_COMMIT_REF_NAME}} - - - name: Merge main into github - if: github.ref == 'refs/heads/main' - run: | - git config --local user.email "actions@forgejo.local" - git config --local user.name "Forgejo Actions" - git fetch origin - git checkout github || git checkout -b github - git merge origin/main --no-edit --allow-unrelated-histories - git push --force origin github \ No newline at end of file diff --git a/.github/workflows/auto-release.yml b/.github/workflows/auto-release.yml new file mode 100644 index 0000000..411ef56 --- /dev/null +++ b/.github/workflows/auto-release.yml @@ -0,0 +1,51 @@ +name: Automatic Release + +on: + push: + branches: + - main + paths: + - "dist/shiggycord.js" + - "dist/shiggycord.min.js" + +jobs: + release: + name: Create Release + runs-on: ubuntu-latest + permissions: + contents: write + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Setup Bun + uses: oven-sh/setup-bun@v1 + with: + bun-version: latest + + - name: Install dependencies + run: bun install + + - name: Extract bunny version + id: bunny_version + run: | + version=$(grep 'const version:' src/modules.d.ts | head -n1 | sed 's/[^0-9.]//g') + echo "version=$version" >> $GITHUB_OUTPUT + + - name: Build (if needed) + run: bun run build + + - name: Create Release + uses: softprops/action-gh-release@v1 + with: + tag_name: v${{ steps.bunny_version.outputs.version }} + name: Release v${{ steps.bunny_version.outputs.version }} + files: | + dist/shiggycord.js + dist/shiggycord.min.js + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.releaserc.json b/.releaserc.json new file mode 100644 index 0000000..3709012 --- /dev/null +++ b/.releaserc.json @@ -0,0 +1,49 @@ +{ + "branches": ["main"], + "plugins": [ + "@semantic-release/commit-analyzer", + "@semantic-release/release-notes-generator", + [ + "@semantic-release/changelog", + { + "changelogFile": "CHANGELOG.md" + } + ], + [ + "@semantic-release/npm", + { + "npmPublish": false + } + ], + [ + "@semantic-release/github", + { + "assets": [ + { + "path": "dist/shiggycord.js", + "label": "ShiggyCord Bundle" + }, + { + "path": "dist/shiggycord.min.js", + "label": "ShiggyCord Bundle (minified)" + } + ] + } + ], + [ + "@semantic-release/git", + { + "assets": ["package.json", "CHANGELOG.md"], + "message": "chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}" + } + ], + [ + "@saithodev/semantic-release-backmerge", + { + "branches": ["dev"], + "backmergeStrategy": "merge", + "backmergeMessage": "chore(release): merge release ${nextRelease.version} into dev [skip ci]" + } + ] + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 3f012b8..0000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "editor.codeActionsOnSave": { - "source.fixAll.eslint": "explicit" - }, - "[typescript]": { - "editor.defaultFormatter": "vscode.typescript-language-features" - }, - "[typescriptreact]": { - "editor.defaultFormatter": "vscode.typescript-language-features" - }, - "[json]": { - "editor.formatOnSave": false - }, - "javascript.format.semicolons": "insert", - "typescript.format.semicolons": "insert", - "typescript.preferences.quoteStyle": "double", - "javascript.preferences.quoteStyle": "double" -} \ No newline at end of file diff --git a/README.md b/README.md index 3110cf4..1f4b6c1 100644 --- a/README.md +++ b/README.md @@ -1,31 +1,31 @@ # ShiggyCord -An unofficial fork of Kettu, made just for fun +An unofficial fork of ShiggyCord, made just for fun Idea taken from Rosiecord ## Installing ### Android/iOS -- **Injecting bundle:** ``https://github.com/kmmiio99o/ShiggyCord/releases//kettu.js`` +- **Injecting bundle:** ``https://github.com/kmmiio99o/ShiggyCord/releases//shiggycord.js`` ## Building -1. Install a Kettu loader with loader config support (any mentioned in the [Installing](#installing) section). +1. Install a ShiggyCord loader with loader config support (any mentioned in the [Installing](#installing) section). 1. Go to Settings > General and enable Developer Settings. 1. Clone the repo: ``` - git clone https://codeberg.org/kmmiio99o/Shiggycord.git + git clone https://github.com/kmmiio99o/Shiggycord.git ``` 1. Install dependencies: ``` bun i ``` -1. Build Kettu's code: +1. Build ShiggyCord's code: ``` bun run build ``` 1. In the newly created `dist` directory, run a HTTP server. I recommend [http-server](https://www.npmjs.com/package/http-server). -1. Go to Settings > Developer enabled earlier. Enable `Load from custom url` and input the IP address and port of the server (e.g. `http://192.168.1.236:4040/kettu.js`) in the new input box labelled `Kettu URL`. -1. Restart Discord. Upon reload, you should notice that your device will download Kettu's bundled code from your server, rather than GitHub. +1. Go to Settings > Developer enabled earlier. Enable `Load from custom url` and input the IP address and port of the server (e.g. `http://192.168.1.236:4040/shiggycord.js`) in the new input box labelled `ShiggyCord URL`. +1. Restart Discord. Upon reload, you should notice that your device will download ShiggyCord's bundled code from your server, rather than GitHub. 1. Make your changes, rebuild, reload, go wild! -Alternatively, you can directly *serve* the bundled code by running `bun run serve`. `kettu.js` will be served on your local address under the port 4040. You will then insert `http://:4040/kettu.js` as a custom url and reload. Whenever you restart your mobile client, the script will rebuild the bundle as your client fetches it. +Alternatively, you can directly *serve* the bundled code by running `bun run serve`. `shiggycord.js` will be served on your local address under the port 4040. You will then insert `http://:4040/shiggycord.js` as a custom url and reload. Whenever you restart your mobile client, the script will rebuild the bundle as your client fetches it. diff --git a/package.json b/package.json index da1c39c..a345630 100644 --- a/package.json +++ b/package.json @@ -1,58 +1,58 @@ { - "name": "Kettu", - "private": true, - "author": { - "name": "cocobo1", - "url": "https://github.com/C0C0B01" - }, - "scripts": { - "build": "node scripts/build.mjs", - "serve": "node scripts/serve.mjs", - "serve:adb": "bun run serve --adb", - "lint": "eslint ./src --ext .js,.jsx,.ts,.tsx" - }, - "license": "BSD-3-Clause", - "devDependencies": { - "@saithodev/semantic-release-backmerge": "^4.0.1", - "@semantic-release/changelog": "^6.0.3", - "@semantic-release/exec": "^6.0.3", - "@semantic-release/git": "^10.0.1", - "semantic-release": "^24.2.7", - "@swc/core": "^1.13.1", - "@swc/helpers": "^0.5.17", - "@types/node": "^20.19.9", - "@types/yargs-parser": "^21.0.3", - "@typescript-eslint/eslint-plugin": "^7.18.0", - "@typescript-eslint/parser": "^7.18.0", - "@typescript-eslint/typescript-estree": "^7.18.0", - "chalk": "^5.4.1", - "esbuild": "^0.20.2", - "esbuild-plugin-globals": "^0.2.0", - "eslint": "^8.57.1", - "eslint-plugin-import-alias": "^1.2.0", - "eslint-plugin-react": "^7.37.5", - "eslint-plugin-simple-import-sort": "^12.1.1", - "eslint-plugin-unused-imports": "^3.2.0", - "typescript": "^5.8.3", - "yargs-parser": "^21.1.1" - }, - "dependencies": { - "@gullerya/object-observer": "^6.1.4", - "@shopify/react-native-skia": "^1.12.4", - "@tanstack/react-query": "^5.83.0", - "@types/chroma-js": "~2.4.5", - "@types/lodash": "~4.17.20", - "@types/react": "19.1.8", - "react-native": "0.78.0", - "es-toolkit": "^1.39.7", - "fuzzysort": "^3.1.0", - "intl-messageformat": "^10.7.16", - "moment": "2.22.2", - "react-native-reanimated": "^3.18.0", - "spitroast": "^1.4.4", - "type-fest": "^4.41.0" - }, + "name": "ShiggyCord", + "private": true, + "author": { + "name": "kmmiio99o", + "url": "https://github.com/kmmiio99o" + }, + "scripts": { + "build": "node scripts/build.mjs", + "serve": "node scripts/serve.mjs", + "serve:adb": "bun run serve --adb", + "lint": "eslint ./src --ext .js,.jsx,.ts,.tsx" + }, + "license": "BSD-3-Clause", + "devDependencies": { + "@saithodev/semantic-release-backmerge": "^4.0.1", + "@semantic-release/changelog": "^6.0.3", + "@semantic-release/exec": "^6.0.3", + "@semantic-release/git": "^10.0.1", + "semantic-release": "^24.2.7", + "@swc/core": "^1.13.1", + "@swc/helpers": "^0.5.17", + "@types/node": "^20.19.9", + "@types/yargs-parser": "^21.0.3", + "@typescript-eslint/eslint-plugin": "^7.18.0", + "@typescript-eslint/parser": "^7.18.0", + "@typescript-eslint/typescript-estree": "^7.18.0", + "chalk": "^5.4.1", + "esbuild": "^0.20.2", + "esbuild-plugin-globals": "^0.2.0", + "eslint": "^8.57.1", + "eslint-plugin-import-alias": "^1.2.0", + "eslint-plugin-react": "^7.37.5", + "eslint-plugin-simple-import-sort": "^12.1.1", + "eslint-plugin-unused-imports": "^3.2.0", + "typescript": "^5.8.3", + "yargs-parser": "^21.1.1" + }, + "dependencies": { + "@gullerya/object-observer": "^6.1.4", + "@shopify/react-native-skia": "^1.12.4", + "@tanstack/react-query": "^5.83.0", + "@types/chroma-js": "~2.4.5", + "@types/lodash": "~4.17.20", + "@types/react": "19.1.8", + "react-native": "0.78.0", + "es-toolkit": "^1.39.7", + "fuzzysort": "^3.1.0", + "intl-messageformat": "^10.7.16", + "moment": "2.22.2", + "react-native-reanimated": "^3.18.0", + "spitroast": "^1.4.4", + "type-fest": "^4.41.0" + }, "trustedDependencies": [ "@swc/core" ] -} \ No newline at end of file +} diff --git a/scripts/build.mjs b/scripts/build.mjs index 75e3ffb..0e57964 100644 --- a/scripts/build.mjs +++ b/scripts/build.mjs @@ -13,119 +13,121 @@ import { printBuildSuccess } from "./util.mjs"; /** @type string[] */ const metroDeps = await (async () => { - const ast = await swc.parseFile(path.resolve("./shims/depsModule.ts")); - return ast.body.at(-1).expression.right.properties.map(p => p.key.value); + const ast = await swc.parseFile(path.resolve("./shims/depsModule.ts")); + return ast.body.at(-1).expression.right.properties.map((p) => p.key.value); })(); const args = yargs(process.argv.slice(2)); const { - "release-branch": releaseBranch, - "build-minify": buildMinify, - "dev": dev + "release-branch": releaseBranch, + "build-minify": buildMinify, + dev: dev, } = args; let context = null; /** @type {import("esbuild").BuildOptions} */ const config = { - entryPoints: ["src/entry.ts"], - bundle: true, - outfile: "dist/kettu.js", - format: "iife", - splitting: false, - external: [], - supported: { - // Hermes does not actually support const and let, even though it syntactically - // accepts it, but it's treated just like 'var' and causes issues - "const-and-let": false - }, - footer: { - js: "//# sourceURL=kettu" - }, - loader: { - ".png": "dataurl" - }, - define: { - window: "globalThis", - __DEV__: JSON.stringify(releaseBranch !== "main") - }, - inject: ["./shims/asyncIteratorSymbol.js", "./shims/promiseAllSettled.js"], - legalComments: "none", - alias: { - "!kettu-deps-shim!": "./shims/depsModule.ts", - "spitroast": "./node_modules/spitroast", - "react/jsx-runtime": "./shims/jsxRuntime" - }, - plugins: [ - globalPlugin({ - ...metroDeps.reduce((obj, key) => { - obj[key] = `require("!kettu-deps-shim!")[${JSON.stringify(key)}]`; - return obj; - }, {}) - }), - { - name: "swc", - setup(build) { - build.onLoad({ filter: /\.[cm]?[jt]sx?$/ }, async args => { - const result = await swc.transformFile(args.path, { - jsc: { - externalHelpers: true, - transform: { - constModules: { - globals: { - "bunny-build-info": { - version: `"1.3.0"` - } - } - }, - react: { - runtime: "automatic" - } - }, - }, - // https://github.com/facebook/hermes/blob/3815fec63d1a6667ca3195160d6e12fee6a0d8d5/doc/Features.md - // https://github.com/facebook/hermes/issues/696#issuecomment-1396235791 - env: { - targets: "fully supports es6", - include: [ - "transform-block-scoping", - "transform-classes", - "transform-async-to-generator", - "transform-async-generator-functions" - ], - exclude: [ - "transform-parameters", - "transform-template-literals", - "transform-exponentiation-operator", - "transform-named-capturing-groups-regex", - "transform-nullish-coalescing-operator", - "transform-object-rest-spread", - "transform-optional-chaining", - "transform-logical-assignment-operators" - ] - }, - }); + entryPoints: ["src/entry.ts"], + bundle: true, + outfile: "dist/shiggycord.js", + format: "iife", + splitting: false, + external: [], + supported: { + // Hermes does not actually support const and let, even though it syntactically + // accepts it, but it's treated just like 'var' and causes issues + "const-and-let": false, + }, + footer: { + js: "//# sourceURL=shiggycord", + }, + loader: { + ".png": "dataurl", + }, + define: { + window: "globalThis", + __DEV__: JSON.stringify(releaseBranch !== "main"), + }, + inject: ["./shims/asyncIteratorSymbol.js", "./shims/promiseAllSettled.js"], + legalComments: "none", + alias: { + "!kettu-deps-shim!": "./shims/depsModule.ts", + spitroast: "./node_modules/spitroast", + "react/jsx-runtime": "./shims/jsxRuntime", + }, + plugins: [ + globalPlugin({ + ...metroDeps.reduce((obj, key) => { + obj[key] = `require("!kettu-deps-shim!")[${JSON.stringify(key)}]`; + return obj; + }, {}), + }), + { + name: "swc", + setup(build) { + build.onLoad({ filter: /\.[cm]?[jt]sx?$/ }, async (args) => { + const result = await swc.transformFile(args.path, { + jsc: { + externalHelpers: true, + transform: { + constModules: { + globals: { + "bunny-build-info": { + version: `"1.0.0"`, + }, + }, + }, + react: { + runtime: "automatic", + }, + }, + }, + // https://github.com/facebook/hermes/blob/3815fec63d1a6667ca3195160d6e12fee6a0d8d5/doc/Features.md + // https://github.com/facebook/hermes/issues/696#issuecomment-1396235791 + env: { + targets: "fully supports es6", + include: [ + "transform-block-scoping", + "transform-classes", + "transform-async-to-generator", + "transform-async-generator-functions", + ], + exclude: [ + "transform-parameters", + "transform-template-literals", + "transform-exponentiation-operator", + "transform-named-capturing-groups-regex", + "transform-nullish-coalescing-operator", + "transform-object-rest-spread", + "transform-optional-chaining", + "transform-logical-assignment-operators", + ], + }, + }); - return { contents: result.code }; - }); - } - } - ] + return { contents: result.code }; + }); + }, + }, + ], }; export async function buildBundle(overrideConfig = {}) { - context = { - hash: releaseBranch ? execSync("git rev-parse --short HEAD").toString().trim() : crypto.randomBytes(8).toString("hex").slice(0, 7) - }; + context = { + hash: releaseBranch + ? execSync("git rev-parse --short HEAD").toString().trim() + : crypto.randomBytes(8).toString("hex").slice(0, 7), + }; - const initialStartTime = performance.now(); - await build({ ...config, ...overrideConfig }); + const initialStartTime = performance.now(); + await build({ ...config, ...overrideConfig }); - return { - config, - context, - timeTook: performance.now() - initialStartTime - }; + return { + config, + context, + timeTook: performance.now() - initialStartTime, + }; } const pathToThisFile = path.resolve(fileURLToPath(import.meta.url)); @@ -133,25 +135,16 @@ const pathPassedToNode = path.resolve(process.argv[1]); const isThisFileBeingRunViaCLI = pathToThisFile.includes(pathPassedToNode); if (isThisFileBeingRunViaCLI) { - const { timeTook } = await buildBundle(); + const { timeTook } = await buildBundle(); - printBuildSuccess( - context.hash, - releaseBranch, - timeTook - ); + printBuildSuccess(context.hash, releaseBranch, timeTook); - if (buildMinify) { - const { timeTook } = await buildBundle({ - minify: true, - outfile: config.outfile.replace(/\.js$/, ".min.js") - }); + if (buildMinify) { + const { timeTook } = await buildBundle({ + minify: true, + outfile: config.outfile.replace(/\.js$/, ".min.js"), + }); - printBuildSuccess( - context.hash, - releaseBranch, - timeTook, - true - ); - } + printBuildSuccess(context.hash, releaseBranch, timeTook, true); + } } diff --git a/scripts/serve.mjs b/scripts/serve.mjs index dfcba60..590c3e0 100644 --- a/scripts/serve.mjs +++ b/scripts/serve.mjs @@ -13,47 +13,43 @@ import { printBuildSuccess } from "./util.mjs"; const args = yargs(process.argv.slice(2)); export function serve(options) { - // @ts-ignore - const server = http.createServer(async (req, res) => { - const { pathname } = url.parse(req.url || "", true); - if (pathname?.endsWith(".js")) { - try { - const { config, context, timeTook } = await buildBundle(); + // @ts-ignore + const server = http.createServer(async (req, res) => { + const { pathname } = url.parse(req.url || "", true); + if (pathname?.endsWith(".js")) { + try { + const { config, context, timeTook } = await buildBundle(); - printBuildSuccess( - context.hash, - args.production, - timeTook - ); + printBuildSuccess(context.hash, args.production, timeTook); - res.writeHead(200, { "Content-Type": "application/javascript" }); - res.end(await readFile(config.outfile, "utf-8")); - } catch { - res.writeHead(500); - res.end(); - } - } else { - res.writeHead(404); - res.end(); - } - }, options); - - server.listen(args.port ?? 4040); - - console.info(chalk.bold.yellowBright("Serving Kettu on:")); - - const netInterfaces = os.networkInterfaces(); - for (const netinterfaces of Object.values(netInterfaces)) { - for (const details of netinterfaces || []) { - if (details.family !== "IPv4") continue; - const port = chalk.green(server.address()?.port.toString()); - console.info(` http://${details.address}:${port}/bundle.js`); - } + res.writeHead(200, { "Content-Type": "application/javascript" }); + res.end(await readFile(config.outfile, "utf-8")); + } catch { + res.writeHead(500); + res.end(); + } + } else { + res.writeHead(404); + res.end(); } + }, options); - return server; + server.listen(args.port ?? 4040); + + console.info(chalk.bold.yellowBright("Serving ShiggyCord on:")); + + const netInterfaces = os.networkInterfaces(); + for (const netinterfaces of Object.values(netInterfaces)) { + for (const details of netinterfaces || []) { + if (details.family !== "IPv4") continue; + const port = chalk.green(server.address()?.port.toString()); + console.info(` http://${details.address}:${port}/shiggycord.js`); + } + } + + return server; } const server = serve(); -console.log("\nPress Q key or Ctrl+C to exit."); \ No newline at end of file +console.log("\nPress Q key or Ctrl+C to exit."); diff --git a/src/assets/icons/codeberg-logo_icon_white.png b/src/assets/icons/codeberg-logo_icon_white.png deleted file mode 100644 index 873d3c7..0000000 Binary files a/src/assets/icons/codeberg-logo_icon_white.png and /dev/null differ diff --git a/src/assets/icons/kettu.png b/src/assets/icons/kettu.png deleted file mode 100644 index f4a6c5a..0000000 Binary files a/src/assets/icons/kettu.png and /dev/null differ diff --git a/src/assets/icons/shiggy.png b/src/assets/icons/shiggy.png new file mode 100644 index 0000000..abd0db2 Binary files /dev/null and b/src/assets/icons/shiggy.png differ diff --git a/src/core/commands/debug.ts b/src/core/commands/debug.ts index b1975c6..466af61 100644 --- a/src/core/commands/debug.ts +++ b/src/core/commands/debug.ts @@ -1,35 +1,41 @@ import { Strings } from "@core/i18n"; -import { ApplicationCommand, ApplicationCommandOptionType } from "@lib/api/commands/types"; +import { + ApplicationCommand, + ApplicationCommandOptionType, +} from "@lib/api/commands/types"; import { getDebugInfo } from "@lib/api/debug"; import { messageUtil } from "@metro/common"; -export default () => { +export default () => + { name: "debug", description: Strings.COMMAND_DEBUG_DESC, options: [ - { - name: "ephemeral", - type: ApplicationCommandOptionType.BOOLEAN, - description: Strings.COMMAND_DEBUG_OPT_EPHEMERALLY, - } + { + name: "ephemeral", + type: ApplicationCommandOptionType.BOOLEAN, + description: Strings.COMMAND_DEBUG_OPT_EPHEMERALLY, + }, ], execute([ephemeral], ctx) { - const info = getDebugInfo(); - const content = [ - "**Kettu Debug Info**", - `> Kettu: ${info.bunny.version} (${info.bunny.loader.name} ${info.bunny.loader.version})`, - `> Discord: ${info.discord.version} (${info.discord.build})`, - `> React: ${info.react.version} (RN ${info.react.nativeVersion})`, - `> Hermes: ${info.hermes.version} (bcv${info.hermes.bytecodeVersion})`, - `> System: ${info.os.name} ${info.os.version} ${info.os.sdk ? `(SDK ${info.os.sdk})` : ""}`.trimEnd(), - `> Device: ${info.device.model} (${info.device.codename})`, - ].join("\n"); + const info = getDebugInfo(); + const content = [ + "**ShiggyCord Debug Info**", + `> ShiggyCord: ${info.bunny.version} (${info.bunny.loader.name} ${info.bunny.loader.version})`, + `> Discord: ${info.discord.version} (${info.discord.build})`, + `> React: ${info.react.version} (RN ${info.react.nativeVersion})`, + `> Hermes: ${info.hermes.version} (bcv${info.hermes.bytecodeVersion})`, + `> System: ${info.os.name} ${info.os.version} ${info.os.sdk ? `(SDK ${info.os.sdk})` : ""}`.trimEnd(), + `> Device: ${info.device.model}`, + ].join("\n"); - if (ephemeral?.value) { - messageUtil.sendBotMessage(ctx.channel.id, content); - } else { - const fixNonce = Date.now().toString(); - messageUtil.sendMessage(ctx.channel.id, { content }, void 0, {nonce:fixNonce}); - } - } -}; + if (ephemeral?.value) { + messageUtil.sendBotMessage(ctx.channel.id, content); + } else { + const fixNonce = Date.now().toString(); + messageUtil.sendMessage(ctx.channel.id, { content }, void 0, { + nonce: fixNonce, + }); + } + }, + }; diff --git a/src/core/i18n/default.json b/src/core/i18n/default.json index afd4443..f13e3b1 100644 --- a/src/core/i18n/default.json +++ b/src/core/i18n/default.json @@ -1,121 +1,121 @@ { - "ABOUT": "About", - "ACTIONS": "Actions", - "ARE_YOU_SURE_TO_CLEAR_DATA": "Are you sure you wish to clear the data of {name}?", - "ARE_YOU_SURE_TO_DELETE_PLUGIN": "Are you sure you wish to delete {name}? This will clear all of the plugin's data.", - "ARE_YOU_SURE_TO_DELETE_THEME": "Are you sure you wish to delete {name}?", - "ASSET_BROWSER": "Asset Browser", - "BRAND": "Brand", - "PUPU": "Kettu", - "PUPU_URL": "Kettu URL", - "BROWSER": "Browser", - "BYTECODE": "Bytecode", - "CANCEL": "Cancel", - "CLEAR": "Clear", - "CLEAR_BUNDLE": "Clear JS Bundle", - "CLEAR_BUNDLE_DESC": "Clear the cached bundle. This will force a re-download of the bundle next app launch.", - "CLEAR_DATA": "Clear data", - "CLEAR_DATA_FAILED": "Failed to clear data for {name}", - "CLEAR_DATA_SUCCESSFUL": "Cleared data for {name}", - "CODEBERG": "Codeberg", - "CODENAME": "Codename", - "COMMAND_DEBUG_DESC": "Send Kettu debug info.", - "COMMAND_DEBUG_OPT_EPHEMERALLY": "Send debug info ephemerally.", - "COMMAND_EVAL_DESC": "Evaluate JavaScript code.", - "COMMAND_EVAL_OPT_ASYNC": "Whether to support 'await' in code. Must explicitly return for result (default: false)", - "COMMAND_EVAL_OPT_CODE": "The code to evaluate.", - "COMMAND_PLUGINS_DESC": "Send list of installed plugins.", - "COMMAND_PLUGINS_OPT_EPHEMERALLY": "Send plugins list ephemerally.", - "COMPONENT": "Component", - "CONFIRMATION_LINK_IS_A_TYPE": "This link is a **{urlType, select, plugin {Plugin} theme {Theme} other {Add-on}}**, would you like to install it?", - "CONNECT_TO_DEBUG_WEBSOCKET": "Connect to debug WebSocket", - "CONNECT_TO_REACT_DEVTOOLS": "Connect to React DevTools", - "CONTINUE": "Continue", - "COPIED_TO_CLIPBOARD": "Copied to clipboard", - "COPY_URL": "Copy URL", - "DEBUG": "Debug", - "DEBUGGER_URL": "Debugger URL", - "DELETE": "Delete", - "DESC_EXTRACT_FONTS_FROM_THEME": "Looks out for \"fonts\" field in your currently applied theme and install it.", - "DEVELOPER": "Developer", - "DEVELOPER_SETTINGS": "Developer Settings", - "DISABLE_THEME": "Disable Theme", - "DISABLE_UPDATES": "Disable Updates", - "DISCORD_SERVER": "Discord Server", - "DONE": "Done", - "ENABLE_EVAL_COMMAND": "Enable /eval command", - "ENABLE_EVAL_COMMAND_DESC": "Evaluate JavaScript directly from a command. Be cautious when using this command as it may pose a security risk. Make sure to know what you are doing.", - "ENABLE_UPDATES": "Enable Updates", - "ERROR_BOUNDARY_TOOLS_LABEL": "ErrorBoundary Tools", - "EXTRACT": "Extract", - "FONT_NAME": "Font Name", - "FONTS": "Fonts", - "GENERAL": "General", - "GITHUB": "GitHub", - "HOLD_UP": "Hold Up", - "INFO": "Info", - "INSTALL": "Install", - "INSTALL_ADDON": "Install an add-on", - "INSTALL_FONT": "Install a font", - "INSTALL_PLUGIN": "Install a plugin", - "INSTALL_REACT_DEVTOOLS": "Install React DevTools", - "INSTALL_THEME": "Install a theme", - "LABEL_EXTRACT_FONTS_FROM_THEME": "Extract font from theme", - "LINKS": "Links", - "LOAD_FROM_CUSTOM_URL": "Load from custom URL", - "LOAD_FROM_CUSTOM_URL_DEC": "Load Kettu from a custom endpoint.", - "LOAD_REACT_DEVTOOLS": "Load React DevTools", - "LOADER": "Loader", - "MACHINE_ID": "Machine ID", - "MANUFACTURER": "Manufacturer", - "MESSAGE": "Message", - "MISCELLANEOUS": "Miscellaneous", - "MODAL_RELOAD_REQUIRED": "Reload app?", - "MODAL_RELOAD_REQUIRED_DESC": "A reload is required to see the changes. Do you want to reload now?", - "MODAL_THEME_REFETCHED": "Theme refetched", - "MODAL_THEME_REFETCHED_DESC": "A reload is required to see the changes. Do you want to reload now?", - "MODAL_UNPROXIED_PLUGIN_DESC": "The plugin you are trying to install has not been proxied/verified by staff. Are you sure you want to continue?", - "MODAL_UNPROXIED_PLUGIN_HEADER": "Unproxied Plugin", - "MODEL": "Model", - "OPEN_IN_BROWSER": "Open in Browser", - "OPERATING_SYSTEM": "Operating System", - "OVERFLOW_PLUGIN_SETTINGS": "Plugin Settings", - "PLATFORM": "Platform", - "PLUGIN_REFETCH_FAILED": "Failed to refetch plugin", - "PLUGIN_REFETCH_SUCCESSFUL": "Successfully refetched plugin", - "PLUGINS": "Plugins", - "REFETCH": "Refetch", - "RELOAD": "Reload", - "RELOAD_DISCORD": "Reload Discord", - "RELOAD_IN_NORMAL_MODE": "Reload in Normal Mode", - "RELOAD_IN_NORMAL_MODE_DESC": "Safe mode currently enabled, tap to reload in normal mode", - "RELOAD_IN_SAFE_MODE": "Reload in Safe Mode", - "RELOAD_IN_SAFE_MODE_DESC": "Tap to reload Discord without loading addons", - "REMOVE": "Remove", - "RESTART_REQUIRED_TO_TAKE_EFFECT": "Restart is required to take effect", - "RETRY": "Retry", - "RETRY_RENDER": "Retry Render", - "SAFE_MODE": "Safe Mode", - "SAFE_MODE_NOTICE_FONTS": "You are in safe mode, meaning fonts have been temporarily disabled. {enabled, select, true {If a font appears to be causing the issue, you can press below to disable it persistently.} other {}}", - "SAFE_MODE_NOTICE_PLUGINS": "You are in safe mode, so plugins cannot be loaded. Disable any misbehaving plugins, then return to Normal Mode from the General settings page.", - "SAFE_MODE_NOTICE_THEMES": "You are in safe mode, meaning themes have been temporarily disabled. {enabled, select, true {If a theme appears to be causing the issue, you can press below to disable it persistently.} other {}}", - "SEARCH": "Search", - "SEPARATOR": ", ", - "SETTINGS_ACTIVATE_DISCORD_EXPERIMENTS": "Activate Discord Experiments", - "SETTINGS_ACTIVATE_DISCORD_EXPERIMENTS_DESC": "Warning: Messing with this feature may lead to account termination. I heavily discourage using this and am not responsible for anything that happens if you use it", - "STACK_TRACE": "Stack Trace", - "SUCCESSFULLY_INSTALLED": "Successfully installed", - "THEME_EXTRACTOR_DESC": "This pack overrides the following: {fonts}", - "THEME_REFETCH_FAILED": "Failed to refetch theme", - "THEME_REFETCH_SUCCESSFUL": "Successfully refetched theme", - "THEMES": "Themes", - "THEMES_RELOAD_FOR_CHANGES": "Reload the app to fully apply changes", - "TOASTS_INSTALLED_PLUGIN": "Installed plugin", - "TOASTS_PLUGIN_UPDATE": "{update, select, true {Enabled} other {Disabled}} updates for {name}", - "UH_OH": "Uh Oh", - "UNINSTALL": "Uninstall", - "UNINSTALL_TITLE": "Uninstall {title}", - "URL_PLACEHOLDER": "https://codeberg.org/cocobo1/Kettu", - "VERSION": "Version", - "VERSIONS": "Versions" -} \ No newline at end of file + "ABOUT": "About", + "ACTIONS": "Actions", + "ARE_YOU_SURE_TO_CLEAR_DATA": "Are you sure you wish to clear the data of {name}?", + "ARE_YOU_SURE_TO_DELETE_PLUGIN": "Are you sure you wish to delete {name}? This will clear all of the plugin's data.", + "ARE_YOU_SURE_TO_DELETE_THEME": "Are you sure you wish to delete {name}?", + "ASSET_BROWSER": "Asset Browser", + "BRAND": "Brand", + "PUPU": "ShiggyCord", + "PUPU_URL": "ShiggyCord URL", + "BROWSER": "Browser", + "BYTECODE": "Bytecode", + "CANCEL": "Cancel", + "CLEAR": "Clear", + "CLEAR_BUNDLE": "Clear JS Bundle", + "CLEAR_BUNDLE_DESC": "Clear the cached bundle. This will force a re-download of the bundle next app launch.", + "CLEAR_DATA": "Clear data", + "CLEAR_DATA_FAILED": "Failed to clear data for {name}", + "CLEAR_DATA_SUCCESSFUL": "Cleared data for {name}", + "CODEBERG": "Codeberg", + "CODENAME": "Codename", + "COMMAND_DEBUG_DESC": "Send ShiggyCord debug info.", + "COMMAND_DEBUG_OPT_EPHEMERALLY": "Send debug info ephemerally.", + "COMMAND_EVAL_DESC": "Evaluate JavaScript code.", + "COMMAND_EVAL_OPT_ASYNC": "Whether to support 'await' in code. Must explicitly return for result (default: false)", + "COMMAND_EVAL_OPT_CODE": "The code to evaluate.", + "COMMAND_PLUGINS_DESC": "Send list of installed plugins.", + "COMMAND_PLUGINS_OPT_EPHEMERALLY": "Send plugins list ephemerally.", + "COMPONENT": "Component", + "CONFIRMATION_LINK_IS_A_TYPE": "This link is a **{urlType, select, plugin {Plugin} theme {Theme} other {Add-on}}**, would you like to install it?", + "CONNECT_TO_DEBUG_WEBSOCKET": "Connect to debug WebSocket", + "CONNECT_TO_REACT_DEVTOOLS": "Connect to React DevTools", + "CONTINUE": "Continue", + "COPIED_TO_CLIPBOARD": "Copied to clipboard", + "COPY_URL": "Copy URL", + "DEBUG": "Debug", + "DEBUGGER_URL": "Debugger URL", + "DELETE": "Delete", + "DESC_EXTRACT_FONTS_FROM_THEME": "Looks out for \"fonts\" field in your currently applied theme and install it.", + "DEVELOPER": "Developer", + "DEVELOPER_SETTINGS": "Developer Settings", + "DISABLE_THEME": "Disable Theme", + "DISABLE_UPDATES": "Disable Updates", + "DISCORD_SERVER": "Discord Server", + "DONE": "Done", + "ENABLE_EVAL_COMMAND": "Enable /eval command", + "ENABLE_EVAL_COMMAND_DESC": "Evaluate JavaScript directly from a command. Be cautious when using this command as it may pose a security risk. Make sure to know what you are doing.", + "ENABLE_UPDATES": "Enable Updates", + "ERROR_BOUNDARY_TOOLS_LABEL": "ErrorBoundary Tools", + "EXTRACT": "Extract", + "FONT_NAME": "Font Name", + "FONTS": "Fonts", + "GENERAL": "General", + "GITHUB": "GitHub", + "HOLD_UP": "Hold Up", + "INFO": "Info", + "INSTALL": "Install", + "INSTALL_ADDON": "Install an add-on", + "INSTALL_FONT": "Install a font", + "INSTALL_PLUGIN": "Install a plugin", + "INSTALL_REACT_DEVTOOLS": "Install React DevTools", + "INSTALL_THEME": "Install a theme", + "LABEL_EXTRACT_FONTS_FROM_THEME": "Extract font from theme", + "LINKS": "Links", + "LOAD_FROM_CUSTOM_URL": "Load from custom URL", + "LOAD_FROM_CUSTOM_URL_DEC": "Load ShiggyCord from a custom endpoint.", + "LOAD_REACT_DEVTOOLS": "Load React DevTools", + "LOADER": "Loader", + "MACHINE_ID": "Machine ID", + "MANUFACTURER": "Manufacturer", + "MESSAGE": "Message", + "MISCELLANEOUS": "Miscellaneous", + "MODAL_RELOAD_REQUIRED": "Reload app?", + "MODAL_RELOAD_REQUIRED_DESC": "A reload is required to see the changes. Do you want to reload now?", + "MODAL_THEME_REFETCHED": "Theme refetched", + "MODAL_THEME_REFETCHED_DESC": "A reload is required to see the changes. Do you want to reload now?", + "MODAL_UNPROXIED_PLUGIN_DESC": "The plugin you are trying to install has not been proxied/verified by staff. Are you sure you want to continue?", + "MODAL_UNPROXIED_PLUGIN_HEADER": "Unproxied Plugin", + "MODEL": "Model", + "OPEN_IN_BROWSER": "Open in Browser", + "OPERATING_SYSTEM": "Operating System", + "OVERFLOW_PLUGIN_SETTINGS": "Plugin Settings", + "PLATFORM": "Platform", + "PLUGIN_REFETCH_FAILED": "Failed to refetch plugin", + "PLUGIN_REFETCH_SUCCESSFUL": "Successfully refetched plugin", + "PLUGINS": "Plugins", + "REFETCH": "Refetch", + "RELOAD": "Reload", + "RELOAD_DISCORD": "Reshiggy your Discord", + "RELOAD_IN_NORMAL_MODE": "Reload in Normal Mode", + "RELOAD_IN_NORMAL_MODE_DESC": "Safe mode currently enabled, tap to reload in normal mode", + "RELOAD_IN_SAFE_MODE": "Reload in Safe Mode", + "RELOAD_IN_SAFE_MODE_DESC": "Tap to reload Discord without loading addons", + "REMOVE": "Remove", + "RESTART_REQUIRED_TO_TAKE_EFFECT": "Restart is required to take effect", + "RETRY": "Retry", + "RETRY_RENDER": "Retry Render", + "SAFE_MODE": "Safe Mode", + "SAFE_MODE_NOTICE_FONTS": "You are in safe mode, meaning fonts have been temporarily disabled. {enabled, select, true {If a font appears to be causing the issue, you can press below to disable it persistently.} other {}}", + "SAFE_MODE_NOTICE_PLUGINS": "You are in safe mode, so plugins cannot be loaded. Disable any misbehaving plugins, then return to Normal Mode from the General settings page.", + "SAFE_MODE_NOTICE_THEMES": "You are in safe mode, meaning themes have been temporarily disabled. {enabled, select, true {If a theme appears to be causing the issue, you can press below to disable it persistently.} other {}}", + "SEARCH": "Search", + "SEPARATOR": ", ", + "SETTINGS_ACTIVATE_DISCORD_EXPERIMENTS": "Activate Discord Experiments", + "SETTINGS_ACTIVATE_DISCORD_EXPERIMENTS_DESC": "Warning: Messing with this feature may lead to account termination. I heavily discourage using this and am not responsible for anything that happens if you use it", + "STACK_TRACE": "Stack Trace", + "SUCCESSFULLY_INSTALLED": "Successfully installed", + "THEME_EXTRACTOR_DESC": "This pack overrides the following: {fonts}", + "THEME_REFETCH_FAILED": "Failed to refetch theme", + "THEME_REFETCH_SUCCESSFUL": "Successfully refetched theme", + "THEMES": "Themes", + "THEMES_RELOAD_FOR_CHANGES": "Reload the app to fully apply changes", + "TOASTS_INSTALLED_PLUGIN": "Installed plugin", + "TOASTS_PLUGIN_UPDATE": "{update, select, true {Enabled} other {Disabled}} updates for {name}", + "UH_OH": "Uh Oh", + "UNINSTALL": "Uninstall", + "UNINSTALL_TITLE": "Uninstall {title}", + "URL_PLACEHOLDER": "https://github.com/kmmiio99o/ShiggyCord", + "VERSION": "Version", + "VERSIONS": "Versions" +} diff --git a/src/core/i18n/index.ts b/src/core/i18n/index.ts index efeea3b..910a0ea 100644 --- a/src/core/i18n/index.ts +++ b/src/core/i18n/index.ts @@ -4,7 +4,9 @@ import { PrimitiveType } from "intl-messageformat"; import langDefault from "./default.json"; -const IntlMessageFormat = findByNameLazy("MessageFormat") as typeof import("intl-messageformat").default; +const IntlMessageFormat = findByNameLazy( + "MessageFormat", +) as typeof import("intl-messageformat").default; type I18nKey = keyof typeof langDefault; @@ -14,55 +16,73 @@ let _lastSetLocale: string | null = null; const _loadedLocale = new Set(); const _loadedStrings = {} as Record; -export const Strings = new Proxy({}, { +export const Strings = new Proxy( + {}, + { get: (_t, prop: keyof typeof langDefault) => { - if (_currentLocale && _loadedStrings[_currentLocale]?.[prop]) { - return _loadedStrings[_currentLocale]?.[prop]; - } - return langDefault[prop]; - } -}) as Record; + if (_currentLocale && _loadedStrings[_currentLocale]?.[prop]) { + return _loadedStrings[_currentLocale]?.[prop]; + } + return langDefault[prop]; + }, + }, +) as Record; export function initFetchI18nStrings() { - const cb = ({ locale }: { locale: string; }) => { - const languageMap = { - "es-ES": "es", - "es-419": "es_419", - "zh-TW": "zh-Hant", - "zh-CN": "zh-Hans", - "pt-PT": "pt", - "pt-BR": "pt_BR", - "sv-SE": "sv" - } as Record; + const cb = ({ locale }: { locale: string }) => { + const languageMap = { + "es-ES": "es", + "es-419": "es_419", + "zh-TW": "zh-Hant", + "zh-CN": "zh-Hans", + "pt-PT": "pt", + "pt-BR": "pt_BR", + "sv-SE": "sv", + } as Record; - const resolvedLocale = _lastSetLocale = languageMap[locale] ?? locale; + const resolvedLocale = (_lastSetLocale = languageMap[locale] ?? locale); - if (resolvedLocale.startsWith("en-")) { - _currentLocale = null; - return; - } + if (resolvedLocale.startsWith("en-")) { + _currentLocale = null; + return; + } - if (!_loadedLocale.has(resolvedLocale)) { - _loadedLocale.add(resolvedLocale); + if (!_loadedLocale.has(resolvedLocale)) { + _loadedLocale.add(resolvedLocale); - fetch(`https://raw.githubusercontent.com/C0C0B01/i18n/main/resources/${resolvedLocale}/kettu.json`) - .then(r => r.json()) - .then(strings => _loadedStrings[resolvedLocale] = strings) - .then(() => resolvedLocale === _lastSetLocale && (_currentLocale = resolvedLocale)) - .catch(e => console.error(`An error occured while fetching strings for ${resolvedLocale}: ${e}`)); - } else { - _currentLocale = resolvedLocale; - } - }; + fetch( + `https://raw.githubusercontent.com/C0C0B01/i18n/main/resources/${resolvedLocale}/shiggycord.json`, + ) + .then((r) => r.json()) + .then((strings) => (_loadedStrings[resolvedLocale] = strings)) + .then( + () => + resolvedLocale === _lastSetLocale && + (_currentLocale = resolvedLocale), + ) + .catch((e) => + console.error( + `An error occured while fetching strings for ${resolvedLocale}: ${e}`, + ), + ); + } else { + _currentLocale = resolvedLocale; + } + }; - FluxDispatcher.subscribe("I18N_LOAD_SUCCESS", cb); - return () => FluxDispatcher.unsubscribe("I18N_LOAD_SUCCESS", cb); + FluxDispatcher.subscribe("I18N_LOAD_SUCCESS", cb); + return () => FluxDispatcher.unsubscribe("I18N_LOAD_SUCCESS", cb); } -type FormatStringRet = T extends PrimitiveType ? string : string | T | (string | T)[]; +type FormatStringRet = T extends PrimitiveType + ? string + : string | T | (string | T)[]; -export function formatString(key: I18nKey, val: Record): FormatStringRet { - const str = Strings[key]; - // @ts-ignore - return new IntlMessageFormat(str).format(val); +export function formatString( + key: I18nKey, + val: Record, +): FormatStringRet { + const str = Strings[key]; + // @ts-ignore + return new IntlMessageFormat(str).format(val); } diff --git a/src/core/plugins/fixembed/index.ts b/src/core/plugins/fixembed/index.ts new file mode 100644 index 0000000..eb5b50a --- /dev/null +++ b/src/core/plugins/fixembed/index.ts @@ -0,0 +1,194 @@ +import { defineCorePlugin } from ".."; +import { findByProps } from "@metro"; +import { after } from "@lib/api/patcher"; +import { logger } from "@lib/utils/logger"; +import { settings } from "@lib/api/settings"; +import { React } from "@metro/common"; + +const { FormSection, FormRow, FormSwitch, FormText } = findByProps( + "FormSection", + "FormRow", + "FormSwitch", + "FormText", +); +const { ScrollView } = require("react-native"); + +type FixEmbedSettings = { + enabled: boolean; + twitter: boolean; + instagram: boolean; + tiktok: boolean; + reddit: boolean; +}; + +declare module "@lib/api/settings" { + interface Settings { + fixembed?: FixEmbedSettings; + } +} + +const MessageActions = findByProps("sendMessage"); +let unpatch: (() => void) | null = null; + +function transformLinks(content: string, config: FixEmbedSettings): string { + let result = content; + // Twitter/X: Replace domain, keep path + if (config.twitter) { + result = result.replace( + /https?:\/\/(?:www\.)?(twitter\.com|x\.com)(\/[^\s]+)/gi, + "https://fxtwitter.com$2", + ); + } + // Instagram: Replace domain, keep path + if (config.instagram) { + result = result.replace( + /https?:\/\/(?:www\.)?instagram\.com(\/[^\s]+)/gi, + "https://ddinstagram.com$1", + ); + } + // TikTok: Replace domain, keep path + if (config.tiktok) { + result = result.replace( + /https?:\/\/(?:www\.)?tiktok\.com(\/[^\s]+)/gi, + "https://tnktok.com$1", + ); + // TikTok short links (vm.tiktok.com) + result = result.replace( + /https?:\/\/vm\.tiktok\.com\/([A-Za-z0-9]+)\/?/gi, + "https://vm.tnktok.com/$1", + ); + } + // Reddit: Replace domain, keep path + if (config.reddit) { + result = result.replace( + /https?:\/\/(?:www\.)?reddit\.com(\/[^\s]+)/gi, + "https://rxddit.com$1", + ); + } + return result; +} + +export default defineCorePlugin({ + manifest: { + id: "bunny.fixembed", + version: "1.0.0", + type: "plugin", + spec: 3, + main: "", + display: { + name: "FixEmbed", + description: + "Improves social media embeds by using privacy-friendly alternative frontends.", + authors: [{ name: "ShiggyCord Team" }], + }, + }, + + SettingsComponent() { + const { useState, useEffect } = React; + const [config, setConfig] = useState({ + enabled: settings.fixembed?.enabled ?? true, + twitter: settings.fixembed?.twitter ?? true, + instagram: settings.fixembed?.instagram ?? true, + tiktok: settings.fixembed?.tiktok ?? true, + reddit: settings.fixembed?.reddit ?? true, + }); + + useEffect(() => { + settings.fixembed = config; + }, [config]); + + const updateConfig = (key: keyof FixEmbedSettings, value: boolean) => { + setConfig((prev) => ({ ...prev, [key]: value })); + }; + + return React.createElement(ScrollView, { style: { flex: 1 } }, [ + React.createElement( + FormSection, + { title: "General Settings" }, + React.createElement(FormRow, { + label: "Enable FixEmbed", + subLabel: "Master switch for all link conversions", + trailing: React.createElement(FormSwitch, { + value: config.enabled, + onValueChange: (v: boolean) => updateConfig("enabled", v), + }), + }), + ), + React.createElement(FormSection, { title: "Platforms" }, [ + React.createElement(FormRow, { + label: "Twitter/X", + trailing: React.createElement(FormSwitch, { + value: config.twitter, + disabled: !config.enabled, + onValueChange: (v: boolean) => updateConfig("twitter", v), + }), + }), + React.createElement(FormRow, { + label: "Instagram", + trailing: React.createElement(FormSwitch, { + value: config.instagram, + disabled: !config.enabled, + onValueChange: (v: boolean) => updateConfig("instagram", v), + }), + }), + React.createElement(FormRow, { + label: "TikTok", + trailing: React.createElement(FormSwitch, { + value: config.tiktok, + disabled: !config.enabled, + onValueChange: (v: boolean) => updateConfig("tiktok", v), + }), + }), + React.createElement(FormRow, { + label: "Reddit", + trailing: React.createElement(FormSwitch, { + value: config.reddit, + disabled: !config.enabled, + onValueChange: (v: boolean) => updateConfig("reddit", v), + }), + }), + ]), + React.createElement( + FormSection, + { title: "About" }, + React.createElement( + FormText, + { style: { padding: 12 } }, + "This plugin automatically converts social media links to privacy-friendly alternative frontends for better embeds.", + ), + ), + ]); + }, + + start() { + logger.log("FixEmbed: Starting plugin"); + settings.fixembed = settings.fixembed || { + enabled: true, + twitter: true, + instagram: true, + tiktok: true, + reddit: true, + }; + + if (!unpatch) { + unpatch = after("sendMessage", MessageActions, (args) => { + const config = settings.fixembed!; + if (!config.enabled) return; + if (args[1]?.content) { + args[1].content = transformLinks(args[1].content, config); + args[1].nonce = args[1].nonce || Math.random().toString(36).slice(2); + } + }); + } + logger.log("FixEmbed: Patched outgoing messages"); + }, + + stop() { + logger.log("FixEmbed: Stopping plugin"); + if (unpatch) { + unpatch(); + unpatch = null; + } + logger.log("FixEmbed: Unpatched outgoing messages"); + }, +}); diff --git a/src/core/plugins/index.ts b/src/core/plugins/index.ts index 7b423f7..6657185 100644 --- a/src/core/plugins/index.ts +++ b/src/core/plugins/index.ts @@ -1,22 +1,26 @@ import { PluginInstanceInternal } from "@lib/addons/plugins/types"; interface CorePlugin { - default: PluginInstanceInternal; - preenabled: boolean; + default: PluginInstanceInternal; + preenabled: boolean; } // Called from @lib/plugins export const getCorePlugins = (): Record => ({ - "bunny.quickinstall": require("./quickinstall"), - "bunny.badges": require("./badges"), - "vendetta.notrack": require("./notrack") + "bunny.quickinstall": { ...require("./quickinstall"), preenabled: true }, + "bunny.badges": require("./badges"), + "bunny.notrack": { ...require("./notrack"), preenabled: true }, + "bunny.messagefix": { ...require("./messagefix") }, + "bunny.fixembed": { ...require("./fixembed"), preenabled: true }, }); /** * @internal */ -export function defineCorePlugin(instance: PluginInstanceInternal): PluginInstanceInternal { - // @ts-expect-error - instance[Symbol.for("bunny.core.plugin")] = true; - return instance; +export function defineCorePlugin( + instance: PluginInstanceInternal, +): PluginInstanceInternal { + // @ts-expect-error + instance[Symbol.for("bunny.core.plugin")] = true; + return instance; } diff --git a/src/core/plugins/messagefix/index.ts b/src/core/plugins/messagefix/index.ts new file mode 100644 index 0000000..146bf2b --- /dev/null +++ b/src/core/plugins/messagefix/index.ts @@ -0,0 +1,56 @@ +import { defineCorePlugin } from ".."; +import { findByProps } from "@metro"; +import { logger } from "@lib/utils/logger"; + +// Find the MessageActions module +const MessageActions = findByProps("sendMessage"); +let originalSendMessage: Function; + +export default defineCorePlugin({ + manifest: { + id: "bunny.messagefix", + version: "1.0.0", + type: "plugin", + spec: 3, + main: "", + display: { + name: "MessageFix", + description: "Ensures messages include the required nonce parameter", + authors: [{ name: "ShiggyCord Team" }], + }, + }, + + start() { + // Save original function + originalSendMessage = MessageActions.sendMessage; + + // Replace with our implementation + MessageActions.sendMessage = function ( + channelId, + message, + replyRef, + options, + ) { + // Ensure options exists and has a nonce + options = options || {}; + options.nonce = options.nonce || Date.now().toString(); + + // Call original with fixed parameters + return originalSendMessage.call( + this, + channelId, + message, + replyRef, + options, + ); + }; + + logger.log("MessageFix: Enabled - adding nonce to all messages"); + }, + + stop() { + // Restore original function + if (originalSendMessage) MessageActions.sendMessage = originalSendMessage; + logger.log("MessageFix: Disabled"); + }, +}); diff --git a/src/core/plugins/notrack/index.ts b/src/core/plugins/notrack/index.ts index 58865de..8459303 100644 --- a/src/core/plugins/notrack/index.ts +++ b/src/core/plugins/notrack/index.ts @@ -1,36 +1,230 @@ -import network from "./lib/network"; -import console from "./lib/console"; -import miscellanous from "./lib/miscellanous"; -import sentry from "./lib/sentry"; import { defineCorePlugin } from ".."; +import { findByProps } from "@metro"; +import { instead } from "@lib/api/patcher"; import { logger } from "@lib/utils/logger"; -const patches = [ - network(), - console(), - miscellanous(), - // Sentry causes issues - // sentry(), -]; +// Type for functions that need to be cleaned up when plugin is disabled +type PatchCleanupFn = () => void; + +// Store all active patches for cleanup +let patches: PatchCleanupFn[] = []; + +// AnalyticsUtils and other tracking utilities +const AnalyticsUtils = findByProps("AnalyticsActionHandlers"); +const SuperPropUtils = findByProps("encodeProperties", "track"); +const VoiceStateUtils = findByProps("getVoiceStateMetadata"); +const CrashReportUtils = findByProps("submitLiveCrashReport"); +const MetricsUtils = findByProps("_metrics"); + +// Sentry interface definition to avoid TypeScript errors +interface SentryClient { + getOptions: () => { enabled: boolean }; + close: () => void; + open: () => void; +} + +interface SentryScope { + clear: () => void; +} + +interface SentryHub { + getClient: () => SentryClient | undefined; + getStackTop: () => { scope: SentryScope }; + getScope: () => SentryScope; + addBreadcrumb?: (breadcrumb: unknown) => void; +} + +interface GlobalSentry { + hub?: SentryHub; +} + +// Get Sentry objects with proper type checking +const sentryGlobal = (globalThis as unknown as { __SENTRY__?: GlobalSentry }) + .__SENTRY__; +const sentryHub = sentryGlobal?.hub; +const sentryClient = sentryHub?.getClient(); + +// Sentry related objects with type safety +const Sentry = { + initializer: findByProps("initSentry"), + main: sentryHub, + client: sentryClient, +}; + +// Utility function to create no-operation patches +const noop = (prop: string, parent: Record): PatchCleanupFn => { + try { + return instead(prop, parent, () => undefined); + } catch (e) { + return () => false; + } +}; + +// Network tracking prevention +function patchNetwork(): PatchCleanupFn { + // Analytics and tracking endpoints to block + const analyticsTest = + /client-analytics\.braintreegateway\.com|discord\.com\/api\/v9\/(science|track)|app\.adjust\..*|.*\.ingest\.sentry\.io/; + + try { + const unpatch = instead( + "send", + XMLHttpRequest.prototype, + function ( + this: XMLHttpRequest & { __sentry_xhr__?: { url: string } }, + args: unknown[], + orig: Function, + ) { + if ( + this.__sentry_xhr__?.url && + analyticsTest.test(this.__sentry_xhr__.url) + ) { + return undefined; + } + return orig.apply(this, args); + }, + ); + return unpatch; + } catch (e) { + return () => false; + } +} + +// Console patching to remove Sentry wrappers +function patchConsole(): PatchCleanupFn { + type ConsoleFunction = Function & { __sentry_original__?: Function }; + type ConsoleFunctions = Record; + + const sentrified: ConsoleFunctions = {}; + + try { + // We need to use Object.keys because TypeScript doesn't allow indexing Console with strings + (Object.keys(console) as Array).forEach((key) => { + const consoleFunc = (console as unknown as ConsoleFunctions)[key]; + if (consoleFunc) { + sentrified[key] = consoleFunc; + const originalFunc = consoleFunc.__sentry_original__; + (console as unknown as ConsoleFunctions)[key] = + originalFunc ?? consoleFunc; + } + }); + } catch (e) { + logger.log("Failed to de-sentrify console functions!", e); + } + + return () => { + Object.keys(sentrified).forEach((key) => { + if (sentrified[key]) { + (console as unknown as ConsoleFunctions)[key] = sentrified[key]; + } + }); + }; +} + +// Miscellaneous tracking functions +function patchMiscellaneous(): PatchCleanupFn { + const miscPatches = [ + // Global analytics utilities + AnalyticsUtils?.AnalyticsActionHandlers && + noop("handleTrack", AnalyticsUtils.AnalyticsActionHandlers), + AnalyticsUtils?.AnalyticsActionHandlers && + noop("handleFingerprint", AnalyticsUtils.AnalyticsActionHandlers), + + // Super properties tracking + SuperPropUtils && noop("track", SuperPropUtils), + + // Voice state metadata tracking + VoiceStateUtils && noop("trackWithMetadata", VoiceStateUtils), + + // Crash reporter + CrashReportUtils && noop("submitLiveCrashReport", CrashReportUtils), + + // Metrics + MetricsUtils?._metrics && noop("push", MetricsUtils._metrics), + ].filter(Boolean) as PatchCleanupFn[]; + + return () => miscPatches.forEach((p) => p()); +} + +// Sentry error reporting prevention +function patchSentry(): PatchCleanupFn { + const sentryPatches: PatchCleanupFn[] = []; + + // Add patches if Sentry components exist + if (Sentry.initializer) { + sentryPatches.push(noop("initSentry", Sentry.initializer)); + } + + if (Sentry.main && Sentry.main.addBreadcrumb) { + sentryPatches.push(noop("addBreadcrumb", Sentry.main)); + } + + // Disable Sentry client + if (Sentry.client) { + try { + Sentry.client.getOptions().enabled = false; + Sentry.client.close(); + + if (Sentry.main) { + if (Sentry.main.getStackTop) { + Sentry.main.getStackTop().scope.clear(); + } + + if (Sentry.main.getScope) { + Sentry.main.getScope().clear(); + } + } + } catch (e) { + // Silent fail + } + } + + return () => { + try { + sentryPatches.forEach((p) => p()); + + // Re-enable Sentry client on cleanup + if (Sentry.client) { + Sentry.client.getOptions().enabled = true; + Sentry.client.open(); + } + } catch (e) { + // Silent fail + } + }; +} export default defineCorePlugin({ - manifest: { - id: "vendetta.notrack", - version: "1.0.0", - type: "plugin", - spec: 3, - main: "", - display: { - name: "NoTrack", - description: "Disables Discord's telemetry", - authors: [{ name: "maisymoe" }] - } + manifest: { + id: "bunny.notrack", + version: "1.0.0", + type: "plugin", + spec: 3, + main: "", + display: { + name: "NoTrack", + description: "Disables Discord's telemetry", + authors: [{ name: "maisymoe" }], }, - start() { - network(), - console(), - miscellanous(), - sentry() - } -}); + }, + start() { + // Apply all patches + patches = [ + patchNetwork(), + patchConsole(), + patchMiscellaneous(), + patchSentry(), + ].filter(Boolean); + + logger.log("NoTrack: Enabled - all telemetry tracking disabled"); + }, + + stop() { + // Call all cleanup functions + patches.forEach((p) => p?.()); + patches = []; + + logger.log("NoTrack: Disabled - telemetry tracking restored"); + }, +}); diff --git a/src/core/plugins/notrack/lib/console.ts b/src/core/plugins/notrack/lib/console.ts deleted file mode 100644 index 7a69471..0000000 --- a/src/core/plugins/notrack/lib/console.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { logger } from "@lib/utils/logger"; - -export default () => { - const sentrified = {}; - - try { - Object.keys(console).forEach((x) => { - sentrified[x] = console[x]; - console[x] = console[x].__sentry_original__ ?? console[x]; - }); - } catch(e) { - logger.log("Failed to de-sentrify console functions!", e); - }; - - return () => Object.keys(console).forEach((x) => console[x] = sentrified[x]); -} diff --git a/src/core/plugins/notrack/lib/miscellanous.ts b/src/core/plugins/notrack/lib/miscellanous.ts deleted file mode 100644 index b6a6093..0000000 --- a/src/core/plugins/notrack/lib/miscellanous.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { findByProps } from "@metro"; -import { noop } from "./utils"; - -// Stuff in here is non-specific tracking scattered around the app - -const AnalyticsUtils = findByProps("AnalyticsActionHandlers"); -const SuperPropUtils = findByProps("encodeProperties", "track"); -const VoiceStateUtils = findByProps("getVoiceStateMetadata"); -const CrashReportUtils = findByProps("submitLiveCrashReport"); -const MetricsUtils = findByProps("_metrics"); - -export default () => { - const patches = [ - // Global analytics utilities - ...(["handleTrack", "handleFingerprint"].map((i) => noop(i, AnalyticsUtils.AnalyticsActionHandlers))), - // TODO: This is a getter - look into patching it - // noop("analyticsTrackingStoreMaker", AnalyticsUtils), - - // Super properties tracking - noop("track", SuperPropUtils), - - // Voice state metadata tracking - noop("trackWithMetadata", VoiceStateUtils), - - // Crash reporter - noop("submitLiveCrashReport", CrashReportUtils), - - // Whatever "metrics" implies - noop("push", MetricsUtils._metrics), - ]; - - return () => patches.forEach((p) => p()); -} diff --git a/src/core/plugins/notrack/lib/network.ts b/src/core/plugins/notrack/lib/network.ts deleted file mode 100644 index 68f762e..0000000 --- a/src/core/plugins/notrack/lib/network.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { instead } from "@lib/api/patcher"; - -export default () => { - // https://github.com/yellowsink/shelter-plugins/blob/3a62d1557b704f4617f72882edba826218eb8c6e/plugins/antitrack/index.js#L10-L25 - const analyticsTest = /client-analytics\.braintreegateway\.com|discord\.com\/api\/v9\/(science|track)|app\.adjust\..*|.*\.ingest\.sentry\.io/; - - return instead( - "send", - XMLHttpRequest.prototype, - function (args, orig) { if (!analyticsTest.test(this.__sentry_xhr__?.url)) return orig.apply(this, args) }, - ); -} diff --git a/src/core/plugins/notrack/lib/sentry.ts b/src/core/plugins/notrack/lib/sentry.ts deleted file mode 100644 index 5b01b7e..0000000 --- a/src/core/plugins/notrack/lib/sentry.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { findByProps } from "@metro"; -import { noop } from "./utils"; - -const Sentry = { - initializer: findByProps("initSentry"), - main: globalThis.__SENTRY__?.hub, - client: globalThis.__SENTRY__?.hub?.getClient(), -}; - -export default () => { - const patches = [ - noop("initSentry", Sentry.initializer), - noop("addBreadcrumb", Sentry.main), - ]; - - Sentry.client.getOptions().enabled = false; - Sentry.client.close(); - Sentry.main.getStackTop().scope.clear(); - Sentry.main.getScope().clear(); - - return () => { - patches.forEach((p) => p()); - Sentry.client.getOptions().enabled = true; - Sentry.client.open(); - }; -} diff --git a/src/core/plugins/notrack/lib/utils.ts b/src/core/plugins/notrack/lib/utils.ts deleted file mode 100644 index 55f871f..0000000 --- a/src/core/plugins/notrack/lib/utils.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { instead } from "@lib/api/patcher"; - -export const noop = (prop: string, parent: any) => instead(prop, parent, () => {}); diff --git a/src/core/plugins/quickinstall/forumPost.tsx b/src/core/plugins/quickinstall/forumPost.tsx index 25665ad..ad6fb7e 100644 --- a/src/core/plugins/quickinstall/forumPost.tsx +++ b/src/core/plugins/quickinstall/forumPost.tsx @@ -5,7 +5,13 @@ import { installTheme, removeTheme, themes } from "@lib/addons/themes"; import { findAssetId } from "@lib/api/assets"; import { isThemeSupported } from "@lib/api/native/loader"; import { after } from "@lib/api/patcher"; -import { HTTP_REGEX_MULTI, VD_DISCORD_SERVER_ID, VD_PLUGINS_CHANNEL_ID, VD_PROXY_PREFIX, VD_THEMES_CHANNEL_ID } from "@lib/utils/constants"; +import { + HTTP_REGEX_MULTI, + VD_DISCORD_SERVER_ID, + VD_PLUGINS_CHANNEL_ID, + VD_PROXY_PREFIX, + VD_THEMES_CHANNEL_ID, +} from "@lib/utils/constants"; import { lazyDestructure } from "@lib/utils/lazy"; import { Button } from "@metro/common/components"; import { findByProps, findByPropsLazy } from "@metro/wrappers"; @@ -16,74 +22,91 @@ type PostType = "Plugin" | "Theme"; // const ForumPostLongPressActionSheet = findByNameProxy("ForumPostLongPressActionSheet", false); // const { ActionSheetRow } = findByPropsProxy("ActionSheetRow"); -const { useFirstForumPostMessage } = lazyDestructure(() => findByProps("useFirstForumPostMessage")); +const { useFirstForumPostMessage } = lazyDestructure(() => + findByProps("useFirstForumPostMessage"), +); const forumReactions = findByPropsLazy("MostCommonForumPostReaction"); const postMap = { - Plugin: { - storage: VdPluginManager.plugins, - urlsFilter: (url: string) => url.startsWith(VD_PROXY_PREFIX), - installOrRemove: (url: string) => { - const isInstalled = postMap.Plugin.storage[url]; - return isInstalled ? VdPluginManager.removePlugin(url) : VdPluginManager.installPlugin(url); - } + Plugin: { + storage: VdPluginManager.plugins, + urlsFilter: (url: string) => url.startsWith(VD_PROXY_PREFIX), + installOrRemove: (url: string) => { + const isInstalled = postMap.Plugin.storage[url]; + return isInstalled + ? VdPluginManager.removePlugin(url) + : VdPluginManager.installPlugin(url); }, - Theme: { - storage: themes, - urlsFilter: (url: string) => url.endsWith(".json"), - installOrRemove: (url: string) => { - const isInstalled = postMap.Theme.storage[url]; - return isInstalled ? removeTheme(url) : installTheme(url); - }, - } + }, + Theme: { + storage: themes, + urlsFilter: (url: string) => url.endsWith(".json"), + installOrRemove: (url: string) => { + const isInstalled = postMap.Theme.storage[url]; + return isInstalled ? removeTheme(url) : installTheme(url); + }, + }, }; -function useExtractThreadContent(thread: any, _firstMessage = null, actionSheet = false): ([PostType, string]) | void { - if (thread.guild_id !== VD_DISCORD_SERVER_ID) return; +function useExtractThreadContent( + thread: any, + _firstMessage = null, + actionSheet = false, +): [PostType, string] | void { + if (thread.guild_id !== VD_DISCORD_SERVER_ID) return; - // Determine what type of addon this is. - let postType: PostType; - if (thread.parent_id === VD_PLUGINS_CHANNEL_ID) { - postType = "Plugin"; - } else if (thread.parent_id === VD_THEMES_CHANNEL_ID && isThemeSupported()) { - postType = "Theme"; - } else return; + // Determine what type of addon this is. + let postType: PostType; + if (thread.parent_id === VD_PLUGINS_CHANNEL_ID) { + postType = "Plugin"; + } else if (thread.parent_id === VD_THEMES_CHANNEL_ID && isThemeSupported()) { + postType = "Theme"; + } else return; - const { firstMessage } = actionSheet ? useFirstForumPostMessage(thread) : { firstMessage: _firstMessage }; + const { firstMessage } = actionSheet + ? useFirstForumPostMessage(thread) + : { firstMessage: _firstMessage }; - const urls = firstMessage?.content?.match(HTTP_REGEX_MULTI)?.filter(postMap[postType].urlsFilter); - if (!urls || !urls[0]) return; + const urls = firstMessage?.content + ?.match(HTTP_REGEX_MULTI) + ?.filter(postMap[postType].urlsFilter); + if (!urls || !urls[0]) return; - // Sync with lib/managers/plugins - if (postType === "Plugin" && !urls[0].endsWith("/")) urls[0] += "/"; + // Sync with lib/managers/plugins + if (postType === "Plugin" && !urls[0].endsWith("/")) urls[0] += "/"; - return [postType, urls[0]]; + return [postType, urls[0]]; } -function useInstaller(thread: any, firstMessage = null, actionSheet = false): [true] | [false, PostType, boolean, boolean, () => Promise] { - const [postType, url] = useExtractThreadContent(thread, firstMessage, actionSheet) ?? []; +function useInstaller( + thread: any, + firstMessage = null, + actionSheet = false, +): [true] | [false, PostType, boolean, boolean, () => Promise] { + const [postType, url] = + useExtractThreadContent(thread, firstMessage, actionSheet) ?? []; - useProxy(VdPluginManager.plugins); - useProxy(themes); + useProxy(VdPluginManager.plugins); + useProxy(themes); - const [isInstalling, setIsInstalling] = React.useState(false); + const [isInstalling, setIsInstalling] = React.useState(false); - if (!postType || !url) return [true]; + if (!postType || !url) return [true]; - const isInstalled = Boolean(postMap[postType].storage[url]); + const isInstalled = Boolean(postMap[postType].storage[url]); - const installOrRemove = async () => { - setIsInstalling(true); - try { - await postMap[postType].installOrRemove(url); - } catch (e: any) { - showToast(e.message, findAssetId("Small")); - } finally { - setIsInstalling(false); - } - }; + const installOrRemove = async () => { + setIsInstalling(true); + try { + await postMap[postType].installOrRemove(url); + } catch (e: any) { + showToast(e.message, findAssetId("Small")); + } finally { + setIsInstalling(false); + } + }; - return [false, postType, isInstalled, isInstalling, installOrRemove]; + return [false, postType, isInstalled, isInstalling, installOrRemove]; } // // apparently broken??? @@ -104,33 +127,43 @@ function useInstaller(thread: any, firstMessage = null, actionSheet = false): [t // ); // }); -const installButtonPatch = () => after("MostCommonForumPostReaction", forumReactions, ([{ thread, firstMessage }], res) => { - const [shouldReturn, _, installed, loading, installOrRemove] = useInstaller(thread, firstMessage, true); - if (shouldReturn) return; +const installButtonPatch = () => + after( + "MostCommonForumPostReaction", + forumReactions, + ([{ thread, firstMessage }], res) => { + const [shouldReturn, _, installed, loading, installOrRemove] = + useInstaller(thread, firstMessage, true); + if (shouldReturn) return; - return <> - {res} - + return ( + <> + {res} +