├── .npmrc
├── pnpm-workspace.yaml
├── .gitignore
├── plugins
├── realmoji
│ ├── package.json
│ ├── manifest.json
│ └── src
│ │ └── index.ts
├── devkitplus
│ ├── src
│ │ ├── def.ts
│ │ ├── index.ts
│ │ ├── patches
│ │ │ ├── globals.ts
│ │ │ ├── socket.ts
│ │ │ └── window.ts
│ │ └── settings.tsx
│ └── manifest.json
├── audiofix
│ ├── src
│ │ └── index.ts
│ └── manifest.json
├── no-typing
│ ├── src
│ │ └── index.ts
│ └── manifest.json
├── sacvu
│ ├── manifest.json
│ └── src
│ │ ├── settings.jsx
│ │ ├── index.js
│ │ └── payload.js
├── picture-links
│ ├── manifest.json
│ └── src
│ │ └── index.tsx
├── saucenao
│ ├── manifest.json
│ └── src
│ │ └── index.tsx
├── message-logger
│ ├── manifest.json
│ └── src
│ │ ├── settings.tsx
│ │ └── index.ts
├── url-import
│ ├── manifest.json
│ └── src
│ │ └── index.js
└── no-embed-copy
│ ├── manifest.json
│ └── src
│ └── index.ts
├── .prettierignore
├── tsconfig.json
├── prettier.config.js
├── package.json
├── README.md
├── LICENSE
└── .github
└── workflows
└── deploy.yml
/.npmrc:
--------------------------------------------------------------------------------
1 | shamefully-hoist=true
2 |
--------------------------------------------------------------------------------
/pnpm-workspace.yaml:
--------------------------------------------------------------------------------
1 | packages:
2 | - "plugins/*"
3 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | dist
2 | node_modules
3 | .pnpm-debug.log
4 | package-lock.json
5 |
--------------------------------------------------------------------------------
/plugins/realmoji/package.json:
--------------------------------------------------------------------------------
1 | {"dependencies":{"vendetta-extras":"^1.0.3"}}
2 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | .gitignore
2 | .prettierignore
3 | node_modules
4 | LICENSE
5 | pnpm-lock.yaml
6 | dist
7 |
--------------------------------------------------------------------------------
/plugins/devkitplus/src/def.ts:
--------------------------------------------------------------------------------
1 | export {};
2 |
3 | declare global {
4 | interface Window {
5 | __vendetta_loader?: LoaderIdentity;
6 | dk: any;
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/plugins/audiofix/src/index.ts:
--------------------------------------------------------------------------------
1 | import { ReactNative as RN } from "@vendetta/metro/common";
2 | import { instead } from "@vendetta/patcher";
3 |
4 | export const onUnload = instead("setCommunicationModeOn", RN.NativeModules.AudioManager === null ? RN.NativeModules.RTNAudioManager : RN.NativeModules.AudioManager, () => {});
5 |
--------------------------------------------------------------------------------
/plugins/realmoji/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Realmoji",
3 | "description": "Makes freemojis real",
4 | "authors": [
5 | {
6 | "name": "redstonekasi",
7 | "id": "265064055490871297"
8 | }
9 | ],
10 | "main": "src/index.ts",
11 | "vendetta": {
12 | "icon": "ic_emoji_24px"
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "outDir": "dist",
4 | "target": "ESNext",
5 | "module": "ESNext",
6 | "moduleResolution": "node",
7 | "esModuleInterop": true,
8 | "jsx": "react-native",
9 | "allowJs": true
10 | },
11 | "include": ["plugins", "node_modules/vendetta-types"]
12 | }
13 |
--------------------------------------------------------------------------------
/plugins/no-typing/src/index.ts:
--------------------------------------------------------------------------------
1 | import { instead } from "@vendetta/patcher";
2 | import { findByProps } from "@vendetta/metro";
3 |
4 | const Typing = findByProps("startTyping");
5 | const patches = ["startTyping", "stopTyping"].map((k) => instead(k, Typing, () => {}));
6 |
7 | export const onUnload = () => patches.forEach((unpatch) => unpatch());
8 |
--------------------------------------------------------------------------------
/plugins/no-typing/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "No typing",
3 | "description": "Hides your typing status from others.",
4 | "authors": [
5 | {
6 | "name": "redstonekasi",
7 | "id": "265064055490871297"
8 | }
9 | ],
10 | "main": "src/index.ts",
11 | "vendetta": {
12 | "icon": "ic_chat_bubble_16px"
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/plugins/devkitplus/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "devkitplus",
3 | "description": "Various tools of varying degree of usefulness.",
4 | "authors": [
5 | {
6 | "name": "redstonekasi",
7 | "id": "265064055490871297"
8 | }
9 | ],
10 | "main": "src/index.ts",
11 | "vendetta": {
12 | "icon": "ic_badge_staff"
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/plugins/sacvu/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "super_awesome_confidential_vendetta_update.ts",
3 | "description": "whatever may this do?",
4 | "authors": [
5 | {
6 | "name": "redstonekasi",
7 | "id": "265064055490871297"
8 | }
9 | ],
10 | "main": "src/index.js",
11 | "vendetta": {
12 | "icon": "ic_link"
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/prettier.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | printWidth: 100,
3 | tabWidth: 2,
4 | useTabs: false,
5 | semi: true,
6 | singleQuote: false,
7 | quoteProps: "as-needed",
8 | jsxSingleQuote: false,
9 | trailingComma: "all",
10 | bracketSpacing: true,
11 | bracketSameLine: true,
12 | arrowParens: "always",
13 | proseWrap: "always",
14 | };
15 |
--------------------------------------------------------------------------------
/plugins/picture-links/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Picture Links",
3 | "description": "Allows you to click on profile pictures and banners.",
4 | "authors": [
5 | {
6 | "name": "redstonekasi",
7 | "id": "265064055490871297"
8 | }
9 | ],
10 | "main": "src/index.tsx",
11 | "vendetta": {
12 | "icon": "ic_link"
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/plugins/saucenao/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Image search",
3 | "description": "Quickly open an image in various reverse image search services",
4 | "authors": [
5 | {
6 | "name": "redstonekasi",
7 | "id": "265064055490871297"
8 | }
9 | ],
10 | "main": "src/index.tsx",
11 | "vendetta": {
12 | "icon": "search"
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/plugins/audiofix/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Blueooth Audio Fix",
3 | "description": "Prevents discord from enabling handsfree mode while in a call",
4 | "authors": [
5 | {
6 | "name": "redstonekasi",
7 | "id": "265064055490871297"
8 | }
9 | ],
10 | "main": "src/index.ts",
11 | "vendetta": {
12 | "icon": "speaker"
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/plugins/message-logger/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Message Logger",
3 | "description": "Temporarily logs deletions for moderation purposes",
4 | "authors": [
5 | {
6 | "name": "redstonekasi",
7 | "id": "265064055490871297"
8 | }
9 | ],
10 | "main": "src/index.ts",
11 | "vendetta": {
12 | "icon": "ic_audit_log_24px"
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/plugins/url-import/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "URL import",
3 | "description": "Allows you to install plugins directly from a URL's action sheet.",
4 | "authors": [
5 | {
6 | "name": "redstonekasi",
7 | "id": "265064055490871297"
8 | }
9 | ],
10 | "main": "src/index.js",
11 | "vendetta": {
12 | "icon": "ic_link"
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/plugins/no-embed-copy/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "No Embed Copy",
3 | "description": "Makes pressing on an embed no longer copy it's content.",
4 | "authors": [
5 | {
6 | "name": "redstonekasi",
7 | "id": "265064055490871297"
8 | }
9 | ],
10 | "main": "src/index.ts",
11 | "vendetta": {
12 | "icon": "ic_message_copy"
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": true,
3 | "scripts": {
4 | "build": "node build.mjs"
5 | },
6 | "devDependencies": {
7 | "@rollup/plugin-commonjs": "^24.0.0",
8 | "@rollup/plugin-node-resolve": "^15.0.1",
9 | "esbuild": "^0.16.15",
10 | "rollup": "^3.9.1",
11 | "rollup-plugin-esbuild": "^5.0.0",
12 | "vendetta-types": "^2.4.20"
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/plugins/no-embed-copy/src/index.ts:
--------------------------------------------------------------------------------
1 | import { findByProps } from "@vendetta/metro";
2 | import { after, instead } from "@vendetta/patcher";
3 |
4 | const MessagesHandlersModule = findByProps("MessagesHandlers");
5 |
6 | export const onUnload = after("MessagesHandlers", MessagesHandlersModule, (_, ret) => {
7 | if (ret?.handleCopyText) instead("handleCopyText", ret, () => {});
8 | });
9 |
--------------------------------------------------------------------------------
/plugins/url-import/src/index.js:
--------------------------------------------------------------------------------
1 | import { id } from "@vendetta/plugin";
2 | import { removePlugin } from "@vendetta/plugins";
3 | import { getAssetIDByName } from "@vendetta/ui/assets";
4 | import { showToast } from "@vendetta/ui/toasts";
5 |
6 | setTimeout(() => {
7 | removePlugin(id);
8 | showToast("URL import has been integrated into Vendetta and thus removed.", getAssetIDByName("ic_information_filled_24px"));
9 | });
10 |
11 | export const onUnload = () => {};
12 |
--------------------------------------------------------------------------------
/plugins/devkitplus/src/index.ts:
--------------------------------------------------------------------------------
1 | import globals from "./patches/globals";
2 | import socket from "./patches/socket";
3 | import window from "./patches/window";
4 |
5 | import { storage } from "@vendetta/plugin";
6 |
7 | // Some default settings
8 | storage.assignGlobals ??= true;
9 | storage.autoDebugger ??= true;
10 | storage.autoRDC ??= true;
11 |
12 | globals(storage.assignGlobals);
13 | socket();
14 |
15 | export const onUnload = () => {
16 | globals(false);
17 | window();
18 | };
19 |
20 | export { default as settings } from "./settings";
21 |
--------------------------------------------------------------------------------
/plugins/devkitplus/src/patches/globals.ts:
--------------------------------------------------------------------------------
1 | import { patcher, metro, utils, debug } from "@vendetta";
2 | import { common } from "@vendetta/metro";
3 | import { findByKeyword, findByKeywordAll } from "./window";
4 |
5 | const patch = () => {
6 | const apply = {
7 | ...patcher,
8 | ...metro,
9 | ...common,
10 | ...utils,
11 | ...debug,
12 | findByKeyword,
13 | findByKeywordAll,
14 | };
15 |
16 | delete apply["React"];
17 |
18 | Object.assign(window, apply);
19 |
20 | return () => Object.keys(apply).forEach((k) => delete window[k]);
21 | }
22 |
23 | let unpatch;
24 |
25 | export default (on = true) => {
26 | unpatch?.();
27 | if (on) unpatch = patch();
28 | }
29 |
--------------------------------------------------------------------------------
/plugins/devkitplus/src/patches/socket.ts:
--------------------------------------------------------------------------------
1 | import { settings } from "@vendetta";
2 | import { connectToDebugger } from "@vendetta/debug";
3 | import { ReactNative } from "@vendetta/metro/common";
4 | import { storage } from "@vendetta/plugin";
5 |
6 | export const rdcProp = window.__vendetta_loader?.features.devtools?.prop;
7 |
8 | export default () => {
9 | // this is only run once since you'd permanently have the annoying toasts if it didn't
10 | if (storage.autoDebugger && settings.debuggerUrl)
11 | connectToDebugger(settings.debuggerUrl)
12 |
13 |
14 | if (storage.autoRDC && window[rdcProp]) {
15 | window[rdcProp].connectToDevTools({
16 | host: settings.debuggerUrl.split(":")[0],
17 | resolveRNStyle: ReactNative.StyleSheet.flatten,
18 | });
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/plugins/message-logger/src/settings.tsx:
--------------------------------------------------------------------------------
1 | import { ReactNative } from "@vendetta/metro/common";
2 | import { Forms } from "@vendetta/ui/components";
3 | import { getAssetIDByName } from "@vendetta/ui/assets";
4 | import { storage } from "@vendetta/plugin";
5 | import { useProxy } from "@vendetta/storage";
6 |
7 | const { FormIcon, FormSwitchRow } = Forms;
8 |
9 | storage.nopk ??= false;
10 |
11 | export default () => {
12 | useProxy(storage);
13 |
14 | return (
15 |
16 | }
19 | onValueChange={(v) => void (storage.nopk = v)}
20 | value={storage.nopk}
21 | />
22 |
23 | );
24 | };
25 |
--------------------------------------------------------------------------------
/plugins/sacvu/src/settings.jsx:
--------------------------------------------------------------------------------
1 | import { ReactNative } from "@vendetta/metro/common";
2 | import { FormIcon, FormSwitchRow } from "@vendetta/ui/components/Forms";
3 | import { useProxy } from "@vendetta/storage";
4 | import { storage } from "@vendetta/plugin";
5 |
6 | export default () => {
7 | useProxy(storage);
8 |
9 | return (
10 |
11 | }
15 | onValueChange={(v) => {
16 | vendetta.plugin.storage.random = v;
17 | }}
18 | value={vendetta.plugin.storage.random}
19 | />
20 |
21 | );
22 | };
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Vendetta plugins
2 |
3 | ### Picture Links
4 | `https://redstonekasi.github.io/vendetta-plugins/picture-links`
5 | Link's Pictures.
6 |
7 | ### devkitplus
8 | `https://redstonekasi.github.io/vendetta-plugins/devkitplus`
9 | Some useful features for making development easier, currently:
10 |
11 | - Exposing often used functions on window (see
12 | [here](https://github.com/redstonekasi/vendetta-plugins/blob/main/plugins/devkitplus/globals.js)
13 | for a list)
14 | - Auto-connecting to debugger websocket (and React DevTools if you have that)
15 |
16 | ### Realmoji
17 | `https://redstonekasi.github.io/vendetta-plugins/realmoji`
18 | Makes (freemojis)[https://beefers.github.io/strife/Freemoji] real.
19 |
20 | ### URL import
21 | `https://redstonekasi.github.io/vendetta-plugins/url-import`
22 | Allows you to install plugins directly from a URL's action sheet.
23 |
24 | ### No typing
25 | `https://redstonekasi.github.io/vendetta-plugins/no-typing`
26 | Hides your typing status from others.
27 |
--------------------------------------------------------------------------------
/plugins/devkitplus/src/patches/window.ts:
--------------------------------------------------------------------------------
1 | import { after } from "@vendetta/patcher";
2 | import { find, findAll } from "@vendetta/metro";
3 |
4 | const recons = [];
5 |
6 | const snipe = (funcName, funcParent, oneTime = false) => {
7 | const unrecon = after(
8 | funcName,
9 | funcParent,
10 | (args, ret) => console.log(`RECON: ${funcName}`, args, ret),
11 | oneTime
12 | );
13 | recons.push(unrecon);
14 | return unrecon;
15 | };
16 |
17 | const shotgun = (funcParent, oneTime = false) => {
18 | const unrecons = Object.getOwnPropertyNames(funcParent)
19 | .filter((field) => typeof funcParent[field] === "function")
20 | .map((funcName) => snipe(funcName, funcParent, oneTime));
21 |
22 | recons.push(...unrecons);
23 | return () => unrecons.forEach((f) => f());
24 | };
25 |
26 | const keywordFilter = (ks) => (m) => ks.every((s) => Object.keys(m).some((k) => k.toLowerCase().includes(s.toLowerCase())));
27 |
28 | export const findByKeyword = (...k) => find(keywordFilter(k));
29 | export const findByKeywordAll = (...k) => findAll(keywordFilter(k));
30 |
31 | window.dk = {
32 | snipe,
33 | shotgun,
34 | wipe: () => {
35 | recons.forEach((f) => f());
36 | recons.length = 0;
37 | },
38 | findByKeyword,
39 | findByKeywordAll
40 | };
41 |
42 | export default () => delete window.dk;
43 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | BSD 3-Clause License
2 |
3 | Copyright (c) 2022 redstonekasi
4 |
5 | Redistribution and use in source and binary forms, with or without
6 | modification, are permitted provided that the following conditions are met:
7 |
8 | 1. Redistributions of source code must retain the above copyright notice, this
9 | list of conditions and the following disclaimer.
10 |
11 | 2. Redistributions in binary form must reproduce the above copyright notice,
12 | this list of conditions and the following disclaimer in the documentation
13 | and/or other materials provided with the distribution.
14 |
15 | 3. Neither the name of the copyright holder nor the names of its
16 | contributors may be used to endorse or promote products derived from
17 | this software without specific prior written permission.
18 |
19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
23 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
25 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
26 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
27 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 |
--------------------------------------------------------------------------------
/plugins/devkitplus/src/settings.tsx:
--------------------------------------------------------------------------------
1 | import { ReactNative } from "@vendetta/metro/common";
2 | import { Forms } from "@vendetta/ui/components";
3 | import { getAssetIDByName } from "@vendetta/ui/assets";
4 | import { storage } from "@vendetta/plugin";
5 | import { useProxy } from "@vendetta/storage";
6 |
7 | import globals from "./patches/globals";
8 | import { rdcProp } from "./patches/socket";
9 |
10 | const { FormDivider, FormIcon, FormSwitchRow } = Forms;
11 |
12 | export default () => {
13 | useProxy(storage);
14 |
15 | return (
16 |
17 | }
21 | onValueChange={(v) => {
22 | storage.assignGlobals = v;
23 | globals(v);
24 | }}
25 | value={storage.assignGlobals}
26 | />
27 |
28 | }
32 | onValueChange={(v) => (storage.autoDebugger = v)}
33 | value={storage.autoDebugger}
34 | />
35 |
36 | {window[rdcProp] && }
40 | onValueChange={(v) => (storage.autoRDC = v)}
41 | value={storage.autoRDC}
42 | />}
43 |
44 | );
45 | };
46 |
--------------------------------------------------------------------------------
/.github/workflows/deploy.yml:
--------------------------------------------------------------------------------
1 | name: Build and deploy
2 |
3 | on:
4 | push:
5 | branches: [main]
6 |
7 | jobs:
8 | deploy:
9 | runs-on: ubuntu-latest
10 |
11 | steps:
12 | - uses: actions/checkout@v2
13 |
14 | - name: Prepare environment
15 | uses: actions/setup-node@v2
16 | with:
17 | node-version: "16"
18 |
19 | - run: npm install --global pnpm
20 |
21 | - name: Install deps
22 | run: pnpm m i
23 |
24 | - name: Build plugin(s)
25 | run: node ./build.mjs
26 |
27 | # Foolproof feature:
28 | # - Copies over README so that the root of the deployed website shows it
29 | # - Changes 404 page to README so that you don't get lost while clicking links
30 | # If you remove this step then you should probably remove the enable_jekyll option in the next one
31 | - name: Copy additional files
32 | run: |
33 | cp README.md dist/README.md
34 | printf -- "---\npermalink: /404.html\n---\n" > dist/404.md
35 | printf -- "> **Note:** You accessed a link that returned a 404, probably by clicking one of the plugin links. You're supposed to copy the link address and add it into Vendetta.\n\n" >> dist/404.md
36 | cat README.md >> dist/404.md
37 |
38 | # Documentation: https://github.com/peaceiris/actions-gh-pages
39 | - name: Deploy to GitHub Pages
40 | uses: peaceiris/actions-gh-pages@v3
41 | with:
42 | github_token: ${{ secrets.GITHUB_TOKEN }}
43 | publish_dir: ./dist
44 | # Makes it so the md files in the previous step get processed by GitHub Pages
45 | enable_jekyll: true
46 | # This creates the CNAME file required to host GitHub Pages on a custom domain
47 | # cname: example.com
48 |
--------------------------------------------------------------------------------
/plugins/sacvu/src/index.js:
--------------------------------------------------------------------------------
1 | import { ReactNative } from "@vendetta/metro/common";
2 | import { storage } from "@vendetta/plugin";
3 | import { loader } from "@vendetta";
4 |
5 | import payload from "./payload.js" assert {type: "raw"};
6 |
7 | export const onLoad = async function() {
8 | __vendetta_loader.features.loaderConfig = false;
9 | loader.config.customLoadUrl = {
10 | enabled: true,
11 | url: "http://0.0.0.0",
12 | };
13 |
14 | const ven = await fetch("https://raw.githubusercontent.com/vendetta-mod/builds/master/vendetta.js", { cache: "no-store" });
15 | if (!ven.ok) return;
16 |
17 | const vendettaJs = await ven.text();
18 | const wrappedPayload = `(()=>{${payload.replace("__RAND_PROVIDER__", storage.random ? "math" : "xoshiro")}})();`;
19 | const newVendetta = wrappedPayload + "\n" + vendettaJs;
20 |
21 | const [readPath, writeTarget, writeVjs] = ReactNative.Platform.select({
22 | default: ["/data/user/0/com.discord/cache/vendetta.js", "cache", "vendetta.js"],
23 | ios: [
24 | `${nativeModuleProxy.DCDFileManager.getConstants().DocumentsDirPath}/vendetta.js`,
25 | "documents",
26 | "Documents/vendetta.js",
27 | ],
28 | });
29 |
30 | const oldVendetta = await nativeModuleProxy.DCDFileManager.readFile(readPath, "utf8");
31 | await nativeModuleProxy.DCDFileManager.writeFile(writeTarget, writeVjs, newVendetta, "utf8");
32 | if (oldVendetta !== newVendetta)
33 | ReactNative.NativeModules.BundleUpdaterManager.reload();
34 | }
35 |
36 | export const onUnload = () => {
37 | // I could make it save the old url but I don't want to right now.
38 | // Though if I do I might as well make it also influence which URL we download Vendetta from in here.
39 | loader.config.customLoadUrl = {
40 | enabled: false,
41 | url: "http://localhost:4040/vendetta.js",
42 | };
43 |
44 | // Give Vendetta time to do it's thing
45 | setTimeout(() => ReactNative.NativeModules.BundleUpdaterManager.reload(), 200);
46 | };
47 |
48 | export { default as settings } from "./settings.jsx";
--------------------------------------------------------------------------------
/plugins/sacvu/src/payload.js:
--------------------------------------------------------------------------------
1 | console.log("VENDETTA FRENDETTA MAJORINCETTA");
2 |
3 | const basicFind = (prop) => Object.values(window.modules).find(m => m?.publicModule.exports?.[prop])?.publicModule?.exports;
4 | const color = basicFind("SemanticColor");
5 |
6 | const provider = {
7 | xoshiro(key) {
8 | let h1 = 1779033703,
9 | h2 = 3144134277,
10 | h3 = 1013904242,
11 | h4 = 2773480762;
12 | for (let i = 0, k; i < key.length; i++) {
13 | k = key.charCodeAt(i);
14 | h1 = h2 ^ Math.imul(h1 ^ k, 597399067);
15 | h2 = h3 ^ Math.imul(h2 ^ k, 2869860233);
16 | h3 = h4 ^ Math.imul(h3 ^ k, 951274213);
17 | h4 = h1 ^ Math.imul(h4 ^ k, 2716044179);
18 | }
19 | h1 = Math.imul(h3 ^ (h1 >>> 18), 597399067);
20 | h2 = Math.imul(h4 ^ (h2 >>> 22), 2869860233);
21 | h3 = Math.imul(h1 ^ (h3 >>> 17), 951274213);
22 | h4 = Math.imul(h2 ^ (h4 >>> 19), 2716044179);
23 | let [a, b, c, d] = [(h1 ^ h2 ^ h3 ^ h4) >>> 0, (h2 ^ h1) >>> 0, (h3 ^ h1) >>> 0, (h4 ^ h1) >>> 0];
24 | return function murder() {
25 | let t = b << 9,
26 | r = a * 5;
27 | r = ((r << 7) | (r >>> 25)) * 9;
28 | c ^= a;
29 | d ^= b;
30 | b ^= c;
31 | a ^= d;
32 | c ^= t;
33 | d = (d << 11) | (d >>> 21);
34 | return (r >>> 0) / 4294967296;
35 | }
36 | },
37 | math: () => Math.random,
38 | };
39 |
40 | function computeColor(key) {
41 | const murder = provider["__RAND_PROVIDER__"](key);
42 | let g = "#";
43 | var h = "0123456789abcdef";
44 | for (var i = 0; i < 6; i++) {
45 | g += h[Math.floor(murder() * 16)];
46 | }
47 | return g;
48 | }
49 |
50 | const keys = Object.keys(color.default.colors);
51 | const refs = Object.values(color.default.colors);
52 | const computedColors = {};
53 |
54 | const oldRaw = color.default.unsafe_rawColors;
55 | color.default.unsafe_rawColors = new Proxy(oldRaw, {
56 | get: (_, prop) => (computedColors[prop] ??= computeColor(prop)),
57 | });
58 |
59 | color.default.meta.resolveSemanticColor = (theme, ref) => {
60 | let name = keys[refs.indexOf(ref)];
61 | if (theme !== "dark") name = theme.toUpperCase() + "_" + name;
62 | return (computedColors[name] ??= computeColor(name));
63 | };
64 |
--------------------------------------------------------------------------------
/plugins/realmoji/src/index.ts:
--------------------------------------------------------------------------------
1 | import { findByName, findByStoreName } from "@vendetta/metro";
2 | import { after, before } from "@vendetta/patcher";
3 | import { Embed, Message } from "vendetta-extras";
4 |
5 | const patches = [];
6 | const { getCustomEmojiById } = findByStoreName("EmojiStore");
7 | const RowManager = findByName("RowManager");
8 | const emojiRegex = /https:\/\/cdn.discordapp.com\/emojis\/(\d+)\.\w+/;
9 |
10 | patches.push(before("generate", RowManager.prototype, ([data]) => {
11 | if (data.rowType !== 1) return;
12 |
13 | let content = data.message.content as string;
14 | if (!content?.length) return;
15 | const matchIndex = content.match(emojiRegex)?.index;
16 | if (matchIndex === undefined) return;
17 | const emojis = content.slice(matchIndex).trim().split("\n");
18 | if (!emojis.every((s) => s.match(emojiRegex))) return;
19 | content = content.slice(0, matchIndex);
20 |
21 | while (content.indexOf(" ") !== -1)
22 | content = content.replace(" ", ` ${emojis.shift()} `);
23 |
24 | content = content.trim();
25 | if (emojis.length) content += ` ${emojis.join(" ")}`;
26 |
27 | const embeds = data.message.embeds as Embed[];
28 | for (let i = 0; i < embeds.length; i++) {
29 | const embed = embeds[i];
30 | if (embed.type === "image" && embed.url.match(emojiRegex))
31 | embeds.splice(i--, 1);
32 | }
33 |
34 | data.message.content = content;
35 | data.__realmoji = true;
36 | }));
37 |
38 | patches.push(after("generate", RowManager.prototype, ([data], row) => {
39 | if (data.rowType !== 1 || data.__realmoji !== true) return;
40 | const { content } = row.message as Message;
41 | if (!Array.isArray(content)) return;
42 |
43 | const jumbo = content.every((c) => (c.type === "link" && c.target.match(emojiRegex)) || (c.type === "text" && c.content === " "));
44 |
45 | for (let i = 0; i < content.length; i++) {
46 | const el = content[i];
47 | if (el.type !== "link") continue;
48 |
49 | const match = el.target.match(emojiRegex);
50 | if (!match) continue;
51 | const url = `${match[0]}?size=128`;
52 |
53 | const emoji = getCustomEmojiById(match[1]);
54 |
55 | content[i] = {
56 | type: "customEmoji",
57 | id: match[1],
58 | alt: emoji?.name ?? "",
59 | src: url,
60 | frozenSrc: url.replace("gif", "webp"),
61 | jumboable: jumbo ? true : undefined,
62 | };
63 | }
64 | }));
65 |
66 | export const onUnload = () => patches.forEach((unpatch) => unpatch());
67 |
--------------------------------------------------------------------------------
/plugins/picture-links/src/index.tsx:
--------------------------------------------------------------------------------
1 | import { findByProps, findByName, findByStoreName } from "@vendetta/metro";
2 | import { after } from "@vendetta/patcher";
3 | import { ReactNative } from "@vendetta/metro/common";
4 |
5 | const { Pressable } = findByProps("Button", "Text", "View");
6 | const ProfileBanner = findByName("ProfileBanner", false);
7 | const HeaderAvatar = findByName("HeaderAvatar", false);
8 | const GuildIcon = findByName("GuildIcon");
9 | const { openMediaModal } = findByName("MediaModal", false);
10 | const { hideActionSheet } = findByProps("hideActionSheet");
11 | const { getChannelId } = findByStoreName("SelectedChannelStore");
12 | const { getGuildId } = findByStoreName("SelectedGuildStore");
13 |
14 | function openModal(uri, event) {
15 | ReactNative.Image.getSize(uri, (width, height) => {
16 | hideActionSheet(); // hide user sheet
17 | openMediaModal({
18 | initialSources: [
19 | {
20 | uri,
21 | sourceURI: uri,
22 | width,
23 | height,
24 | guildId: getGuildId(),
25 | channelId: getChannelId(),
26 | },
27 | ],
28 | initialIndex: 0,
29 | originLayout: {
30 | width: 0, // this would ideally be the size of the small pfp but this proved very hard to implement
31 | height: 0,
32 | x: event.pageX,
33 | y: event.pageY,
34 | resizeMode: "fill",
35 | },
36 | });
37 | });
38 | }
39 |
40 | const unpatchAvatar = after("default", HeaderAvatar, ([{ user, style, guildId }], res) => {
41 | const guildSpecific = user.guildMemberAvatars?.[guildId] && `https://cdn.discordapp.com/guilds/${guildId}/users/${user.id}/avatars/${user.guildMemberAvatars[guildId]}.png`;
42 | const image = user?.getAvatarURL?.(false, 4096, true);
43 | if (!image) return res;
44 |
45 | const url =
46 | typeof image === "number"
47 | ? `https://cdn.discordapp.com/embed/avatars/${Number(BigInt(user.id) >> 22n) % 6}.png`
48 | : image?.replace(".webp", ".png");
49 |
50 | delete res.props.style;
51 |
52 | return (
53 | openModal(url, nativeEvent)}
55 | onLongPress={({ nativeEvent }) => guildSpecific && openModal(guildSpecific, nativeEvent)}
56 | style={style}>
57 | {res}
58 |
59 | );
60 | });
61 |
62 | const unpatchBanner = after("default", ProfileBanner, ([{ bannerSource }], res) => {
63 | if (typeof bannerSource?.uri !== "string" || !res) return res;
64 |
65 | const url = bannerSource.uri
66 | .replace(/(?:\?size=\d{3,4})?$/, "?size=4096")
67 | .replace(".webp", ".png");
68 |
69 | return openModal(url, nativeEvent)}>{res};
70 | });
71 |
72 | const unpatchGuildIcon = after("render", GuildIcon.prototype, function (_, res) {
73 | if (this.props?.size !== "XLARGE") return;
74 | const url = this.props?.guild?.getIconURL?.(4096);
75 | if (!url) return res;
76 |
77 | return (
78 | openModal(url, nativeEvent)}>
79 | {res}
80 |
81 | );
82 | });
83 |
84 | export function onUnload() {
85 | unpatchAvatar();
86 | unpatchBanner();
87 | unpatchGuildIcon();
88 | }
89 |
--------------------------------------------------------------------------------
/plugins/message-logger/src/index.ts:
--------------------------------------------------------------------------------
1 | import { findByName, findByProps } from "@vendetta/metro";
2 | import { FluxDispatcher, ReactNative } from "@vendetta/metro/common";
3 | import { after, before, instead } from "@vendetta/patcher";
4 |
5 | const patches = [];
6 | const ChannelMessages = findByProps("_channelMessages");
7 | const MessageRecordUtils = findByProps("updateMessageRecord", "createMessageRecord");
8 | const MessageRecord = findByName("MessageRecord", false);
9 | const RowManager = findByName("RowManager");
10 |
11 | import { storage } from "@vendetta/plugin";
12 |
13 | patches.push(before("dispatch", FluxDispatcher, ([event]) => {
14 | if (event.type === "MESSAGE_DELETE") {
15 | if (event.__vml_cleanup) return event;
16 |
17 | const channel = ChannelMessages.get(event.channelId);
18 | const message = channel?.get(event.id);
19 | if (!message) return event;
20 |
21 | if (message.author?.id == "1") return event;
22 | if (message.state == "SEND_FAILED") return event;
23 |
24 | storage.nopk && fetch(`https://api.pluralkit.me/v2/messages/${encodeURIComponent(message.id)}`)
25 | .then((res) => res.json())
26 | .then((data) => {
27 | if (message.id === data.original && !data.member?.keep_proxy) {
28 | FluxDispatcher.dispatch({
29 | type: "MESSAGE_DELETE",
30 | id: message.id,
31 | channelId: message.channel_id,
32 | __vml_cleanup: true,
33 | });
34 | }
35 | });
36 |
37 | return [{
38 | message: {
39 | ...message.toJS(),
40 | __vml_deleted: true,
41 | },
42 | type: "MESSAGE_UPDATE",
43 | }];
44 | }
45 | }));
46 |
47 | patches.push(after("generate", RowManager.prototype, ([data], row) => {
48 | if (data.rowType !== 1) return;
49 | if (data.message.__vml_deleted) {
50 | row.message.edited = "deleted";
51 | row.backgroundHighlight ??= {};
52 | row.backgroundHighlight.backgroundColor = ReactNative.processColor("#da373c22");
53 | row.backgroundHighlight.gutterColor = ReactNative.processColor("#da373cff");
54 | }
55 | }));
56 |
57 | patches.push(instead("updateMessageRecord", MessageRecordUtils, function ([oldRecord, newRecord], orig) {
58 | if (newRecord.__vml_deleted) {
59 | return MessageRecordUtils.createMessageRecord(newRecord, oldRecord.reactions);
60 | }
61 | return orig.apply(this, [oldRecord, newRecord]);
62 | }));
63 |
64 | patches.push(after("createMessageRecord", MessageRecordUtils, function ([message], record) {
65 | record.__vml_deleted = message.__vml_deleted;
66 | // record.__vml_edits = message.__vml_edits;
67 | }));
68 |
69 | patches.push(after("default", MessageRecord, ([props], record) => {
70 | record.__vml_deleted = !!props.__vml_deleted;
71 | // record.__vml_edits = props.__vml_edits;
72 | }));
73 |
74 | export const onUnload = () => {
75 | patches.forEach((unpatch) => unpatch());
76 |
77 | for (const channelId in ChannelMessages._channelMessages) {
78 | for (const message of ChannelMessages._channelMessages[channelId]._array) {
79 | message.__vml_deleted && FluxDispatcher.dispatch({
80 | type: "MESSAGE_DELETE",
81 | id: message.id,
82 | channelId: message.channel_id,
83 | __vml_cleanup: true,
84 | });
85 | }
86 | }
87 | };
88 |
89 | export { default as settings } from "./settings";
90 |
--------------------------------------------------------------------------------
/plugins/saucenao/src/index.tsx:
--------------------------------------------------------------------------------
1 | import { React, ReactNative as RN } from "@vendetta/metro/common";
2 | import { Forms } from "@vendetta/ui/components";
3 | import { findByProps, findByName } from "@vendetta/metro";
4 | import { after, before } from "@vendetta/patcher";
5 | import { getAssetIDByName } from "@vendetta/ui/assets";
6 | import { storage } from "@vendetta/plugin";
7 | import { useProxy } from "@vendetta/storage";
8 |
9 | const { FormRow } = Forms;
10 | const Icon = findByName("Icon") ?? findByProps("Sizes", "compare") as unknown as React.ElementType;
11 |
12 | const ActionSheet = findByProps("openLazy", "hideActionSheet");
13 | const { openURL } = findByProps("openURL", "openDeeplink");
14 |
15 | const SearchIcon =
16 |
17 | interface Service {
18 | name: string; // service's name
19 | url: string; // service url with %s template
20 | default?: boolean; // on by default?
21 | }
22 |
23 | const services: Record = {
24 | saucenao: {
25 | name: "SauceNAO",
26 | url: `https://saucenao.com/search.php?url=%s`,
27 | default: true,
28 | },
29 | tracemoe: {
30 | name: "trace.moe",
31 | url: `https://trace.moe/?url=%s`,
32 | default: true,
33 | },
34 | iqdb: {
35 | name: "IQDB",
36 | url: `https://iqdb.org/?url=%s`,
37 | },
38 | imgops: {
39 | name: "ImgOps",
40 | url: `https://imgops.com/%s`,
41 | },
42 | tineye: {
43 | name: "TinEye",
44 | url: `https://tineye.com/search?url=%s`,
45 | },
46 | google: {
47 | name: "Google Images",
48 | url: `https://www.google.com/searchbyimage?image_url=%s&safe=off&sbisrc=cr_1_5_2`,
49 | },
50 | yandex: {
51 | name: "Yandex Images",
52 | url: `https://yandex.com/images/search?rpt=imageview&url=%s`,
53 | },
54 | };
55 |
56 |
57 | // Just for a bit of separation
58 | export const onLoad = () => {
59 | storage.services ??= {};
60 | for (const [id, service] of Object.entries(services))
61 | storage.services[id] ??= service.default ?? false;
62 | };
63 |
64 | export const onUnload = before("openLazy", ActionSheet, ([component, key]) => {
65 | if (key !== "MediaShareActionSheet") return;
66 | component.then((instance) => {
67 | const unpatchInstance = after("default", instance, ([{ syncer }], res) => {
68 | React.useEffect(() => void unpatchInstance(), []);
69 |
70 | let source = syncer.sources[syncer.index.value];
71 | if (Array.isArray(source)) source = source[0];
72 | const url = source.sourceURI ?? source.uri;
73 |
74 | const rows = res.props.children.props.children; // findInReactTree?
75 |
76 | rows.push(...Object.keys(services).filter((id) => storage.services[id]).map((id) =>
77 | openURL(services[id].url.replace("%s", url))}
81 | />
82 | ));
83 | });
84 | });
85 | });
86 |
87 | const { FormSection, FormDivider, FormRadioRow } = Forms;
88 |
89 | export const settings = () => {
90 | useProxy(storage);
91 |
92 | return (
93 |
94 |
95 |
99 | void (storage.services[id] = !storage.services[id])}
103 | />
104 | }
105 | />
106 |
107 |
108 | );
109 | }
110 |
--------------------------------------------------------------------------------