├── .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 | --------------------------------------------------------------------------------