From 13bd7f0b0ade0084d8a29c828ae9faf609474571 Mon Sep 17 00:00:00 2001 From: thororen1234 <78185467+thororen1234@users.noreply.github.com> Date: Mon, 18 Aug 2025 14:41:49 -0400 Subject: [PATCH 01/21] Automate Chrome Publishing --- .github/workflows/build.yml | 7 --- .github/workflows/publish.yml | 90 ++++++++++++++++++++++------------- 2 files changed, 57 insertions(+), 40 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 17b520fbd..77250b9d3 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -69,13 +69,6 @@ jobs: run: | gh release upload latest --clobber dist/release/* - - name: Create Tag - uses: butlerlogic/action-autotag@1.1.2 - env: - GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" - with: - tag_prefix: "v" - - name: Upload Plugin JSONs to Equibored repo run: | git config --global user.name "GitHub-Actions" diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 507a00894..537197267 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -1,44 +1,68 @@ name: Release Browser Extension on: - push: - tags: - - v* + push: + branches: + - main jobs: - Publish: - runs-on: ubuntu-latest + release: + runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 - - name: check that tag matches package.json version - run: | - pkg_version="v$(jq -r .version < package.json)" - if [[ "${{ github.ref_name }}" != "$pkg_version" ]]; then - echo "Tag ${{ github.ref_name }} does not match package.json version $pkg_version" >&2 - exit 1 - fi + - name: Get package.json version + id: pkg + run: echo "version=v$(jq -r .version < package.json)" >> $GITHUB_OUTPUT - - uses: pnpm/action-setup@v3 + - name: Get latest Git tag + id: tag + run: | + latest_tag=$(git describe --tags --abbrev=0 2>/dev/null || echo "v0.0.0") + echo "latest=$latest_tag" >> $GITHUB_OUTPUT - - name: Use Node.js 20 - uses: actions/setup-node@v4 - with: - node-version: 20 - cache: "pnpm" + - name: Check if package.json version is newer + id: check + run: | + pkg="${{ steps.pkg.outputs.version }}" + latest="${{ steps.tag.outputs.latest }}" - - name: Install dependencies - run: pnpm install --frozen-lockfile + echo "Latest tag: $latest" + echo "Package version: $pkg" - - name: Build web - run: pnpm buildWebStandalone + if [ "$pkg" != "$latest" ] && [ "$(sort -V <<<"$latest"$'\n'"$pkg" | tail -n1)" = "$pkg" ]; then + echo "release=true" >> $GITHUB_OUTPUT + else + echo "release=false" >> $GITHUB_OUTPUT - - name: Publish extension - run: | - cd dist/browser/chromium-unpacked - pnpx chrome-webstore-upload-cli@2.1.0 upload --auto-publish - env: - EXTENSION_ID: ${{ secrets.CHROME_EXTENSION_ID }} - CLIENT_ID: ${{ secrets.CHROME_CLIENT_ID }} - CLIENT_SECRET: ${{ secrets.CHROME_CLIENT_SECRET }} - REFRESH_TOKEN: ${{ secrets.CHROME_REFRESH_TOKEN }} + - name: Stop if not newer + if: steps.check.outputs.release == 'false' + run: | + echo "No new version to release (package.json ${{ steps.pkg.outputs.version }} <= latest tag ${{ steps.tag.outputs.latest }})" + exit 0 + + - uses: pnpm/action-setup@v3 + + - name: Use Node.js 20 + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: "pnpm" + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Build web + run: pnpm buildWebStandalone + + - name: Publish extension + run: | + cd dist/browser/chromium-unpacked + pnpx chrome-webstore-upload-cli@2.1.0 upload --auto-publish + env: + EXTENSION_ID: ${{ secrets.CHROME_EXTENSION_ID }} + CLIENT_ID: ${{ secrets.CHROME_CLIENT_ID }} + CLIENT_SECRET: ${{ secrets.CHROME_CLIENT_SECRET }} + REFRESH_TOKEN: ${{ secrets.CHROME_REFRESH_TOKEN }} From 1ff7fe1537bc53d4c6ba4869413b6526009ae4ff Mon Sep 17 00:00:00 2001 From: thororen1234 <78185467+thororen1234@users.noreply.github.com> Date: Mon, 18 Aug 2025 14:44:23 -0400 Subject: [PATCH 02/21] Make sure to make new tag --- .github/workflows/publish.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 537197267..e710003dc 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -43,6 +43,13 @@ jobs: echo "No new version to release (package.json ${{ steps.pkg.outputs.version }} <= latest tag ${{ steps.tag.outputs.latest }})" exit 0 + - name: Create Tag + uses: butlerlogic/action-autotag@1.1.2 + env: + GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" + with: + tag_prefix: "v" + - uses: pnpm/action-setup@v3 - name: Use Node.js 20 From b06404caed16a43c93e97a415378c6ec62c08f35 Mon Sep 17 00:00:00 2001 From: Etorix <92535668+EtorixDev@users.noreply.github.com> Date: Tue, 19 Aug 2025 05:46:03 -0700 Subject: [PATCH 03/21] Patch fix. (#350) --- src/equicordplugins/questify/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/equicordplugins/questify/index.tsx b/src/equicordplugins/questify/index.tsx index 133c0272a..260aaf8b4 100644 --- a/src/equicordplugins/questify/index.tsx +++ b/src/equicordplugins/questify/index.tsx @@ -1025,7 +1025,7 @@ export default definePlugin({ }, { // Open Video Modal - match: /(\i\?)(\(0,\i.openVideoQuestModal\)\({quest:(\i))/, + match: /(\i\?)?(\(0,\i.openVideoQuestModal\)\({quest:(\i))/, replace: "$1questifyContinue&&$2" }, { From 36664455717f788e0dfb6d14eb77b4045c45a2ef Mon Sep 17 00:00:00 2001 From: thororen1234 <78185467+thororen1234@users.noreply.github.com> Date: Tue, 19 Aug 2025 14:15:13 -0400 Subject: [PATCH 04/21] More Support Info --- src/plugins/_core/supportHelper.tsx | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/plugins/_core/supportHelper.tsx b/src/plugins/_core/supportHelper.tsx index 263bf51c8..aa9674cb2 100644 --- a/src/plugins/_core/supportHelper.tsx +++ b/src/plugins/_core/supportHelper.tsx @@ -56,6 +56,7 @@ const TrustedRolesIds = [ const AsyncFunction = async function () { }.constructor; const ShowCurrentGame = getUserSettingLazy("status", "showCurrentGame")!; +const ShowEmbeds = getUserSettingLazy("textAndImages", "renderEmbeds")!; async function forceUpdate() { const outdated = await checkForUpdates(); @@ -121,12 +122,23 @@ async function generateDebugInfoMessage() { info["Last Crash Reason"] = (await tryOrElse(() => DiscordNative.processUtils.getLastCrash(), undefined))?.rendererCrashReason ?? "N/A"; } + const potentiallyProblematicPlugins = [ + "NoRPC", "NoProfileThemes", "NoMosaic", "Moyai", "AlwaysAnimate", "ClientTheme", + "Equissant", "Ingtoninator", "KeyboardSounds", "NeverPausePreviews", "NoDeleteSafety", + "NoRoleHeaders", "NoSystemBadge" + ].filter(Vencord.Plugins.isPluginEnabled) ?? []; + + if (Vencord.Plugins.isPluginEnabled("CustomIdle") && Vencord.Settings.plugins.CustomIdle.idleTimeout === 0) { + potentiallyProblematicPlugins.push("CustomIdle"); + } + const commonIssues = { - "NoRPC enabled": Vencord.Plugins.isPluginEnabled("NoRPC"), "Activity Sharing disabled": tryOrElse(() => !ShowCurrentGame.getSetting(), false), + "Link embeds disabled": tryOrElse(() => !ShowEmbeds.getSetting(), false), "Equicord DevBuild": !IS_STANDALONE, "Has UserPlugins": Object.values(PluginMeta).some(m => m.userPlugin), "More than two weeks out of date": BUILD_TIMESTAMP < Date.now() - 12096e5, + [`Potentially Problematic Plugins: ${potentiallyProblematicPlugins.join(", ")}`]: potentiallyProblematicPlugins.length }; let content = `>>> ${Object.entries(info).map(([k, v]) => `**${k}**: ${v}`).join("\n")}`; From 89c6df4fcf3d709ba252fa27d31c6d60e03a7300 Mon Sep 17 00:00:00 2001 From: thororen1234 <78185467+thororen1234@users.noreply.github.com> Date: Tue, 19 Aug 2025 14:32:50 -0400 Subject: [PATCH 05/21] SupportHelper: Simplify Text --- src/plugins/_core/supportHelper.tsx | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/plugins/_core/supportHelper.tsx b/src/plugins/_core/supportHelper.tsx index aa9674cb2..c62b6f45b 100644 --- a/src/plugins/_core/supportHelper.tsx +++ b/src/plugins/_core/supportHelper.tsx @@ -123,9 +123,8 @@ async function generateDebugInfoMessage() { } const potentiallyProblematicPlugins = [ - "NoRPC", "NoProfileThemes", "NoMosaic", "Moyai", "AlwaysAnimate", "ClientTheme", - "Equissant", "Ingtoninator", "KeyboardSounds", "NeverPausePreviews", "NoDeleteSafety", - "NoRoleHeaders", "NoSystemBadge" + "NoRPC", "NoProfileThemes", "NoMosaic", "NoRoleHeaders", "NoSystemBadge", "NoDeleteSafety", + "Moyai", "AlwaysAnimate", "ClientTheme", "Equissant", "Ingtoninator", "KeyboardSounds", "NeverPausePreviews", ].filter(Vencord.Plugins.isPluginEnabled) ?? []; if (Vencord.Plugins.isPluginEnabled("CustomIdle") && Vencord.Settings.plugins.CustomIdle.idleTimeout === 0) { @@ -133,11 +132,11 @@ async function generateDebugInfoMessage() { } const commonIssues = { - "Activity Sharing disabled": tryOrElse(() => !ShowCurrentGame.getSetting(), false), - "Link embeds disabled": tryOrElse(() => !ShowEmbeds.getSetting(), false), + "Activity Sharing Disabled": tryOrElse(() => !ShowCurrentGame.getSetting(), false), + "Link Embeds Disabled": tryOrElse(() => !ShowEmbeds.getSetting(), false), "Equicord DevBuild": !IS_STANDALONE, "Has UserPlugins": Object.values(PluginMeta).some(m => m.userPlugin), - "More than two weeks out of date": BUILD_TIMESTAMP < Date.now() - 12096e5, + ">2 Weeks Outdated": BUILD_TIMESTAMP < Date.now() - 12096e5, [`Potentially Problematic Plugins: ${potentiallyProblematicPlugins.join(", ")}`]: potentiallyProblematicPlugins.length }; From 1f6dc116a0b51454319db550af52e9c123b5c650 Mon Sep 17 00:00:00 2001 From: thororen1234 <78185467+thororen1234@users.noreply.github.com> Date: Tue, 19 Aug 2025 14:48:33 -0400 Subject: [PATCH 06/21] SupportHelper: Sort --- src/plugins/_core/supportHelper.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/plugins/_core/supportHelper.tsx b/src/plugins/_core/supportHelper.tsx index c62b6f45b..235e141ef 100644 --- a/src/plugins/_core/supportHelper.tsx +++ b/src/plugins/_core/supportHelper.tsx @@ -122,10 +122,10 @@ async function generateDebugInfoMessage() { info["Last Crash Reason"] = (await tryOrElse(() => DiscordNative.processUtils.getLastCrash(), undefined))?.rendererCrashReason ?? "N/A"; } - const potentiallyProblematicPlugins = [ + const potentiallyProblematicPlugins = ([ "NoRPC", "NoProfileThemes", "NoMosaic", "NoRoleHeaders", "NoSystemBadge", "NoDeleteSafety", "Moyai", "AlwaysAnimate", "ClientTheme", "Equissant", "Ingtoninator", "KeyboardSounds", "NeverPausePreviews", - ].filter(Vencord.Plugins.isPluginEnabled) ?? []; + ].filter(Vencord.Plugins.isPluginEnabled) ?? []).sort(); if (Vencord.Plugins.isPluginEnabled("CustomIdle") && Vencord.Settings.plugins.CustomIdle.idleTimeout === 0) { potentiallyProblematicPlugins.push("CustomIdle"); From 7ed241f86134ca865eb4dc4780373e945622d8c6 Mon Sep 17 00:00:00 2001 From: Etorix <92535668+EtorixDev@users.noreply.github.com> Date: Tue, 19 Aug 2025 13:17:38 -0700 Subject: [PATCH 07/21] Downloadify (#349) * PR. * Update definitions.tsx * Typo + cleanup. * Revert breaking change. * Fix image extension flaw + embed context flaw. * Fix typo. * Label. * Video + label fixes. * Label improvement. * Content type improvements. --------- Co-authored-by: thororen <78185467+thororen1234@users.noreply.github.com> --- src/equicordplugins/downloadify/index.tsx | 115 ++ src/equicordplugins/downloadify/native.ts | 94 ++ src/equicordplugins/downloadify/settings.tsx | 109 ++ src/equicordplugins/downloadify/style.css | 77 ++ .../downloadify/utils/definitions.tsx | 371 ++++++ .../downloadify/utils/handlers.tsx | 1006 +++++++++++++++++ .../downloadify/utils/misc.tsx | 253 +++++ 7 files changed, 2025 insertions(+) create mode 100644 src/equicordplugins/downloadify/index.tsx create mode 100644 src/equicordplugins/downloadify/native.ts create mode 100644 src/equicordplugins/downloadify/settings.tsx create mode 100644 src/equicordplugins/downloadify/style.css create mode 100644 src/equicordplugins/downloadify/utils/definitions.tsx create mode 100644 src/equicordplugins/downloadify/utils/handlers.tsx create mode 100644 src/equicordplugins/downloadify/utils/misc.tsx diff --git a/src/equicordplugins/downloadify/index.tsx b/src/equicordplugins/downloadify/index.tsx new file mode 100644 index 000000000..6c4216f9b --- /dev/null +++ b/src/equicordplugins/downloadify/index.tsx @@ -0,0 +1,115 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2025 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import "./style.css"; + +import { EquicordDevs } from "@utils/constants"; +import definePlugin from "@utils/types"; + +import { settings } from "./settings"; +import { GDMContextMenu, GuildContextMenu, handleExpandedModalDownloadButtonClicked, handleHoverDownloadButtonClicked, MessageContextMenu, UserContextMenu, VoiceMessageDownloadButton } from "./utils/handlers"; + +export default definePlugin({ + name: "Downloadify", + description: "Download various assets directly in Discord without having to open a browser or dig through HTML.", + authors: [EquicordDevs.Etorix], + hidden: IS_WEB, + settings, + + VoiceMessageDownloadButton, + handleHoverDownloadButtonClicked, + handleExpandedModalDownloadButtonClicked, + + contextMenus: { + "message": MessageContextMenu, + "guild-context": GuildContextMenu, + "user-context": UserContextMenu, + "gdm-context": GDMContextMenu + }, + + patches: [ + { + // Pass the guild ID to the profile modal context. Used by the next patch. + find: "animate}):null", + replacement: { + match: /("right",)(avatarUrl:null)/, + replace: "$1guildId:arguments[0]?.channel?.guild_id,$2" + } + }, + { + // Pass the guild ID to the profile modal renderer. Allows guild specific profiles + // to load on the initial click instead of having to expand the profile first. + // Needed specifically for member banners. + find: '["children","userId","user"]', + replacement: { + match: /(\),{)(user:\i,currentUser:\i,children)/, + replace: "$1guildId:arguments[0].guildId,$2" + } + }, + { + // Adds a download button to voice messages before the volume slider. + find: "volumeSlider,muted", + replacement: { + match: /(}\),)(\(0,\i.\i\)\(\i.\i,{className:\i.volumeButton)/, + replace: "$1$self.VoiceMessageDownloadButton(arguments[0]),$2" + } + }, + { + // Hides the inline download button on text files in + // favor of the hover download button enabled below. + find: "formattedSize),children", + replacement: { + match: /(\(0,\i.\i\)\(\i.\i,{text:"".concat\(\i.intl.string)/, + replace: "false&&$1" + } + }, + { + // Passes on the file information to be used by the hover download buttons. + find: "downloadUrl,showDownload", + replacement: { + match: /(showImageAppPicker:\i&&\i)/, + replace: "item:arguments[0].item,$1" + } + }, + { + // Forces the hover download button to always be visible on supported media. + // Also overwrites the onClick function to use the custom download handling. + find: "downloadHoverButtonIcon,focusProps:{", + replacement: { + match: /((\i)=>{)(.{0,60}?)showDownload:(\i),(.{0,1250}?)href:\i,/, + replace: "$1const downloadifyHoverItem=$2;$3downloadifyShowDownload:$4=!0,$5onClick:()=>{$self.handleHoverDownloadButtonClicked(downloadifyHoverItem);}," + }, + }, + { + // Overwrites the default download button behavior for expanded image & video modals. + // This patch is lazy loaded. You must open an image or video modal for it to resolve. + find: "SAVE_MEDIA_PRESSED),", + group: true, + replacement: [ + { + // Make use of the download function. + match: /(let{item:\i}=(\i).{0,450}?)(await \i.\i.saveImage\(\i,\i.contentType,\i.\i\)),/, + replace: "$1await $self.handleExpandedModalDownloadButtonClicked($2,async()=>{$3});", + }, + { + // Disable default success toast. + match: /(\(0,.{0,85}?ToastType.SUCCESS\)\))/, + replace: "", + }, + { + // Disable default failure toast. + match: /(\(0,.{0,85}?ToastType.FAILURE\)\))/, + replace: "", + }, + { + // Prevent videos from opening in browser. + match: /(let{item:\i}=\i.{0,350}?)(SAVE_MEDIA_PRESSED\)).{0,100}?(\i\(!0\);)/, + replace: "$1$2,true){$3" + } + ], + } + ] +}); diff --git a/src/equicordplugins/downloadify/native.ts b/src/equicordplugins/downloadify/native.ts new file mode 100644 index 000000000..1cc48e27a --- /dev/null +++ b/src/equicordplugins/downloadify/native.ts @@ -0,0 +1,94 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2025 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { dialog } from "electron"; +import fs from "fs"; +import { Readable } from "stream"; +import { finished } from "stream/promises"; + +/** + * Check if a file exists at a given path. + */ +export async function fileExists(_: Electron.IpcMainInvokeEvent, filePath: string): Promise { + try { + await fs.promises.access(filePath, fs.constants.F_OK); + return true; + } catch { + return false; + } +} + +/** + * Ask the user to select a directory to save files. + */ +export async function getDirectory(_: Electron.IpcMainInvokeEvent): Promise { + const result = await dialog.showOpenDialog({ + properties: ["openDirectory"] + }); + + return result.filePaths[0] ?? null; +} + +/** + * Ask the user to select a file name and location to save a file. + */ +export async function getFilePath(_: Electron.IpcMainInvokeEvent, fileName: string, fileTypes: string[] | null): Promise { + const filters = fileTypes?.map(ext => ({ + name: ext.toUpperCase(), + extensions: [ext] + })) ?? []; + + if (!fileTypes?.length) { + filters.push({ name: "All Files", extensions: ["*"] }); + } + + const result = await dialog.showSaveDialog({ + title: "Save File", + defaultPath: fileName, + buttonLabel: "Save", + properties: ["createDirectory"], + filters + }); + + return result.filePath ?? null; +} + +/** + * Get the content type of a file at a URL. + * + * @returns The content type of the file if it exists, undefined if no content type is available, or null if the file doesn't exist. + */ +export async function queryURL(_: Electron.IpcMainInvokeEvent, url: string): Promise { + const response = await fetch(url, { method: "HEAD" }); + + if (!response.ok) { + return null; + } + + return response.headers.get("content-type") ?? undefined; +} + +/** + * Download a file from a URL to a local file path. + */ +export async function downloadURL(_: Electron.IpcMainInvokeEvent, url: string, filePath: string): Promise { + const response = await fetch(url); + + if (!response.ok) { + return false; + } + + if (!response.body) { + return false; + } + + const readableStream = Readable.fromWeb(response.body as any); + const fileWriteStream = fs.createWriteStream(filePath); + readableStream.pipe(fileWriteStream); + await finished(fileWriteStream); + + return true; +} diff --git a/src/equicordplugins/downloadify/settings.tsx b/src/equicordplugins/downloadify/settings.tsx new file mode 100644 index 000000000..da21af8d8 --- /dev/null +++ b/src/equicordplugins/downloadify/settings.tsx @@ -0,0 +1,109 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2025 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import "./style.css"; + +import { definePluginSettings } from "@api/Settings"; +import ErrorBoundary from "@components/ErrorBoundary"; +import { OptionType } from "@utils/types"; +import { Button, Forms, showToast, Toasts, useState } from "@webpack/common"; +import { JSX } from "react"; + +import { d, DownloadifyLogger, DownloadifyNative } from "./utils/definitions"; +import { getFormattedNow } from "./utils/misc"; + +function DefaultDirectorySetting(): JSX.Element { + const { defaultDirectory } = settings.use(["defaultDirectory"]); + const [isDialogueOpen, setDialogueOpen] = useState(false); + + const handlePickDirectory = async () => { + try { + setDialogueOpen(true); + const directory = await DownloadifyNative.getDirectory(); + directory && (settings.store.defaultDirectory = directory); + } catch (error) { + DownloadifyLogger.error(`[${getFormattedNow()}] [FAILED TO SET DOWNLOAD DIRECTORY]`, error); + showToast("Failed to set download directory.", Toasts.Type.FAILURE, { duration: 3000 }); + } finally { + setDialogueOpen(false); + } + }; + + const handleClearDirectory = () => { + settings.store.defaultDirectory = ""; + }; + + return ( + + + + Default Directory + + + Default download location. If set, the file will always be downloaded in its original format even if alternatives are available. Leave empty to pick a folder and file type each time. + +
+ + {defaultDirectory || "No Directory Set"} + +
+ + +
+
+
+
+ ); +} + +export const settings = definePluginSettings({ + displayStatus: { + description: "Display a status notification when downloads start, finish, or error.", + type: OptionType.BOOLEAN, + default: true, + }, + statusDuration: { + type: OptionType.SLIDER, + description: "The number of seconds to display status notifications.", + markers: [1, 3, 5], + default: 2.5, + stickToMarkers: false, + }, + voiceMessages: { + description: "Add a download button to voice messages.", + type: OptionType.BOOLEAN, + default: true, + }, + allowUnicode: { + description: "Allow non-ASCII characters in file names.", + type: OptionType.BOOLEAN, + default: true, + }, + overwriteFiles: { + description: "If a default directory is set and a download file name matches an existing file, overwrite the file. If disabled, a number will be appended to the file name.", + type: OptionType.BOOLEAN, + default: false, + }, + defaultDirectory: { + component: DefaultDirectorySetting, + type: OptionType.COMPONENT, + default: "", + }, +}); diff --git a/src/equicordplugins/downloadify/style.css b/src/equicordplugins/downloadify/style.css new file mode 100644 index 000000000..2b7f3df5e --- /dev/null +++ b/src/equicordplugins/downloadify/style.css @@ -0,0 +1,77 @@ +.downloadify-voice-message-container { + flex-shrink: 0; + align-items: center; + display: flex; + flex-direction: column; + justify-content: flex-end; + position: relative; + margin: 0 -2px; +} + +.downloadify-voice-message-button { + cursor: pointer; + line-height: 0; + border: none; + width: auto; + background: transparent; + color: var(--interactive-normal); + margin: 0; + padding: 0; + align-items: center; + border-radius: 8px; + box-sizing: border-box; + display: flex; + font-size: 14px; + font-weight: var(--font-weight-medium); + justify-content: center; + position: relative; + transition-duration: .2s; + user-select: none; +} + +.downloadify-voice-message-button:hover { + color: var(--interactive-hover); +} + +.downloadify-directory-container { + margin-top: 10px; +} + +.downloadify-directory-buttons { + margin-top: 10px; + display: flex; + gap: 10px; +} + +.downloadify-directory-button { + border-radius: 4px; + min-height: 34px; + color: white; + height: 34px; + flex: 1; +} + +.downloadify-directory-button:not(.downloadify-disabled):hover { + filter: brightness(0.75); + transition: filter 0.15s ease-in-out; +} + +.downloadify-clear-button { + background-color: rgb(180 71 78); +} + +.downloadify-browse-button { + background-color: rgb(87 100 240); +} + +.downloadify-directory-display { + background-color: var(--background-base-lower); + overflow-wrap: break-word; + font-family: monospace; + word-wrap: break-word; + white-space: pre-wrap; + border-radius: 4px; + user-select: text; + font-size: 14px; + padding: 8px; +} \ No newline at end of file diff --git a/src/equicordplugins/downloadify/utils/definitions.tsx b/src/equicordplugins/downloadify/utils/definitions.tsx new file mode 100644 index 000000000..8f65a964a --- /dev/null +++ b/src/equicordplugins/downloadify/utils/definitions.tsx @@ -0,0 +1,371 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2025 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { classNameFactory } from "@api/Styles"; +import { Logger } from "@utils/Logger"; +import { PluginNative } from "@utils/types"; +import { Channel, Guild, GuildMember, Message, User } from "@vencord/discord-types"; +import { findByPropsLazy } from "@webpack"; +import { JSX } from "react"; + +export const d = classNameFactory("downloadify-"); +export const CDN_BASE = "https://cdn.discordapp.com"; +export const MEDIA_PROXY_BASE = "https://media.discordapp.net"; +export const PRIMARY_DOMAIN_BASE = "https://discord.com"; +export const IMAGE_EXT_1_DOMAIN_BASE = "https://images-ext-1.discordapp.net"; +export const IMAGE_EXT_2_DOMAIN_BASE = "https://images-ext-2.discordapp.net"; +export const TENOR_BASE = "https://c.tenor.com"; +export const TENOR_GIF_ID = "Ad"; +export const TENOR_MP4_ID = "Po"; +export const ASSET_TYPE_EXTRACTOR = new RegExp(/^https:\/\/(?:cdn\.discordapp\.com|media.discordapp.net)\/([a-z-]+(?:\/[a-z-]+){0,2})(?:(?:\/\d+\/[a-z-]+\/\d+\/([a-z-]+))|(?:\/([a-z-]+(?:\/[a-z-_]+)?)))?/i); +export const DownloadifyLogger = new Logger("Downloadify"); +export const DownloadifyNative = VencordNative.pluginHelpers.Downloadify as PluginNative; +export const defaultAssets = findByPropsLazy("DEFAULT_GROUP_DM_AVATARS") as DefaultAssets; + +interface DefaultAssets { + BOT_AVATARS: Record; + DEFAULT_AVATARS: string[]; + DEFAULT_CHANNEL_ICON: string; + DEFAULT_GROUP_DM_AVATARS: string[]; + DEFAULT_PROVISIONAL_AVATARS: string[]; +} + +export interface AssetInfo { + source: AssetSource; + static: string[]; + animated: string[]; + type?: string; + forceAnimated?: boolean; +} + +export enum AssetSource { + CDN = "cdn", + EXTERNAL_IMAGE_PROXY = "external_image_proxy", + ATTACHMENT_MEDIA_PROXY = "attachment_media_proxy", + ASSET_MEDIA_PROXY = "asset_media_proxy", + PRIMARY_DOMAIN = "primary_domain", + EXTERNAL = "external", + TENOR = "tenor" +} + +export const unknownExternal = { + source: AssetSource.EXTERNAL, + static: [], + animated: [] +}; + +export const unknownPrimaryDomain = { + source: AssetSource.PRIMARY_DOMAIN, + static: [], + animated: [] +}; + +export const unknownCDN = { + source: AssetSource.CDN, + static: [], + animated: [] +}; + +export const unknownAssetMediaProxy = { + source: AssetSource.ASSET_MEDIA_PROXY, + static: [], + animated: [] +}; + +export const unknownAttachmentMediaProxy = { + source: AssetSource.ATTACHMENT_MEDIA_PROXY, + static: [], + animated: [] +}; + +export const unknownExternalImageProxy = { + source: AssetSource.EXTERNAL_IMAGE_PROXY, + static: [], + animated: [] +}; + +export const defaultResourceAvailability = { + source: AssetSource.PRIMARY_DOMAIN, + static: ["png"], + animated: [] +}; + +export const APNGAvailability = { + source: AssetSource.ASSET_MEDIA_PROXY, + static: ["png", "webp", "jpg"], + animated: ["apng", "png", "webp", "jpg"] +}; + +export const hashableAvailability = { + source: AssetSource.ASSET_MEDIA_PROXY, + static: ["png", "webp", "jpg"], + animated: ["gif", "awebp", "png", "webp", "jpg"] +}; + +// Defines known asset type availability. Unknown types which may be +// encountered are AssetSource.EXTERNAL which are present in some embeds, +// as well as OTHER attachment types. +export const assetAvailability = { + "user-avatar": hashableAvailability, + "user-banner": hashableAvailability, + "avatar-decoration": APNGAvailability, + "clan-badge": { + source: AssetSource.ASSET_MEDIA_PROXY, + static: ["png"], + animated: [] + }, + "nameplate": { + source: AssetSource.CDN, + static: ["png"], + animated: ["webm", "apng", "png"], + type: "nameplate" + }, + "default-user-avatar": defaultResourceAvailability, + "default-group-icon": defaultResourceAvailability, + "role-icon-custom": { + source: AssetSource.ASSET_MEDIA_PROXY, + static: ["png", "webp", "jpg"], + animated: [], + }, + "role-icon-unicode-emoji": { + source: AssetSource.PRIMARY_DOMAIN, + static: ["svg"], + animated: [], + }, + "guild-icon": hashableAvailability, + "guild-banner": hashableAvailability, + "guild-invite-splash": hashableAvailability, + "guild-discovery-splash": hashableAvailability, + "custom-emoji": hashableAvailability, + "unicode-emoji": { + source: AssetSource.PRIMARY_DOMAIN, + static: ["svg"], + animated: [] + }, + "sticker": { + "APNG": APNGAvailability, + "PNG": { + source: AssetSource.ASSET_MEDIA_PROXY, + static: ["png", "webp", "jpg"], + animated: [] + }, + "GIF": { + source: AssetSource.ASSET_MEDIA_PROXY, + static: [], + animated: ["gif", "awebp", "png", "webp", "jpg"], + forceAnimated: true + }, + "LOTTIE": { + source: AssetSource.CDN, + static: ["json"], + animated: ["json"] + }, + }, + "tenor": { + source: AssetSource.TENOR, + static: [], + animated: ["gif", "mp4"], + forceAnimated: true + }, + "external": { + "image/png": { + source: AssetSource.EXTERNAL_IMAGE_PROXY, + static: ["png", "webp", "jpg"], + animated: [] + }, + "image/webp": { + source: AssetSource.EXTERNAL_IMAGE_PROXY, + static: ["png", "webp", "jpg"], + animated: [] + }, + "image/jpeg": { + source: AssetSource.EXTERNAL_IMAGE_PROXY, + static: ["png", "webp", "jpg"], + animated: [] + }, + "video/mp4": { + source: AssetSource.EXTERNAL, + static: [], + animated: ["mp4"], + forceAnimated: true + }, + }, + "attachment": { + // Image attachments over a certain size and/or resolution are treated as + // non-image files and are therefore not available in alternative formats. + // This is designated by an attachment type of OTHER instead of IMAGE. + "image/png": { + source: AssetSource.ATTACHMENT_MEDIA_PROXY, + static: ["png", "webp", "jpg"], + // APNG attachments are technically supported, but appear to get + // broken by Discord squashing them into static PNGs. APNG is kept + // here in case it is fixed some day. + animated: ["apng", "png", "webp", "jpg"] + }, + "image/webp": { + source: AssetSource.ATTACHMENT_MEDIA_PROXY, + static: ["webp", "png", "jpg"], + animated: ["awebp", "webp", "png", "jpg"] + }, + "image/avif": { + source: AssetSource.ATTACHMENT_MEDIA_PROXY, + static: ["avif", "png", "webp"], + animated: ["avif", "awebp", "png", "webp"] + }, + "image/jpeg": { + source: AssetSource.ATTACHMENT_MEDIA_PROXY, + static: ["jpg", "png", "webp"], + animated: [] + }, + "image/gif": { + source: AssetSource.ATTACHMENT_MEDIA_PROXY, + static: [], + animated: ["gif", "awebp", "png", "webp", "jpg"], + forceAnimated: true + }, + } +}; + +export function DownloadIcon({ width, height }: { width: number; height: number; }): JSX.Element { + return ( + + + + ); +} + +export interface ParsedFile { + path: string; + baseName: string; + extension: string | null; + twitterExtra: string | null; +} + +export interface ParsedURL { + url: string; + host: string; + path: string; + baseName: string; + extension: string | null; + params: { expiry: Record, other: Record; }; + twitterExtra: string | null; +} + +export interface InsertGroup { + id: { group?: string, child?: string; }; + type: "WITH_GROUP" | "BEFORE_GROUP" | "AFTER_GROUP"; + position?: "START" | "END"; +} + +export interface VoiceMessageDownloadButtonProps { + message: Message; + item: { + type: "AUDIO"; + originalItem: { + filename: string; + proxy_url: string; + }; + }; +} + +export interface HoverDownloadProps { + item: { + type: "AUDIO" | "IMAGE" | "VIDEO" | "OTHER"; + downloadUrl: string; + contentType: string; + srcIsAnimated: boolean; + originalItem: { + title?: string; + filename: string; + proxy_url: string; + url: string; + }; + }; +} + +export interface ExpandedModalDownloadProps { + item: { + type: "AUDIO" | "IMAGE" | "VIDEO" | "OTHER"; + url: string; + original: string; + proxyUrl?: string; + contentType?: string; + srcIsAnimated: boolean; + sourceMetadata?: { + identifier: { + type: "attachment" | "embed"; + title?: string; + }, + }; + }; +} + +export interface MessageContextMenuProps { + message: Message; + channel: Channel; + itemSrc: string; + itemSafeSrc?: string; + favoriteableType: "sticker" | "emoji" | null; + favoriteableName: string | null; + favoriteableId: string | null; + contextMenuAPIArguments: any[]; + mediaItem?: { + contentType: string; + proxyUrl: string; + url: string; + }; +} + +export interface GDMContextMenuProps { + channel: Channel & { + icon: string | null; + }; +} + +export interface GuildContextMenuProps { + guild: Guild; +} + +export interface UserContextMenuProps { + user: User; + guildId?: string; +} + +export interface CustomizedUser extends User { + avatarDecorationData: null | { + asset: string; + }; + collectibles: null | Record<"nameplate", { + asset: string; + label: string; + palette: string; + }>; +} + +export interface CustomizedMember extends Omit { + avatarDecoration: null | { + asset: string; + }; +} + +export const AttachmentFlags = { + IS_CLIP: 1 << 0, + IS_THUMBNAIL: 1 << 1, + IS_REMIX: 1 << 2, + IS_SPOILER: 1 << 3, + CONTAINS_EXPLICIT_MEDIA: 1 << 4, + IS_ANIMATED: 1 << 5, + CONTAINS_GORE_CONTENT: 1 << 6 +}; diff --git a/src/equicordplugins/downloadify/utils/handlers.tsx b/src/equicordplugins/downloadify/utils/handlers.tsx new file mode 100644 index 000000000..42dd32fc7 --- /dev/null +++ b/src/equicordplugins/downloadify/utils/handlers.tsx @@ -0,0 +1,1006 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2025 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { ImageIcon } from "@components/Icons"; +import { StickerFormatType } from "@vencord/discord-types/enums"; +import { GuildMemberStore, GuildRoleStore, GuildStore, Menu, showToast, Toasts, UserProfileStore, UserStore } from "@webpack/common"; +import { JSX } from "react"; + +import { settings } from "../settings"; +import { ASSET_TYPE_EXTRACTOR, assetAvailability, AssetInfo, AssetSource, AttachmentFlags, CDN_BASE, CustomizedMember, CustomizedUser, d, defaultAssets, DownloadIcon, DownloadifyLogger, DownloadifyNative, ExpandedModalDownloadProps, GDMContextMenuProps, GuildContextMenuProps, HoverDownloadProps, IMAGE_EXT_1_DOMAIN_BASE, IMAGE_EXT_2_DOMAIN_BASE, MEDIA_PROXY_BASE, MessageContextMenuProps, ParsedURL, PRIMARY_DOMAIN_BASE, TENOR_BASE, TENOR_GIF_ID, TENOR_MP4_ID, unknownAttachmentMediaProxy, unknownCDN, unknownExternal, unknownExternalImageProxy, unknownPrimaryDomain, UserContextMenuProps, VoiceMessageDownloadButtonProps } from "./definitions"; +import { getFormattedNow, joinOrCreateContextMenuGroup, parseFile, parseURL, sanitizeFilename } from "./misc"; + +export function MessageContextMenu(children: Array, props: MessageContextMenuProps): void { + if (!children?.length || !props?.message?.id) { + return; + } + + const { allowUnicode } = settings.store; + let { favoriteableId, favoriteableName, favoriteableType } = props; + const { itemSrc, itemSafeSrc, message, channel, mediaItem, contextMenuAPIArguments } = props; + const target = contextMenuAPIArguments?.[0]?.target; + let targetURL = itemSafeSrc ?? itemSrc; + + const emojiTarget = target.dataset?.type === "emoji" + ? target + : target.children?.[1]?.firstChild?.dataset?.type === "emoji" + ? target.children[1].firstChild + : target.parentElement?.children?.[1]?.firstChild?.dataset?.type === "emoji" + ? target.parentElement.children[1].firstChild + : null; + + if (!!emojiTarget?.dataset?.id) { + favoriteableType = "emoji"; + favoriteableId = emojiTarget.dataset.id; + targetURL = emojiTarget.src; + } else if (!!emojiTarget?.dataset?.name) { + favoriteableType = "emoji"; + favoriteableName = emojiTarget.alt ?? emojiTarget.dataset.name; + favoriteableId = null; + targetURL = emojiTarget.src; + } + + const roleIconTarget = target.classList && (Array.from(target.classList) as string[]).some(cls => cls.includes("roleIcon")) + ? target + : target.firstChild?.classList && (Array.from(target.firstChild.classList) as string[]).some(cls => cls.includes("roleIcon")) + ? target.firstChild + : null; + + const clanBadgeTarget = target.classList && (Array.from(target.classList) as string[]).some(cls => cls.includes("badge")) + ? target + : target.parentElement?.firstChild?.classList && (Array.from(target.parentElement.firstChild.classList) as string[]).some(cls => cls.includes("badge")) + ? target.parentElement.firstChild + : target.firstChild?.firstChild?.classList && (Array.from(target.firstChild.firstChild.classList) as string[]).some(cls => cls.includes("badge")) + ? target.firstChild.firstChild + : null; + + if (target.tagName === "VIDEO" && !!target.src) { + targetURL = contextMenuAPIArguments[0].target.src; + } else if (target.parentElement?.parentElement?.firstChild?.tagName === "VIDEO" && !!target.parentElement?.parentElement?.firstChild?.src) { + targetURL = contextMenuAPIArguments[0].target.parentElement.parentElement.firstChild.src; + } else if (target.parentElement?.parentElement?.parentElement?.firstChild?.tagName === "VIDEO" && !!target.parentElement?.parentElement?.parentElement?.firstChild?.src) { + targetURL = contextMenuAPIArguments[0].target.parentElement.parentElement.parentElement.firstChild.src; + } else if (target.parentElement?.parentElement?.parentElement?.parentElement?.parentElement?.firstChild?.tagName === "VIDEO" && !!target.parentElement?.parentElement?.parentElement?.parentElement?.parentElement?.firstChild?.src) { + targetURL = contextMenuAPIArguments[0].target.parentElement.parentElement.parentElement.parentElement.parentElement.firstChild.src; + } + + const isRoleIcon = !!roleIconTarget; + const isCustomEmoji = favoriteableType === "emoji" && !!favoriteableId; + const isUnicodeEmoji = favoriteableType === "emoji" && !favoriteableId; + const isSticker = favoriteableType === "sticker"; + const isAttachment = !!mediaItem; + const emojiRegex = favoriteableId ? new RegExp(`<(a)?:(\\w+):(${favoriteableId})>`) : null; + let emojiName = isUnicodeEmoji ? (favoriteableName?.replaceAll(":", "") || null) : (favoriteableName ?? (emojiRegex?.exec(message.content || "")?.[2] || null)); + let emojiAnimated = isCustomEmoji ? emojiRegex?.exec(message.content || "")?.[1] === "a" : false; + const emojiURL = (isCustomEmoji || isUnicodeEmoji) ? parseURL(targetURL.replace(CDN_BASE, MEDIA_PROXY_BASE)) : null; + const sticker = isSticker ? message.stickerItems?.find(sticker => sticker.id === favoriteableId) : null; + const downloadifyItems: any[] = []; + + function findNestedComponent(components: any[], checkFor: string): any { + for (const innerComponent of components) { + if (innerComponent.emoji?.id === checkFor || innerComponent.emoji?.name === checkFor) { + return innerComponent; + } + + if (innerComponent.components?.length) { + const found = findNestedComponent(innerComponent.components, checkFor); + if (found) { + return found; + } + } + } + return null; + } + + if (isRoleIcon) { + const parsedURL = parseURL(roleIconTarget.src.replace(CDN_BASE, MEDIA_PROXY_BASE)); + const isUnicodeRoleIcon = itemSrc.startsWith(PRIMARY_DOMAIN_BASE); + const isCustomRoleIcon = !isUnicodeRoleIcon; + const roleID = GuildMemberStore.getMember(channel.guild_id, message.author.id)?.iconRoleId; + const role = roleID ? GuildRoleStore.getRole(channel.guild_id, roleID) : null; + const sanitizedRoleName = role ? sanitizeFilename(role.name, allowUnicode) : null; + + downloadifyItems.push( + ImageIcon({ width: 20, height: 20 })} + action={async () => { + await handleDownload( + parsedURL, + sanitizedRoleName ? `${sanitizedRoleName}-icon` : null, + isCustomRoleIcon ? assetAvailability["role-icon-custom"] : assetAvailability["role-icon-unicode-emoji"], + false + ); + }} + /> + ); + } else if (clanBadgeTarget && !!(message.author as any).primaryGuild) { + const { primaryGuild } = (message.author as any); + const sanitizedGuildTag = sanitizeFilename(primaryGuild.tag, allowUnicode); + + downloadifyItems.push( + ImageIcon({ width: 20, height: 20 })} + action={async () => { + await handleDownload( + parseURL(clanBadgeTarget.src.replace(CDN_BASE, MEDIA_PROXY_BASE)), + sanitizedGuildTag ? `${sanitizedGuildTag}-clan-badge` : null, + assetAvailability["clan-badge"], + false + ); + }} + /> + ); + } else if (isCustomEmoji) { + const reaction = message.reactions?.find(reaction => reaction.emoji.id === favoriteableId)?.emoji; + const component = message.components?.length ? findNestedComponent(message.components, favoriteableId as string) : null; + emojiAnimated = component?.emoji?.animated || reaction?.animated || emojiAnimated; + emojiName = component?.emoji?.name || reaction?.name || emojiName; + + downloadifyItems.push( + ImageIcon({ width: 20, height: 20 })} + action={async () => { + await handleDownload( + emojiURL as ParsedURL, + emojiName, + assetAvailability["custom-emoji"], + emojiAnimated + ); + }} + /> + ); + } else if (isUnicodeEmoji) { + const reaction = message.reactions?.find(reaction => reaction.emoji.name === favoriteableName)?.emoji; + const component = message.components?.length ? findNestedComponent(message.components, favoriteableName as string) : null; + emojiName = component?.emoji?.name || reaction?.name || emojiName; + emojiAnimated = false; + + downloadifyItems.push( + ImageIcon({ width: 20, height: 20 })} + action={async () => { + await handleDownload( + emojiURL as ParsedURL, + emojiName, + assetAvailability["unicode-emoji"], + emojiAnimated + ); + }} + /> + ); + } else if (isSticker) { + let stickerURL; + let stickerSource; + let stickerAnimated; + + if (sticker?.format_type === StickerFormatType.PNG) { + stickerURL = parseURL(`${MEDIA_PROXY_BASE}/stickers/${sticker.id}.png`); + stickerSource = assetAvailability.sticker.PNG; + stickerAnimated = false; + } else if (sticker?.format_type === StickerFormatType.GIF) { + stickerURL = parseURL(`${MEDIA_PROXY_BASE}/stickers/${sticker.id}.gif`); + stickerSource = assetAvailability.sticker.GIF; + stickerAnimated = true; + } else if (sticker?.format_type === StickerFormatType.APNG) { + stickerURL = parseURL(`${MEDIA_PROXY_BASE}/stickers/${sticker.id}.png`); + stickerSource = assetAvailability.sticker.APNG; + stickerAnimated = true; + } else if (sticker?.format_type === StickerFormatType.LOTTIE) { + stickerURL = parseURL(`${CDN_BASE}/stickers/${sticker.id}.json`); + stickerSource = assetAvailability.sticker.LOTTIE; + stickerAnimated = true; + } else { + return; + } + + downloadifyItems.push( + ImageIcon({ width: 20, height: 20 })} + action={async () => { + await handleDownload( + stickerURL, + sticker.name, + stickerSource, + stickerAnimated + ); + }} + /> + ); + } else if (isAttachment) { + const assetInfo = assetAvailability.attachment[mediaItem.contentType] ?? unknownCDN; + const attachment = message.attachments?.find(attachment => attachment.proxy_url === mediaItem.proxyUrl || attachment.url === mediaItem.url); + + if (!attachment) { + return; + } + + downloadifyItems.push( + ImageIcon({ width: 20, height: 20 })} + action={async () => { + await handleDownload( + parseURL(assetInfo.source === AssetSource.CDN ? mediaItem.url : mediaItem.proxyUrl), + (attachment as any)?.title || null, + assetInfo, + !!((attachment as any).flags & AttachmentFlags.IS_ANIMATED) + ); + }} + /> + ); + } else if (targetURL) { + let isTenor = false; + + const embedImage = message.embeds?.find(embed => { + const images = (embed as any).images ? (embed as any).images : embed.image ? [embed.image] : []; + return images.some(image => targetURL.startsWith(image.url) || targetURL.startsWith(image.proxyURL)); + }); + + const embedVideo = embedImage ? undefined : message.embeds?.find(embed => { + isTenor = embed.provider?.name === "Tenor"; + const videos = (embed as any).videos ? (embed as any).videos : embed.video ? [embed.video] : []; + return videos.some(video => targetURL.startsWith(video.url) || targetURL.startsWith(video.proxyURL)); + }); + + const embedThumbnail = message.embeds?.find(embed => { + return (embed.thumbnail?.url && targetURL.startsWith(embed.thumbnail.url)) || (embed.thumbnail?.proxyURL && targetURL.startsWith(embed.thumbnail.proxyURL)); + }); + + const embedAuthor = message.embeds?.find(embed => { + return (embed.author?.iconURL && targetURL.startsWith(embed.author.iconURL)) || (embed.author?.iconProxyURL && targetURL.startsWith(embed.author.iconProxyURL)); + }); + + const embedFooter = (message.embeds?.find(embed => { + return ((embed as any).footer?.iconURL && targetURL.startsWith((embed as any).footer.iconURL)) || ((embed as any).footer?.iconProxyURL && targetURL.startsWith((embed as any).footer.iconProxyURL)); + }) as any); + + const targetEmbed = (embedImage || embedVideo || embedThumbnail || embedAuthor || embedFooter); + const targetEmbedItem = (embedImage?.image || embedVideo?.video || embedThumbnail?.thumbnail || embedAuthor?.author || embedFooter?.footer); + const labelEmbedMedia = embedFooter?.footer + ? "Footer Icon" + : embedAuthor?.author + ? "Author Icon" + : embedThumbnail?.thumbnail + ? "Thumbnail Image" + : targetEmbed?.type === "rich" || !!targetEmbed?.provider + ? "Embed Media" + : "Media"; + + downloadifyItems.push( + ImageIcon({ width: 20, height: 20 })} + action={async () => { + let srcIsAnimated = !!(targetEmbedItem as any)?.srcIsAnimated; + let aliasBasename: string | null = null; + let assetInfo: any; + + const contentType = ((targetEmbedItem as any)?.contentType || await DownloadifyNative.queryURL(itemSrc)); + const isMediaProxy = targetURL?.startsWith(MEDIA_PROXY_BASE); + const isPrimaryDomain = targetURL?.startsWith(PRIMARY_DOMAIN_BASE); + const isCDN = targetURL?.startsWith(CDN_BASE); + const isImageExt1 = targetURL?.startsWith(IMAGE_EXT_1_DOMAIN_BASE); + const isImageExt2 = targetURL?.startsWith(IMAGE_EXT_2_DOMAIN_BASE); + const isImageExt = isImageExt1 || isImageExt2; + + if (isTenor && embedVideo) { + targetURL = embedVideo.url ?? targetURL; + } + + const parsedURL = parseURL(targetURL); + + if (isTenor) { + assetInfo = assetAvailability.tenor; + srcIsAnimated = true; + } else if (isImageExt) { + assetInfo = assetAvailability.attachment[contentType] + ?? assetAvailability.external[contentType] + ?? unknownExternalImageProxy; + } else if (isMediaProxy || isCDN) { + const guestimate = guesstimateAsset(parsedURL, contentType); + assetInfo = guestimate.assetInfo ?? (isCDN ? unknownCDN : (isImageExt ? unknownExternalImageProxy : unknownExternal)); + aliasBasename = guestimate.aliasBasename ?? aliasBasename; + srcIsAnimated = guestimate.srcIsAnimated ?? srcIsAnimated; + } else { + assetInfo = isPrimaryDomain ? unknownPrimaryDomain : unknownExternal; + } + + await handleDownload( + parsedURL, + aliasBasename, + assetInfo, + srcIsAnimated + ); + }} + /> + ); + } + + if (!downloadifyItems.length) { + return; + } + + joinOrCreateContextMenuGroup( + children, + downloadifyItems, + "message-content-group", + "downloadify-submenu", + "Download", + [{ + id: { child: "devmode-copy-id" }, + type: "WITH_GROUP", + position: "START" + }] + ); +} + +export function GDMContextMenu(children: Array, props: GDMContextMenuProps): void { + if (!children?.length || !props?.channel?.id) { + return; + } + + const { channel } = props; + const channelIconHash = channel.icon || null; + const channelIconURL = channelIconHash ? parseURL(`${MEDIA_PROXY_BASE}/channel-icons/${channel.id}/${channelIconHash}.png`) : null; + const channelTimestamp = Number(BigInt(channel.id) >> 22n); + const defaultChannelIconURL = channelTimestamp ? parseURL(`${PRIMARY_DOMAIN_BASE}${defaultAssets.DEFAULT_GROUP_DM_AVATARS[channelTimestamp % 8]}`) : null; + + const downloadifyItems = [ + channelIconURL ? ( + ImageIcon({ width: 20, height: 20 })} + action={async () => { + await handleDownload( + channelIconURL, + channel.name ? `${channel.name}-group-icon` : null, + assetAvailability["guild-icon"], + false + ); + }} + /> + ) : null, + defaultChannelIconURL ? ( + ImageIcon({ width: 20, height: 20 })} + action={async () => { + await handleDownload( + defaultChannelIconURL, + channel.name ? `${channel.name}-default-group-icon` : null, + assetAvailability["default-group-icon"], + false + ); + }} + /> + ) : null + ].filter(Boolean); + + joinOrCreateContextMenuGroup( + children, + downloadifyItems, + "channel-content-group", + "downloadify-submenu", + "Download", + [{ + id: { child: "devmode-copy-id" }, + type: "WITH_GROUP", + position: "START" + }] + ); +} + +export function GuildContextMenu(children: Array, props: GuildContextMenuProps): void { + if (!children?.length || !props?.guild?.id) { + return; + } + + const { guild } = props; + const guildIconHash = guild.icon || null; + const guildBannerHash = guild.banner || null; + const guildInviteHash = guild.splash !== guildBannerHash ? guild.splash || null : null; + const guildDiscoveryHash = guild.discoverySplash !== guildInviteHash && guild.discoverySplash !== guildBannerHash ? guild.discoverySplash || null : null; + const guildIconHashAnimated = guildIconHash?.startsWith("a_"); + const guildBannerHashAnimated = guildBannerHash?.startsWith("a_"); + const guildIconURL = guildIconHash ? parseURL(`${MEDIA_PROXY_BASE}/icons/${guild.id}/${guildIconHash}.${guildIconHashAnimated ? "gif" : "png"}`) : null; + const guildBannerURL = guildBannerHash ? parseURL(`${MEDIA_PROXY_BASE}/banners/${guild.id}/${guildBannerHash}.${guildBannerHashAnimated ? "gif" : "png"}`) : null; + const guildInviteURL = guildInviteHash ? parseURL(`${MEDIA_PROXY_BASE}/splashes/${guild.id}/${guildInviteHash}.png`) : null; + const guildDiscoveryURL = guildDiscoveryHash ? parseURL(`${MEDIA_PROXY_BASE}/discovery-splashes/${guild.id}/${guildDiscoveryHash}.png`) : null; + + if (!guildIconURL && !guildBannerURL && !guildInviteURL && !guildDiscoveryURL) { + return; + } + + const downloadifyItems = [guildIconURL ? ( + ImageIcon({ width: 20, height: 20 })} + action={async () => await handleDownload( + guildIconURL, + guild.name ? `${guild.name}-icon` : null, + assetAvailability["guild-icon"], + !!guildIconHash?.startsWith("a_") + )} + /> + ) : null, + guildBannerURL ? ( + ImageIcon({ width: 20, height: 20 })} + action={async () => await handleDownload( + guildBannerURL, + guild.name ? `${guild.name}-banner` : null, + assetAvailability["guild-banner"], + !!guildBannerHash?.startsWith("a_") + )} + /> + ) : null, + guildInviteURL ? ( + ImageIcon({ width: 20, height: 20 })} + action={async () => await handleDownload( + guildInviteURL, + guild.name ? `${guild.name}-invite-splash` : null, + assetAvailability["guild-invite-splash"], + !!guildInviteHash?.startsWith("a_") + )} + /> + ) : null, + guildDiscoveryURL ? ( + ImageIcon({ width: 20, height: 20 })} + action={async () => await handleDownload( + guildDiscoveryURL, + guild.name ? `${guild.name}-discovery-splash` : null, + assetAvailability["guild-discovery-splash"], + !!guildDiscoveryHash?.startsWith("a_") + )} + /> + ) : null].filter(Boolean); + + joinOrCreateContextMenuGroup( + children, + downloadifyItems, + "guild-content-group", + "downloadify-submenu", + "Download", + [{ + id: { child: "devmode-copy-id" }, + type: "WITH_GROUP", + position: "START" + }, + { + id: { child: "privacy" }, + type: "WITH_GROUP", + position: "END" + }] + ); +} + +export function UserContextMenu(children: Array, props: UserContextMenuProps): void { + if (!children?.length || !props.user?.id) { + return; + } + + const user = UserStore.getUser(props.user.id) as CustomizedUser; + const member = (props.guildId ? GuildMemberStore.getMember(props.guildId, props.user.id) : null) as CustomizedMember | null; + const guild = props.guildId ? GuildStore.getGuild(props.guildId) : null; + const userAvatarHash = user.avatar || null; + const memberAvatarHash = member?.avatar || null; + const userBannerHash = UserProfileStore.getUserProfile(user.id)?.banner || null; + const memberBannerHash = UserProfileStore.getGuildMemberProfile(user.id, props.guildId)?.banner || null; + const userAvatarDecorationHash = user.avatarDecorationData?.asset || null; + const memberAvatarDecorationHash = member?.avatarDecoration?.asset || null; + const userNameplatePath = user.collectibles?.nameplate?.asset || null; + const userAvatarURL = userAvatarHash ? parseURL(`${MEDIA_PROXY_BASE}/avatars/${user.id}/${userAvatarHash}.png`) : null; + const memberAvatarURL = memberAvatarHash ? parseURL(`${MEDIA_PROXY_BASE}/guilds/${props.guildId}/users/${user.id}/avatars/${memberAvatarHash}.png`) : null; + const userBannerURL = userBannerHash ? parseURL(`${MEDIA_PROXY_BASE}/banners/${user.id}/${userBannerHash}.png`) : null; + const memberBannerURL = memberBannerHash ? parseURL(`${MEDIA_PROXY_BASE}/guilds/${props.guildId}/users/${user.id}/banners/${memberBannerHash}.png`) : null; + const userAvatarDecorationURL = userAvatarDecorationHash ? parseURL(`${MEDIA_PROXY_BASE}/avatar-decoration-presets/${userAvatarDecorationHash}.png`) : null; + const memberAvatarDecorationURL = memberAvatarDecorationHash ? parseURL(`${MEDIA_PROXY_BASE}/avatar-decoration-presets/${memberAvatarDecorationHash}.png`) : null; + const userNameplateURL = userNameplatePath ? parseURL(`${CDN_BASE}/assets/collectibles/${userNameplatePath}static.png`) : null; + const defaultUserAvatarURL = parseURL(`${PRIMARY_DOMAIN_BASE}${defaultAssets.DEFAULT_AVATARS[user.discriminator === "0" ? (Math.floor(Number(BigInt(user.id) >> 22n)) % 6) : (Number(user.discriminator) % 5)]}`); + + const downloadifyItems = [userAvatarURL ? ( + ImageIcon({ width: 20, height: 20 })} + action={async () => await handleDownload( + userAvatarURL, + `${user.username}-avatar`, + assetAvailability["user-avatar"], + !!userAvatarHash?.startsWith("a_") + )} + /> + ) : null, + userBannerURL ? ( + ImageIcon({ width: 20, height: 20 })} + action={async () => await handleDownload( + userBannerURL, + `${user.username}-banner`, + assetAvailability["user-banner"], + !!userBannerHash?.startsWith("a_") + )} + /> + ) : null, + userAvatarDecorationURL ? ( + ImageIcon({ width: 20, height: 20 })} + action={async () => await handleDownload( + userAvatarDecorationURL, + `${user.username}-avatar-decoration`, + assetAvailability["avatar-decoration"], + true + )} + /> + ) : null, + memberAvatarURL ? ( + ImageIcon({ width: 20, height: 20 })} + action={async () => await handleDownload( + memberAvatarURL, + guild ? `${guild.name}-${user.username}-avatar` : `${user.username}-avatar`, + assetAvailability["user-avatar"], + !!memberAvatarHash?.startsWith("a_") + )} + /> + ) : null, + memberBannerURL ? ( + ImageIcon({ width: 20, height: 20 })} + action={async () => await handleDownload( + memberBannerURL, + guild ? `${guild.name}-${user.username}-banner` : `${user.username}-banner`, + assetAvailability["user-banner"], + !!memberBannerHash?.startsWith("a_") + )} + /> + ) : null, + memberAvatarDecorationURL ? ( + ImageIcon({ width: 20, height: 20 })} + action={async () => await handleDownload( + memberAvatarDecorationURL, + guild ? `${guild.name}-${user.username}-avatar-decoration` : `${user.username}-avatar-decoration`, + assetAvailability["avatar-decoration"], + true + )} + /> + ) : null, + defaultUserAvatarURL ? ( + ImageIcon({ width: 20, height: 20 })} + action={async () => await handleDownload( + defaultUserAvatarURL, + `${user.username}-default-avatar`, + assetAvailability["default-user-avatar"], + false + )} + /> + ) : null, + userNameplateURL ? ( + ImageIcon({ width: 20, height: 20 })} + action={async () => await handleDownload( + userNameplateURL, + `${user.username}-nameplate`, + assetAvailability.nameplate, + true + )} + /> + ) : null].filter(Boolean); + + joinOrCreateContextMenuGroup( + children, + downloadifyItems, + "user-content-group", + "downloadify-submenu", + "Download", + [{ + id: { child: "devmode-copy-id" }, + type: "WITH_GROUP", + position: "START" + }, + { + id: { child: "user-profile" }, + type: "WITH_GROUP", + position: "END" + }] + ); +} + +export async function handleHoverDownloadButtonClicked(props: HoverDownloadProps): Promise { + let { contentType } = props.item; + const { type, downloadUrl, srcIsAnimated, originalItem } = props.item; + const { displayStatus, statusDuration } = settings.store; + + if (!originalItem) { + // Fallback to default behavior. + DownloadifyLogger.info(`[${getFormattedNow()}] [UNRECOGNIZED HOVER DOWNLOAD ITEM]`, props.item); + displayStatus && showToast("File Opened in Browser", Toasts.Type.MESSAGE, { duration: statusDuration * 1000 }); + window.open(downloadUrl, "_blank"); + } else { + contentType ??= (await DownloadifyNative.queryURL(downloadUrl) || ""); + const assetInfo = (type === "IMAGE" ? (assetAvailability.attachment[contentType]) : null) ?? unknownCDN; + + await handleDownload( + parseURL(assetInfo.source === AssetSource.CDN ? downloadUrl : originalItem.proxy_url), + originalItem.title || null, + assetInfo, + !!srcIsAnimated + ); + } +} + +export async function handleExpandedModalDownloadButtonClicked(props: ExpandedModalDownloadProps, fallback: () => Promise): Promise { + let { contentType, srcIsAnimated } = props.item; + const { url, original, proxyUrl, sourceMetadata } = props.item; + const { displayStatus, statusDuration } = settings.store; + + if (!url && !original) { + // Fallback to default behavior. + DownloadifyLogger.info(`[${getFormattedNow()}] [UNRECOGNIZED EXPANDED MODAL DOWNLOAD ITEM]`, props.item); + displayStatus && showToast("Download Started", Toasts.Type.MESSAGE, { duration: statusDuration * 1000 }); + + try { + await fallback(); + displayStatus && showToast("Download Success", Toasts.Type.SUCCESS, { duration: statusDuration * 1000 }); + } catch (error) { + DownloadifyLogger.info(`[${getFormattedNow()}] [FALLBACK DOWNLOAD FAILED]`, error); + displayStatus && showToast("Download Failed", Toasts.Type.FAILURE, { duration: statusDuration * 1000 }); + } + } else { + contentType ??= (await DownloadifyNative.queryURL(url) || ""); + let aliasBasename = sourceMetadata?.identifier.title ?? null; + const isAttachment = sourceMetadata?.identifier.type === "attachment"; + const urlIsMediaProxy = url?.startsWith(MEDIA_PROXY_BASE); + const originalIsMediaProxy = original?.startsWith(MEDIA_PROXY_BASE); + const isMediaProxy = urlIsMediaProxy || originalIsMediaProxy; + const isPrimaryDomain = original?.startsWith(PRIMARY_DOMAIN_BASE); + const isCDN = original?.startsWith(CDN_BASE); + const isImageExt1 = url?.startsWith(IMAGE_EXT_1_DOMAIN_BASE); + const isImageExt2 = url?.startsWith(IMAGE_EXT_2_DOMAIN_BASE); + const isImageExt = isImageExt1 || isImageExt2; + let parsedURL: ParsedURL; + let assetInfo: any; + + if (isAttachment) { + parsedURL = parseURL(proxyUrl || url); + assetInfo = assetAvailability.attachment[contentType] ?? unknownAttachmentMediaProxy; + } else if (isImageExt) { + parsedURL = parseURL(url); + assetInfo = assetAvailability.attachment[contentType] ?? assetAvailability.external[contentType] ?? unknownExternalImageProxy; + } else if (isMediaProxy || isCDN) { + parsedURL = parseURL(urlIsMediaProxy ? url : original); + const guestimate = guesstimateAsset(parsedURL, contentType); + assetInfo = guestimate.assetInfo ?? (isCDN ? unknownCDN : unknownExternal); + aliasBasename = guestimate.aliasBasename ?? aliasBasename; + srcIsAnimated = guestimate.srcIsAnimated ?? srcIsAnimated; + } else { + parsedURL = parseURL(original ?? url); + assetInfo = isPrimaryDomain ? unknownPrimaryDomain : unknownExternal; + } + + await handleDownload( + parsedURL, + aliasBasename, + assetInfo, + !!srcIsAnimated, + ); + } +} + +function guesstimateAsset(parsedURL: ParsedURL, contentType: string): { + assetInfo?: AssetInfo; + aliasBasename?: string; + srcIsAnimated?: boolean; +} { + // Check if the URL is a known asset type. The only defined member + // of assetAvailability that we can't completely check for here are stickers + // due to their format information not being present in the URL. We can make + // accurate guesses based on certain extensions, but not for every format. + let assetInfo: AssetInfo | undefined; + let aliasBasename: string | undefined; + let srcIsAnimated: boolean | undefined; + const detected = parsedURL.url.match(ASSET_TYPE_EXTRACTOR); + const detectedPrimary = detected?.[1] ?? ""; + const detectedSecondary = detected?.[2] ?? ""; + const detectedTertiary = detected?.[3] ?? ""; + + if (detectedPrimary === "attachments") { + assetInfo = assetAvailability.attachment[contentType]; + } else if (detectedPrimary === "emojis") { + assetInfo = assetAvailability["custom-emoji"]; + } else if (detectedPrimary === "clan-badges") { + assetInfo = assetAvailability["clan-badge"]; + } else if (detectedPrimary === "role-icons") { + assetInfo = assetAvailability["role-icon-custom"]; + } else if (detectedPrimary === "discovery-splashes") { + assetInfo = assetAvailability["guild-discovery-splash"]; + } else if (detectedPrimary === "splashes") { + assetInfo = assetAvailability["guild-invite-splash"]; + } else if (detectedPrimary === "banners") { + assetInfo = assetAvailability["guild-banner"]; + } else if (detectedPrimary === "icons") { + assetInfo = assetAvailability["guild-icon"]; + } else if (detectedPrimary === "avatar-decoration-presets") { + assetInfo = assetAvailability["avatar-decoration"]; + srcIsAnimated = true; // Avatar decorations always have animated variants. + } else if (detectedPrimary === "avatars" || (detectedPrimary === "guilds" && detectedSecondary === "avatars")) { + assetInfo = assetAvailability["user-avatar"]; + } else if (detectedPrimary === "banners" || (detectedPrimary === "guilds" && detectedSecondary === "banners")) { + assetInfo = assetAvailability["user-banner"]; + } else if (detectedPrimary === "assets/collectibles/nameplates") { + assetInfo = assetAvailability.nameplate; + aliasBasename = detectedTertiary ?? "nameplate"; + srcIsAnimated = true; // Nameplates always have animated variants. + } else if (detectedPrimary === "stickers") { + if (parsedURL.extension === "gif") { + assetInfo = assetAvailability.sticker.GIF; + } else if (parsedURL.extension === "webp") { + if (srcIsAnimated) { + assetInfo = assetAvailability.sticker.GIF; + } else { + // There is no way to know if a static WEBP sticker + // link is sourced from an APNG, PNG, or GIF sticker. + } + } else if (parsedURL.extension === "png") { + if (srcIsAnimated) { + assetInfo = assetAvailability.sticker.PNG; + } else { + // There is no way to know if a static PNG sticker + // link is sourced from an APNG, PNG, or GIF sticker. + } + } else if (parsedURL.extension === "jpg") { + // There is no way to know if a JPG sticker link + // is sourced from an APNG, PNG, or GIF sticker. + } + } + + return { + assetInfo, + aliasBasename, + srcIsAnimated + }; +} + +export function VoiceMessageDownloadButton(props: VoiceMessageDownloadButtonProps): JSX.Element { + async function voiceMessageDownloadClicked(event: React.MouseEvent) { + event.preventDefault(); + const { item, message } = props; + const { proxy_url } = item.originalItem; + const baseName = `voice-message-${message.id}`; + const parsedURL = parseURL(proxy_url); + + await handleDownload( + parsedURL, + baseName, + { source: AssetSource.ATTACHMENT_MEDIA_PROXY, static: [], animated: [] }, + false, + ); + } + + const { voiceMessages } = settings.use(["voiceMessages"]); + + return ( + <> + {voiceMessages &&
+ +
} + + ); +} + +/** + * Common download handler for all other handlers. + */ +async function handleDownload( + url: ParsedURL, + alias: string | null, + asset: AssetInfo, + animated: boolean, +) { + const extensions = animated || !!asset.forceAnimated ? asset.animated : asset.static; + extensions.length < 1 && url.extension && extensions.push(url.extension); + const { defaultDirectory, overwriteFiles, displayStatus, allowUnicode, statusDuration } = settings.store; + const baseName = alias + ? sanitizeFilename(alias, allowUnicode, "discord-download") + : url.baseName + ? sanitizeFilename(url.baseName, allowUnicode, "discord-download") + : "discord-download"; + + let chosenExtension = extensions[0] ?? ""; + let resolvedExtension = (chosenExtension).replace("apng", "png").replace("awebp", "webp"); + let resolvedPath: string | null = null; + let resolvedURL = url.url; + + if (defaultDirectory) { + const resolvedDirectory = defaultDirectory.trim().replace(/^["']|["']$/g, "").replace(/[/\\]+$/, "").replace(/\\/g, "/"); + resolvedPath = `${resolvedDirectory}/${baseName}${resolvedExtension ? `.${resolvedExtension}` : ""}`; + displayStatus && showToast("Download Started", Toasts.Type.MESSAGE, { duration: statusDuration * 1000 }); + + if (!overwriteFiles) { + for (let num = 1; await DownloadifyNative.fileExists(resolvedPath); num++) { + resolvedPath = `${resolvedDirectory}/${baseName}-${num}${resolvedExtension ? `.${resolvedExtension}` : ""}`; + } + } + } else { + try { + resolvedPath = await DownloadifyNative.getFilePath(baseName, extensions); + + if (!resolvedPath) { + DownloadifyLogger.info(`[${getFormattedNow()}] [SAVE DIALOGUE CLOSED / DOWNLOAD CANCELED]`); + displayStatus && showToast("Download Canceled", Toasts.Type.MESSAGE, { duration: statusDuration * 1000 }); + return; + } + + const chosenFile = parseFile(resolvedPath); + chosenExtension = chosenFile.extension || ""; + resolvedExtension = (chosenExtension || "").replace("apng", "png").replace("awebp", "webp"); + + if (url.extension) { + const extReplacer = new RegExp(`\\.${chosenExtension}(?!.*\\.${chosenExtension})`, "i"); + resolvedPath = resolvedPath.replace(extReplacer, `.${resolvedExtension}`); + } + } catch (error) { + DownloadifyLogger.error(`[${getFormattedNow()}] [ERROR OPENING SAVE DIALOGUE]:`, error); + displayStatus && showToast("Error Opening Save Dialogue", Toasts.Type.FAILURE, { duration: statusDuration * 1000 }); + return; + } + } + + try { + let success = false; + + if ([AssetSource.ASSET_MEDIA_PROXY, AssetSource.ATTACHMENT_MEDIA_PROXY, AssetSource.EXTERNAL_IMAGE_PROXY].includes(asset.source)) { + let baseURL; + + if (asset.source === AssetSource.ASSET_MEDIA_PROXY) { + baseURL = url.host + url.path + url.baseName + (resolvedExtension ? `.${resolvedExtension}` : ""); + } else { + // Attachments on the media proxy use the `format` query parameter (seen below) to retrieve alternate + // formats instead of changing the file extension directly like assets on the media proxy do. + baseURL = url.host + url.path + url.baseName + + (url.extension ? `.${url.extension}` : "") + + (url.twitterExtra ? encodeURIComponent(url.twitterExtra) : ""); + } + + const authParams = url.params.expiry; + const newURL = new URL(baseURL); + + Object.entries(authParams).forEach(([key, value]) => { + newURL.searchParams.append(key, value); + }); + + if ([AssetSource.ATTACHMENT_MEDIA_PROXY, AssetSource.EXTERNAL_IMAGE_PROXY].includes(asset.source)) { + if (url.extension !== resolvedExtension) { + newURL.searchParams.append("format", resolvedExtension.replace("jpg", "jpeg")); + } + } else { + // Attachments on the media proxy do not support + // resizing but assets on the media proxy do. + newURL.searchParams.append("size", "4096"); + } + + if (chosenExtension === "awebp" && animated) { + newURL.searchParams.append("animated", "true"); + } else if (chosenExtension === "png" && animated) { + newURL.searchParams.append("passthrough", "false"); + } + + if (resolvedExtension === "webp") { + newURL.searchParams.append("lossless", "true"); + } + + resolvedURL = newURL.href; + } else if (asset.source === AssetSource.CDN) { + if (asset.type === "nameplate") { + resolvedURL = url.host + url.path + ( + chosenExtension === "apng" + ? "img.png" + : chosenExtension === "webm" + ? "asset.webm" + : "static.png" + ); + } else { + // Currently only the LOTTIE sticker. + resolvedURL = url.url; + } + } else if (asset.source === AssetSource.TENOR) { + const tenorID = url.path.replaceAll("/", "").slice(0, -2); + + if (chosenExtension === "gif") { + resolvedURL = `${TENOR_BASE}/${tenorID}${TENOR_GIF_ID}/tenor.gif`; + } else if (chosenExtension === "mp4") { + resolvedURL = `${TENOR_BASE}/${tenorID}${TENOR_MP4_ID}/tenor.mp4`; + } else { + DownloadifyLogger.warn(`[${getFormattedNow()}] [UNSUPPORTED TENOR EXTENSION]\n${chosenExtension}\n${resolvedPath}\n${resolvedURL}`); + displayStatus && showToast("Unsupported Tenor Extension", Toasts.Type.FAILURE, { duration: statusDuration * 1000 }); + return; + } + } else if ([AssetSource.EXTERNAL, AssetSource.PRIMARY_DOMAIN].includes(asset.source)) { + resolvedURL = url.url; + } + + DownloadifyLogger.info(`[${getFormattedNow()}] [STARTING DOWNLOAD]\n${resolvedPath}\n${resolvedURL}`); + success = await DownloadifyNative.downloadURL(resolvedURL, resolvedPath); + + if (success) { + DownloadifyLogger.info(`[${getFormattedNow()}] [DOWNLOAD SUCCESS]\n${resolvedPath}\n${resolvedURL}`); + displayStatus && showToast("File Downloaded", Toasts.Type.SUCCESS, { duration: statusDuration * 1000 }); + } else { + DownloadifyLogger.error(`[${getFormattedNow()}] [DOWNLOAD FAILED]\n${resolvedPath}\n${resolvedURL}`); + displayStatus && showToast("File Download Failed", Toasts.Type.FAILURE, { duration: statusDuration * 1000 }); + } + } catch (error) { + DownloadifyLogger.error(`[${getFormattedNow()}] [DOWNLOAD ERRORED]\n${resolvedPath}\n${resolvedURL}`, error); + displayStatus && showToast("Error Downloading File", Toasts.Type.FAILURE, { duration: statusDuration * 1000 }); + } +} diff --git a/src/equicordplugins/downloadify/utils/misc.tsx b/src/equicordplugins/downloadify/utils/misc.tsx new file mode 100644 index 000000000..ab1119fb4 --- /dev/null +++ b/src/equicordplugins/downloadify/utils/misc.tsx @@ -0,0 +1,253 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2025 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { findGroupChildrenByChildId } from "@api/ContextMenu"; +import { Menu } from "@webpack/common"; + +import { InsertGroup, ParsedFile, ParsedURL } from "./definitions"; + +/** + * Retrieve information about a file path by parsing its parts. + */ +export function parseFile(filePath: string): ParsedFile { + const parts = filePath.split(/[\\/]/); + let fileNameWithExtra = parts.pop() as string; + + while (fileNameWithExtra.includes("%25")) { + fileNameWithExtra = decodeURIComponent(fileNameWithExtra); + } + + fileNameWithExtra = decodeURIComponent(fileNameWithExtra); + + const posColon = fileNameWithExtra.indexOf(":"); + const fileNameWithoutExtra = posColon === -1 ? fileNameWithExtra : fileNameWithExtra.slice(0, posColon); + const twitterExtra = posColon === -1 ? null : fileNameWithExtra.slice(posColon); + + const path = parts.join("/") + "/"; + const posDot = fileNameWithoutExtra.lastIndexOf("."); + + if (fileNameWithoutExtra === "" || posDot < 1) { + return { + path, + baseName: fileNameWithoutExtra, + extension: null, + twitterExtra: null + }; + } + + return { + path, + baseName: fileNameWithoutExtra.slice(0, posDot), + extension: fileNameWithoutExtra.slice(posDot + 1).toLowerCase(), + twitterExtra: twitterExtra ?? null + }; +} + +/** + * Retrieve information about a URL by parsing its parts. + */ +export function parseURL(url: string): ParsedURL { + const parsed = new URL(url); + const { path, baseName, extension, twitterExtra } = parseFile(parsed.pathname); + const params = Array.from(parsed.searchParams.entries()).reduce( + (acc, [key, value]) => { + if (["ex", "is", "hm"].includes(key)) { + acc.expiry[key] = value; + } else { + acc.other[key] = value; + } + + return acc; + }, { expiry: {}, other: {} } as { expiry: Record, other: Record; } + ); + + return { + url: url, + host: parsed.origin.toLowerCase(), + path, + baseName, + extension, + params, + twitterExtra + }; +} + +/** + * Sanitize a file name by replacing invalid characters with underscores. + */ +export function sanitizeFilename(filename: string, allowUnicode: boolean, fallback?: string): string { + let sanitized = filename.replace(/[<>:"/\\|?*\x00-\x1F]/g, "_") // windows-reserved + .replace(/^\./, "_") // dot files + .replace(/(\.\.)+/g, "_") // relative paths + .replace(/[ \t\n\r]+/g, "_"); // spaces + + if (!allowUnicode) { + sanitized = sanitized.replace(/[^\x00-\x7F]/g, "_"); // non-ASCII characters + } else { + sanitized = sanitized.normalize("NFD"); // normalize unicode + } + + sanitized = sanitized.replace(/^_+|_+$/g, ""); // leading and trailing underscores + return sanitized || fallback || ""; +} + +/** + * Get the current date and time formatted as a string. + */ +export function getFormattedNow(): string { + return new Date().toLocaleString(undefined, { + year: "numeric", + month: "2-digit", + day: "2-digit", + hour: "2-digit", + minute: "2-digit", + second: "2-digit", + hour12: true + }).replace(",", ""); +} + +/** + * Join or create a group in the guild context menu. + */ +export function joinOrCreateContextMenuGroup( + children: Array, + items: Array, + groupId: string, + submenuId?: string, + submenuLabel?: string, + insertOrder?: InsertGroup[] +) { + function joinOrCreateSubmenu() { + const existingItemShouldJoinSubmenu = existingGroup.props.children.find(child => child?.props?.submenuId === submenuId); + let existingSubmenu = existingGroup.props.children.find(child => child?.props?.id === submenuId); + let numitems = items.length; + + if (existingItemShouldJoinSubmenu) { + existingSubmenu = null; + items.unshift(existingItemShouldJoinSubmenu); + numitems++; + const indexOfExistingItemShouldJoinSubmenu = existingGroup.props.children.findIndex(child => child === existingItemShouldJoinSubmenu); + existingGroup.props.children.splice(indexOfExistingItemShouldJoinSubmenu, 1); + } + + if (existingSubmenu) { + if (!Array.isArray(existingSubmenu.props.children)) { + existingSubmenu.props.children = [existingSubmenu.props.children]; + } + + items.forEach(member => member.props.label = member.props.submenuItemLabel ?? member.props.label); + existingSubmenu.props.children.push(...items); + } else if (numitems === 1) { + if (!chosenInsertOption?.position || chosenInsertOption.position === "END") { + existingGroup.props.children.push(items[0]); + } else { + existingGroup.props.children.unshift(items[0]); + } + } else { + existingSubmenu = ( + + {[...items]} + + ); + + items.forEach(member => member.props.label = member.props.submenuItemLabel ?? member.props.label); + + if (!chosenInsertOption?.position || chosenInsertOption.position === "END") { + existingGroup.props.children.push(existingSubmenu); + } else { + existingGroup.props.children.unshift(existingSubmenu); + } + } + } + + items.forEach(member => { + member.props.groupId = groupId; + member.props.submenuId = submenuId; + member.props.submenuLabel = submenuLabel; + }); + + const insertWithOptions = insertOrder?.filter(item => item.type === "WITH_GROUP"); + let existingGroup = children.find(child => child?.props?.id === groupId); + let chosenInsertOption; + + if (!existingGroup && insertWithOptions) { + let targetItem; + + for (const item of insertWithOptions) { + if (item.id.group) { + targetItem = children.find(child => child?.props?.id === item.id.group); + } + + if (!targetItem && item.id.child) { + targetItem = findGroupChildrenByChildId(item.id.child, children, true); + } + + if (targetItem) { + existingGroup = children.find(child => child?.props?.children === targetItem); + chosenInsertOption = item; + break; + } + } + } + + if (existingGroup) { + if (!Array.isArray(existingGroup.props.children)) { + existingGroup.props.children = [existingGroup.props.children]; + } + + if (!submenuId) { + if (!chosenInsertOption?.position || chosenInsertOption.position === "END") { + existingGroup.props.children.push(...items); + } else { + existingGroup.props.children.unshift(...items); + } + + return; + } else { + joinOrCreateSubmenu(); + } + } else { + if (!submenuId) { + existingGroup = ( + + {[...items]} + + ); + } else { + existingGroup = ({[]}); + joinOrCreateSubmenu(); + } + + const insertAfterBeforeOptions = insertOrder?.filter(item => item.type !== "WITH_GROUP") || []; + let targetIndex = children.length; + let targetItem; + + for (const item of insertAfterBeforeOptions) { + if (item.id.group) { + targetItem = children.find(child => child?.props?.id === item.id.group); + } + + if (!targetItem && item.id.child) { + targetItem = findGroupChildrenByChildId(item.id.child, children, true); + } + + if (targetItem) { + targetIndex = children.findIndex(child => child?.props?.children === targetItem); + + if (item.type === "AFTER_GROUP") { + targetIndex++; + } + + break; + } + } + + children.splice(targetIndex, 0, existingGroup); + } +} From f689aa6d89ac4e36877e9c8f695f9b61fe7e2ca7 Mon Sep 17 00:00:00 2001 From: thororen1234 <78185467+thororen1234@users.noreply.github.com> Date: Wed, 20 Aug 2025 12:32:06 -0400 Subject: [PATCH 08/21] HideMedia is back for now --- src/plugins/hideAttachments/index.tsx | 105 +++++++++++++++++++++++++ src/plugins/hideAttachments/styles.css | 10 +++ 2 files changed, 115 insertions(+) create mode 100644 src/plugins/hideAttachments/index.tsx create mode 100644 src/plugins/hideAttachments/styles.css diff --git a/src/plugins/hideAttachments/index.tsx b/src/plugins/hideAttachments/index.tsx new file mode 100644 index 000000000..94f3a86dd --- /dev/null +++ b/src/plugins/hideAttachments/index.tsx @@ -0,0 +1,105 @@ +/* + * Vencord, a modification for Discord's desktop app + * Copyright (c) 2022 Vendicated and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . +*/ + +import "./styles.css"; + +import { get, set } from "@api/DataStore"; +import { updateMessage } from "@api/MessageUpdater"; +import { migratePluginSettings } from "@api/Settings"; +import { ImageInvisible, ImageVisible } from "@components/Icons"; +import { Devs } from "@utils/constants"; +import { classes } from "@utils/misc"; +import definePlugin from "@utils/types"; +import { Message } from "@vencord/discord-types"; +import { ChannelStore } from "@webpack/common"; + +const KEY = "HideMedia_HiddenIds"; + +let hiddenMessages = new Set(); + +async function getHiddenMessages() { + const stored = await get(KEY); + hiddenMessages = new Set(stored ?? []); + return hiddenMessages; +} + +const saveHiddenMessages = (ids: Set) => set(KEY, [...ids]); + +migratePluginSettings("HideMedia", "HideAttachments"); + +const hasMedia = (msg: Message) => msg.attachments.length > 0 || msg.embeds.length > 0 || msg.stickerItems.length > 0; + +export default definePlugin({ + name: "HideMedia", + description: "Hide attachments and embeds for individual messages via hover button", + authors: [Devs.Ven], + dependencies: ["MessageUpdaterAPI"], + + patches: [{ + find: "this.renderAttachments(", + replacement: { + match: /(?<=\i=)this\.render(?:Attachments|Embeds|StickersAccessories)\((\i)\)/g, + replace: "$self.shouldHide($1?.id)?null:$&" + } + }], + + renderMessagePopoverButton(msg) { + if (!hasMedia(msg) && !msg.messageSnapshots.some(s => hasMedia(s.message))) return null; + + const isHidden = hiddenMessages.has(msg.id); + + return { + label: isHidden ? "Show Media" : "Hide Media", + icon: isHidden ? ImageVisible : ImageInvisible, + message: msg, + channel: ChannelStore.getChannel(msg.channel_id), + onClick: () => this.toggleHide(msg.channel_id, msg.id) + }; + }, + + renderMessageAccessory({ message }) { + if (!this.shouldHide(message.id)) return null; + + return ( + + Media Hidden + + ); + }, + + async start() { + await getHiddenMessages(); + }, + + stop() { + hiddenMessages.clear(); + }, + + shouldHide(messageId: string) { + return hiddenMessages.has(messageId); + }, + + async toggleHide(channelId: string, messageId: string) { + const ids = await getHiddenMessages(); + if (!ids.delete(messageId)) + ids.add(messageId); + + await saveHiddenMessages(ids); + updateMessage(channelId, messageId); + } +}); diff --git a/src/plugins/hideAttachments/styles.css b/src/plugins/hideAttachments/styles.css new file mode 100644 index 000000000..0c7498216 --- /dev/null +++ b/src/plugins/hideAttachments/styles.css @@ -0,0 +1,10 @@ +.vc-hideAttachments-accessory { + color: var(--text-muted); + margin-top: 0.5em; + font-style: italic; + font-weight: 400; +} + +.vc-hideAttachments-no-content { + margin-top: 0; +} \ No newline at end of file From cf04b80ebac76b2f42da6b9e21bd454b182ef0a3 Mon Sep 17 00:00:00 2001 From: thororen1234 <78185467+thororen1234@users.noreply.github.com> Date: Wed, 20 Aug 2025 14:51:12 -0400 Subject: [PATCH 09/21] Fix Patches --- src/equicordplugins/allCallTimers/index.tsx | 2 +- src/equicordplugins/betterBlockedUsers/index.tsx | 2 +- src/equicordplugins/noModalAnimation/index.ts | 4 ++-- src/equicordplugins/searchFix/index.tsx | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/equicordplugins/allCallTimers/index.tsx b/src/equicordplugins/allCallTimers/index.tsx index 3ce77fc04..b4fb0e852 100644 --- a/src/equicordplugins/allCallTimers/index.tsx +++ b/src/equicordplugins/allCallTimers/index.tsx @@ -119,7 +119,7 @@ export default definePlugin({ predicate: () => settings.store.showWithoutHover, replacement: [ { - match: /function\(\)\{.+:""(?=.*?userId:(\i))/, + match: /function \i\(\)\{.+:""(?=.*?userId:(\i))/, replace: "$&,$self.renderTimer($1.id)," } ] diff --git a/src/equicordplugins/betterBlockedUsers/index.tsx b/src/equicordplugins/betterBlockedUsers/index.tsx index c0747248c..d24086763 100644 --- a/src/equicordplugins/betterBlockedUsers/index.tsx +++ b/src/equicordplugins/betterBlockedUsers/index.tsx @@ -31,7 +31,7 @@ export default definePlugin({ replace: "$self.renderUser($1,$2)", }, { - match: /(?<=\}=(\i).{0,10}(\i).useState\(.{0,1}\);)/, + match: /(?<=\}=(\i).{0,10}(\i).useState\(\i\).{0,25}\};)/, replace: "let [searchResults,setSearchResults]=$2.useState([]);$self.setUpdateFunc($1,setSearchResults);" }, { diff --git a/src/equicordplugins/noModalAnimation/index.ts b/src/equicordplugins/noModalAnimation/index.ts index 17433d8dd..f27dc029f 100644 --- a/src/equicordplugins/noModalAnimation/index.ts +++ b/src/equicordplugins/noModalAnimation/index.ts @@ -36,8 +36,8 @@ export default definePlugin({ { find: "renderLurkerModeUpsellPopout,position:", replacement: { - match: /200:300/g, - replace: "0:0", + match: /(\i)=(200|300)/g, + replace: "$1=0", }, } ] diff --git a/src/equicordplugins/searchFix/index.tsx b/src/equicordplugins/searchFix/index.tsx index e45e4cd16..c1ae81917 100644 --- a/src/equicordplugins/searchFix/index.tsx +++ b/src/equicordplugins/searchFix/index.tsx @@ -43,7 +43,7 @@ export default definePlugin({ authors: [EquicordDevs.Jaxx], patches: [ { - find: '"SearchQueryStore";', + find: '"SearchQueryStore");', replacement: { match: /\i\.searchResultsQuery=(\i)/, replace: "$&,$self.main($1)" From 0a6bdc4edb52f9c0e088280a1af1bf1a51fd4090 Mon Sep 17 00:00:00 2001 From: thororen1234 <78185467+thororen1234@users.noreply.github.com> Date: Wed, 20 Aug 2025 14:57:57 -0400 Subject: [PATCH 10/21] Fix VC Patches --- src/equicordplugins/equicordToolbox/index.tsx | 2 +- src/plugins/_api/notices.ts | 4 ++-- src/plugins/ignoreActivities/index.tsx | 2 +- src/plugins/superReactionTweaks/index.ts | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/equicordplugins/equicordToolbox/index.tsx b/src/equicordplugins/equicordToolbox/index.tsx index 51ee62c26..b20c123e7 100644 --- a/src/equicordplugins/equicordToolbox/index.tsx +++ b/src/equicordplugins/equicordToolbox/index.tsx @@ -165,7 +165,7 @@ export default definePlugin({ { find: ".controlButtonWrapper,", replacement: { - match: /(?<=toolbar:function\(.{0,100}\()\i.Fragment,/, + match: /(?<=function (\i).{0,100}\()\i.Fragment,(?=.+?toolbar:\1\(\))/, replace: "$self.ToolboxFragmentWrapper," } } diff --git a/src/plugins/_api/notices.ts b/src/plugins/_api/notices.ts index ace58e705..89aa1a465 100644 --- a/src/plugins/_api/notices.ts +++ b/src/plugins/_api/notices.ts @@ -33,8 +33,8 @@ export default definePlugin({ replace: "if(Vencord.Api.Notices.currentNotice)return false;$&" }, { - match: /(?<=,NOTICE_DISMISS:function\(\i\){)return null!=(\i)/, - replace: (m, notice) => `if(${notice}?.id=="EquicordNotice")return(${notice}=null,Vencord.Api.Notices.nextNotice(),true);${m}` + match: /(?<=function (\i)\(\i\){)return null!=(\i)(?=.+?NOTICE_DISMISS:\1)/, + replace: (m, _, notice) => `if(${notice}?.id=="VencordNotice")return(${notice}=null,Vencord.Api.Notices.nextNotice(),true);${m}` } ] } diff --git a/src/plugins/ignoreActivities/index.tsx b/src/plugins/ignoreActivities/index.tsx index fa5573aa3..d541ffbff 100644 --- a/src/plugins/ignoreActivities/index.tsx +++ b/src/plugins/ignoreActivities/index.tsx @@ -259,7 +259,7 @@ export default definePlugin({ replacement: { // let { ... nowPlaying: a = !1 ... // let { overlay: b ... } = Props - match: /#{intl::SETTINGS_GAMES_TOGGLE_OVERLAY}.+?}\(\),(?<=nowPlaying:(\i)=!1,.+?overlay:\i,[^}]+?\}=(\i).+?)/, + match: /null:.{0,10}\i\(\),(?<=nowPlaying:(\i)=!1,.+?overlay:\i,[^}]+?\}=(\i).+?)/, replace: (m, nowPlaying, props) => `${m}$self.renderToggleGameActivityButton(${props},${nowPlaying}),` } }, diff --git a/src/plugins/superReactionTweaks/index.ts b/src/plugins/superReactionTweaks/index.ts index 046ae4f55..8b0e55f97 100644 --- a/src/plugins/superReactionTweaks/index.ts +++ b/src/plugins/superReactionTweaks/index.ts @@ -42,8 +42,8 @@ export default definePlugin({ { find: ",BURST_REACTION_EFFECT_PLAY", replacement: { - match: /(BURST_REACTION_EFFECT_PLAY:\i=>{.+?if\()(\(\(\i,\i\)=>.+?\(\i,\i\))>=\d+?(?=\))/, - replace: (_, rest, playingCount) => `${rest}!$self.shouldPlayBurstReaction(${playingCount})` + match: /(?<=(\i)=\i=>{.+?)(\i\(\i,\i\))>=\i(?=\).+BURST_REACTION_EFFECT_PLAY:\1)/, + replace: "!$self.shouldPlayBurstReaction($2)" } }, { From 9b5a5f456c9855a249d03aefe80b667811bca8bc Mon Sep 17 00:00:00 2001 From: thororen1234 <78185467+thororen1234@users.noreply.github.com> Date: Wed, 20 Aug 2025 17:41:59 -0400 Subject: [PATCH 11/21] Test --- src/equicordplugins/allCallTimers/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/equicordplugins/allCallTimers/index.tsx b/src/equicordplugins/allCallTimers/index.tsx index b4fb0e852..3ce77fc04 100644 --- a/src/equicordplugins/allCallTimers/index.tsx +++ b/src/equicordplugins/allCallTimers/index.tsx @@ -119,7 +119,7 @@ export default definePlugin({ predicate: () => settings.store.showWithoutHover, replacement: [ { - match: /function \i\(\)\{.+:""(?=.*?userId:(\i))/, + match: /function\(\)\{.+:""(?=.*?userId:(\i))/, replace: "$&,$self.renderTimer($1.id)," } ] From 5542ac620279ba5629f020e0357060e967c28b87 Mon Sep 17 00:00:00 2001 From: Etorix <92535668+EtorixDev@users.noreply.github.com> Date: Wed, 20 Aug 2025 14:42:33 -0700 Subject: [PATCH 12/21] Fix patch. (#352) --- src/plugins/showMeYourName/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/showMeYourName/index.tsx b/src/plugins/showMeYourName/index.tsx index 05bd9bd74..74863de73 100644 --- a/src/plugins/showMeYourName/index.tsx +++ b/src/plugins/showMeYourName/index.tsx @@ -788,7 +788,7 @@ export default definePlugin({ replacement: [ { // Replace names in messages and replies. - match: /(onContextMenu:\i,children:)(.{0,100}?),"data-text":(\i\+\i)/, + match: /(onContextMenu:\i,children:)(.{0,150}?),"data-text":(\i\+\i)/, replace: "$1$self.getMessageNameElement(arguments[0])??($2),\"data-text\":$self.getMessageNameText(arguments[0])??($3)" }, { From c4bd6b06b23ae4c566c556a29a4568e7e07873d1 Mon Sep 17 00:00:00 2001 From: Etorix <92535668+EtorixDev@users.noreply.github.com> Date: Wed, 20 Aug 2025 14:42:41 -0700 Subject: [PATCH 13/21] Remove no longer used patch. (#351) --- src/equicordplugins/questify/index.tsx | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/equicordplugins/questify/index.tsx b/src/equicordplugins/questify/index.tsx index 260aaf8b4..13b95d4ff 100644 --- a/src/equicordplugins/questify/index.tsx +++ b/src/equicordplugins/questify/index.tsx @@ -973,14 +973,6 @@ export default definePlugin({ } ] }, - { - // Prevents default pinning of specific Quests to the top of the list. - find: "questTile,{[", - replacement: { - match: /\i.unshift\(\i\):(\i.push\(\i\))/, - replace: "$1:$1" - } - }, { // Sorts the "All Quests" tab quest tiles. // Also sets mobile-only Quests as desktop compatible if the setting is enabled. From 2a123dcefad7801f4719a49f9b43b542ba0a5f8a Mon Sep 17 00:00:00 2001 From: thororen1234 <78185467+thororen1234@users.noreply.github.com> Date: Wed, 20 Aug 2025 17:43:23 -0400 Subject: [PATCH 14/21] Tests --- src/equicordplugins/allCallTimers/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/equicordplugins/allCallTimers/index.tsx b/src/equicordplugins/allCallTimers/index.tsx index 3ce77fc04..b4fb0e852 100644 --- a/src/equicordplugins/allCallTimers/index.tsx +++ b/src/equicordplugins/allCallTimers/index.tsx @@ -119,7 +119,7 @@ export default definePlugin({ predicate: () => settings.store.showWithoutHover, replacement: [ { - match: /function\(\)\{.+:""(?=.*?userId:(\i))/, + match: /function \i\(\)\{.+:""(?=.*?userId:(\i))/, replace: "$&,$self.renderTimer($1.id)," } ] From 1392a3f6aeede92d812f46697dba1ba05124adf1 Mon Sep 17 00:00:00 2001 From: thororen1234 <78185467+thororen1234@users.noreply.github.com> Date: Wed, 20 Aug 2025 18:57:50 -0400 Subject: [PATCH 15/21] Fix Patches --- src/equicordplugins/questCompleter.discordDesktop/index.tsx | 6 +++--- src/plugins/showMeYourName/index.tsx | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/equicordplugins/questCompleter.discordDesktop/index.tsx b/src/equicordplugins/questCompleter.discordDesktop/index.tsx index 330aa7f64..614e827cc 100644 --- a/src/equicordplugins/questCompleter.discordDesktop/index.tsx +++ b/src/equicordplugins/questCompleter.discordDesktop/index.tsx @@ -262,14 +262,14 @@ export default definePlugin({ settings, patches: [ { - find: "BkZhUF)}", + find: "BkZhUF),", replacement: { - match: /(?<=questId:(\i\.id).*?\.PRIMARY,)disabled:!0/, + match: /(?<=questId:(\i\.id).*?"secondary",)disabled:!0/, replace: "onClick: () => $self.mobileQuestPatch($1)" }, }, { - find: "AppTitleBar", + find: '"BACK_FORWARD_NAVIGATION"', replacement: { match: /(?<=trailing:.{0,70}\(\i\.Fragment,{children:\[)/, replace: "$self.renderQuestButton()," diff --git a/src/plugins/showMeYourName/index.tsx b/src/plugins/showMeYourName/index.tsx index 74863de73..52cc38a94 100644 --- a/src/plugins/showMeYourName/index.tsx +++ b/src/plugins/showMeYourName/index.tsx @@ -788,7 +788,7 @@ export default definePlugin({ replacement: [ { // Replace names in messages and replies. - match: /(onContextMenu:\i,children:)(.{0,150}?),"data-text":(\i\+\i)/, + match: /(onContextMenu:\i,children:)(.{0,250}?),"data-text":(\i\+\i)/, replace: "$1$self.getMessageNameElement(arguments[0])??($2),\"data-text\":$self.getMessageNameText(arguments[0])??($3)" }, { From bb85e52dd9c78da7e77b4ad7ed49e67e5319ec9c Mon Sep 17 00:00:00 2001 From: Etorix <92535668+EtorixDev@users.noreply.github.com> Date: Wed, 20 Aug 2025 15:58:16 -0700 Subject: [PATCH 16/21] Fix logical flaw. (#353) --- src/plugins/showMeYourName/index.tsx | 44 +++++++++------------------- 1 file changed, 14 insertions(+), 30 deletions(-) diff --git a/src/plugins/showMeYourName/index.tsx b/src/plugins/showMeYourName/index.tsx index 52cc38a94..5eca3a0b3 100644 --- a/src/plugins/showMeYourName/index.tsx +++ b/src/plugins/showMeYourName/index.tsx @@ -487,9 +487,9 @@ function renderUsername( const message = channelId && messageId ? MessageStore.getMessage(channelId, messageId) : null; const groupId = (message as any)?.showMeYourNameGroupId || null; const isHovering = (isMessage || isReply || isMention) - ? (hoveringMessageSet.has(Number(messageId)) || hoveringMessageSet.has(Number(groupId))) + ? ((messageId && hoveringMessageSet.has(messageId)) || (groupId && hoveringMessageSet.has(groupId))) : isReactionsPopout - ? hoveringReactionPopoutSet.has(Number((author as User).id)) + ? hoveringReactionPopoutSet.has((author as User).id) : false; const shouldGradientGlow = isHovering && hasGradient; @@ -632,43 +632,27 @@ function renderUsername( return [allDataText, nameElement, first.name]; } -const hoveringMessageSet = new Set(); -const hoveringReactionPopoutSet = new Set(); +const hoveringMessageSet = new Set(); +const hoveringReactionPopoutSet = new Set(); function addHoveringMessage(id: string) { - const prevSize = hoveringMessageSet.size; - hoveringMessageSet.add(Number(id)); - - if (hoveringMessageSet.size !== prevSize) { - settings.store.triggerNameRerender = !settings.store.triggerNameRerender; - } + hoveringMessageSet.add(id); + settings.store.triggerNameRerender = !settings.store.triggerNameRerender; } function removeHoveringMessage(id: string) { - const prevSize = hoveringMessageSet.size; - hoveringMessageSet.delete(Number(id)); - - if (hoveringMessageSet.size !== prevSize) { - settings.store.triggerNameRerender = !settings.store.triggerNameRerender; - } + hoveringMessageSet.delete(id); + settings.store.triggerNameRerender = !settings.store.triggerNameRerender; } function addHoveringReactionPopout(id: string) { - const prevSize = hoveringReactionPopoutSet.size; - hoveringReactionPopoutSet.add(Number(id)); - - if (hoveringReactionPopoutSet.size !== prevSize) { - settings.store.triggerNameRerender = !settings.store.triggerNameRerender; - } + hoveringReactionPopoutSet.add(id); + settings.store.triggerNameRerender = !settings.store.triggerNameRerender; } function removeHoveringReactionPopout(id: string) { - const prevSize = hoveringReactionPopoutSet.size; - hoveringReactionPopoutSet.delete(Number(id)); - - if (hoveringReactionPopoutSet.size !== prevSize) { - settings.store.triggerNameRerender = !settings.store.triggerNameRerender; - } + hoveringReactionPopoutSet.delete(id); + settings.store.triggerNameRerender = !settings.store.triggerNameRerender; } const settings = definePluginSettings({ @@ -792,7 +776,7 @@ export default definePlugin({ replace: "$1$self.getMessageNameElement(arguments[0])??($2),\"data-text\":$self.getMessageNameText(arguments[0])??($3)" }, { - // Animate gradients in first-level messages. + // Animate gradients for message authors. match: /(let{setAnimate:\i}=(\i);)/, replace: "$1if($2.animate){$self.addHoveringMessage(arguments[0].message.id)}else{$self.removeHoveringMessage(arguments[0].message.id)};" } @@ -832,7 +816,7 @@ export default definePlugin({ find: "CUSTOM_GIFT?\"\":", replacement: { match: /(\(\i,\i,\i\);)(let \i=\i.id===\i(?:.{0,500}?)hovering:(\i))/, - replace: "$1arguments[0].message.showMeYourNameGroupId=arguments[0].groupId;if($3){$self.addHoveringMessage(arguments[0].groupId);}else{$self.removeHoveringMessage(arguments[0].groupId)};$2" + replace: "$1arguments[0].message.showMeYourNameGroupId=arguments[0].groupId;if($3){$self.addHoveringMessage(arguments[0].groupId)}else{$self.removeHoveringMessage(arguments[0].groupId)};$2" }, }, { From 0165d01ed088bf97268871fcc7c0696d281e47e7 Mon Sep 17 00:00:00 2001 From: Etorix <92535668+EtorixDev@users.noreply.github.com> Date: Wed, 20 Aug 2025 16:19:35 -0700 Subject: [PATCH 17/21] Fix patch. (#354) --- src/equicordplugins/questify/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/equicordplugins/questify/index.tsx b/src/equicordplugins/questify/index.tsx index 13b95d4ff..30de075fc 100644 --- a/src/equicordplugins/questify/index.tsx +++ b/src/equicordplugins/questify/index.tsx @@ -1046,7 +1046,7 @@ export default definePlugin({ { // The Quest Accepted button is disabled by default. If the user reloads the client, they need a way // to resume the automatic completion, so patch in optionally enabling it if the feature is enabled. - match: /(START_QUEST_CTA.{0,400}?)(!0)/, + match: /(START_QUEST_CTA.{0,400}?disabled:)(!0)/, replace: "$1$self.shouldDisableQuestAcceptedButton(arguments[0].quest)??$2" }, { From 382153f9ed1a57318b79dd394ec3374f8ac611af Mon Sep 17 00:00:00 2001 From: Etorix <92535668+EtorixDev@users.noreply.github.com> Date: Thu, 21 Aug 2025 11:02:43 -0700 Subject: [PATCH 18/21] Improve some things. (#355) --- src/equicordplugins/questify/index.tsx | 37 ++++++++--- src/equicordplugins/questify/settings.tsx | 70 ++++++++++----------- src/equicordplugins/questify/utils/misc.tsx | 4 +- 3 files changed, 64 insertions(+), 47 deletions(-) diff --git a/src/equicordplugins/questify/index.tsx b/src/equicordplugins/questify/index.tsx index 30de075fc..70baf9d43 100644 --- a/src/equicordplugins/questify/index.tsx +++ b/src/equicordplugins/questify/index.tsx @@ -430,7 +430,7 @@ function preprocessQuests(quests: Quest[]): Quest[] { const unclaimedSortFunction = createSortFunction(unclaimedSubsort || "Recent DESC"); - // Divide unclaimed quests by completion status before applying subsort. + // Divide unclaimed Quests by completion status before applying subsort. questGroups.unclaimed.sort((a: Quest, b: Quest) => { const aCompleted = !!a.userStatus?.completedAt; const bCompleted = !!b.userStatus?.completedAt; @@ -688,12 +688,12 @@ function processQuestForAutoComplete(quest: Quest): boolean { if (quest.userStatus?.completedAt || existingInterval) { return true; } else if (!playType && !watchType) { - QuestifyLogger.warn(`[${getFormattedNow()}] Could not recognize the quest type for ${questName}.`); + QuestifyLogger.warn(`[${getFormattedNow()}] Could not recognize the Quest type for ${questName}.`); return true; } else if ((watchType && !completeVideoQuestsInBackground) || (playType && (!completeGameQuestsInBackground || !IS_DISCORD_DESKTOP))) { return true; } else if (!questDuration) { - QuestifyLogger.warn(`[${getFormattedNow()}] Could not find duration for quest ${questName}.`); + QuestifyLogger.warn(`[${getFormattedNow()}] Could not find duration for Quest ${questName}.`); return true; } else if (watchType) { startVideoProgressTracking(quest, questDuration); @@ -747,7 +747,7 @@ function getQuestAcceptedButtonText(quest: Quest): string | null { return null; } -function getActiveQuestClosestToCompletion(): Quest | null { +function getQuestPanelOverride(): Quest | null { let closestQuest: Quest | null = null; let closestTimeRemaining = Infinity; @@ -766,6 +766,23 @@ function getActiveQuestClosestToCompletion(): Quest | null { } }); + if (!closestQuest) { + const completedQuests = Array.from(QuestsStore.quests.values() as Quest[]).filter(q => q.userStatus?.completedAt).sort((a, b) => { + const aTime = new Date(a.userStatus?.completedAt as string); + const bTime = new Date(b.userStatus?.completedAt as string); + return bTime.getTime() - aTime.getTime(); + }); + + completedQuests.forEach(quest => { + const completedQuest = quest.userStatus?.completedAt; + const questStatus = getQuestStatus(quest); + + if (completedQuest && questStatus === QuestStatus.Unclaimed) { + closestQuest = quest; + } + }); + } + return closestQuest; } @@ -789,7 +806,7 @@ export default definePlugin({ shouldHideGiftInventoryRelocationNotice, shouldHideFriendsListActiveNowPromotion, shouldDisableQuestAcceptedButton, - getActiveQuestClosestToCompletion, + getQuestPanelOverride, getQuestAcceptedButtonText, processQuestForAutoComplete, activeQuestIntervals, @@ -858,7 +875,7 @@ export default definePlugin({ }, { // Hides the new Quest popup above the account panel. - // Allows in-progress quests to still show. + // Allows in-progress Quests to still show. find: "QUESTS_BAR,questId", replacement: { match: /return null==(\i)\?null:\(/, @@ -871,7 +888,7 @@ export default definePlugin({ find: "questDeliveryOverride)?", replacement: { match: /(\i=)(\i.\i.questDeliveryOverride)/, - replace: "$1$self.getActiveQuestClosestToCompletion()??$2" + replace: "$1$self.getQuestPanelOverride()??$2" } }, { @@ -957,7 +974,7 @@ export default definePlugin({ group: true, replacement: [ { - // Restyles quest tiles with colors. + // Restyles Quest tiles with colors. match: /className:(\i\(\)\(\i.container,\i\)),/, replace: "className:$self.getQuestTileClasses($1,arguments[0].quest),style:$self.getQuestTileStyle(arguments[0].quest)," }, @@ -974,7 +991,7 @@ export default definePlugin({ ] }, { - // Sorts the "All Quests" tab quest tiles. + // Sorts the "All Quests" tab Quest tiles. // Also sets mobile-only Quests as desktop compatible if the setting is enabled. find: ".ALL);return(", replacement: { @@ -983,7 +1000,7 @@ export default definePlugin({ } }, { - // Sorts the "Claimed Quests" tab quest tiles. + // Sorts the "Claimed Quests" tab Quest tiles. find: ".ALL)}):(", replacement: { match: /(claimedQuests:(\i).{0,50}?;)/, diff --git a/src/equicordplugins/questify/settings.tsx b/src/equicordplugins/questify/settings.tsx index 65a133056..51c82ebb7 100644 --- a/src/equicordplugins/questify/settings.tsx +++ b/src/equicordplugins/questify/settings.tsx @@ -50,10 +50,10 @@ export function fetchAndAlertQuests(source: string, logger: Logger): void { const shouldAlert = settings.store.fetchingQuestsAlert; if (shouldAlert) { - logger.info(`[${getFormattedNow()}] New quests detected. Playing alert sound.`); + logger.info(`[${getFormattedNow()}] New Quests detected. Playing alert sound.`); AudioPlayer(shouldAlert, 1).play(); } else { - logger.info(`[${getFormattedNow()}] New quests detected.`); + logger.info(`[${getFormattedNow()}] New Quests detected.`); } } } @@ -75,13 +75,13 @@ export function startAutoFetchingQuests(seconds?: number): void { } const interval = seconds ? seconds * 1000 : settings.store.fetchingQuestsInterval * 1000; - QuestifyLogger.info(`[${getFormattedNow()}] Starting AutoFetch of quests every ${(interval / 60000).toFixed(2)} minutes.`); + QuestifyLogger.info(`[${getFormattedNow()}] Starting AutoFetch of Quests every ${(interval / 60000).toFixed(2)} minutes.`); autoFetchInterval = setInterval(() => { fetchAndAlertQuests("Questify-AutoFetch", QuestifyLogger); }, interval); } export function stopAutoFetchingQuests(): void { if (autoFetchInterval) { - QuestifyLogger.info(`[${getFormattedNow()}] Stopping AutoFetch of quests.`); + QuestifyLogger.info(`[${getFormattedNow()}] Stopping AutoFetch of Quests.`); clearInterval(autoFetchInterval); autoFetchInterval = null; } @@ -712,8 +712,7 @@ function RestyleQuestsSetting() { restyleQuestsIgnored, restyleQuestsExpired, restyleQuestsGradient, - restyleQuestsPreload, - ignoredQuestProfile + restyleQuestsPreload } = settings.use([ "restyleQuestsUnclaimed", "restyleQuestsClaimed", @@ -721,7 +720,6 @@ function RestyleQuestsSetting() { "restyleQuestsExpired", "restyleQuestsGradient", "restyleQuestsPreload", - "ignoredQuestProfile" ]); const [unclaimedColor, setUnclaimedColor] = useState(restyleQuestsUnclaimed); @@ -871,26 +869,6 @@ function RestyleQuestsSetting() { /> -
-
- - Ignored Quest Profile - - value === ignoredQuestProfile} + select={(value: string) => { + settings.store.ignoredQuestProfile = value; + }} + /> +
+
@@ -1314,7 +1314,7 @@ function FetchingQuestsSetting(): JSX.Element { Configure how often to fetch Quests from Discord and set up alerts for new Quests.

- By default, Discord only fetches quests on load and when visiting the Quests page. + By default, Discord only fetches Quests on load and when visiting the Quests page. This means that without a fetch interval defined below, this plugin will become unaware of new Quests added throughout the day.

@@ -1609,25 +1609,25 @@ export const settings = definePluginSettings({ }, unclaimedSubsort: { type: OptionType.STRING, - description: "Subsort method for unclaimed quests.", + description: "Subsort method for unclaimed Quests.", default: "Expiring ASC", // "Recent ASC", "Recent DESC", "Expiring ASC", "Expiring DESC" hidden: true }, claimedSubsort: { type: OptionType.STRING, - description: "Subsort method for claimed quests.", + description: "Subsort method for claimed Quests.", default: "Claimed DESC", // "Recent ASC", "Recent DESC", "Claimed ASC", "Claimed DESC" hidden: true }, ignoredSubsort: { type: OptionType.STRING, - description: "Subsort method for ignored quests.", + description: "Subsort method for ignored Quests.", default: "Recent DESC", // "Recent ASC", "Recent DESC", "Expiring ASC", "Expiring DESC" hidden: true }, expiredSubsort: { type: OptionType.STRING, - description: "Subsort method for expired quests.", + description: "Subsort method for expired Quests.", default: "Expiring DESC", // "Recent ASC", "Recent DESC", "Expiring ASC", "Expiring DESC" hidden: true }, @@ -1639,7 +1639,7 @@ export const settings = definePluginSettings({ }, onQuestsPage: { type: OptionType.BOOLEAN, - description: "Whether the user is currently on the quests page.", + description: "Whether the user is currently on the Quests page.", default: false, hidden: true }, diff --git a/src/equicordplugins/questify/utils/misc.tsx b/src/equicordplugins/questify/utils/misc.tsx index 1aeff166d..5f62452b5 100644 --- a/src/equicordplugins/questify/utils/misc.tsx +++ b/src/equicordplugins/questify/utils/misc.tsx @@ -18,7 +18,7 @@ export const QuestifyLogger = new Logger("Questify"); export const PlayAudio = findByCodeLazy("Unable to find sound for pack name:"); // Takes an audio name or URL, volume, and a callback function. Plays when play() is called. const AudioPlayerConstructor = findByCodeLazy("sound has no duration"); -export function AudioPlayer(name: string, volume: number = 1, callback?: () => void): any { return new AudioPlayerConstructor(name, null, volume, "default", callback || (() => { })); } +export function AudioPlayer(name: string, volume: number = 1, callback?: () => void): any { return new AudioPlayerConstructor(name, null, volume, "default", callback ?? (() => { })); } export const QuestsStore = findStoreLazy("QuestsStore"); export const questPath = "/discovery/quests"; export const leftClick = 0; @@ -267,7 +267,7 @@ export async function reportPlayGameQuestProgress(quest: Quest, terminal: boolea const progress = progressPlayType?.value || 1; if (!questPlayType) { - logger?.warn(`[${getFormattedNow()}] Could not recognize the quest type for Quest ${questName}.`); + logger?.warn(`[${getFormattedNow()}] Could not recognize the Quest type for Quest ${questName}.`); return { progress: null }; } From aa6915072caf1ae0594484c97f65ed8a0ddaf22c Mon Sep 17 00:00:00 2001 From: thororen1234 <78185467+thororen1234@users.noreply.github.com> Date: Fri, 22 Aug 2025 13:17:52 -0400 Subject: [PATCH 19/21] Add Some Readmes --- .../{ABSRPC => absRPC}/index.tsx | 0 src/equicordplugins/allCallTimers/README.md | 12 +++++ src/equicordplugins/altKrispSwitch/README.md | 3 ++ .../alwaysExpandProfiles/README.md | 3 ++ src/equicordplugins/amITyping/README.md | 3 ++ src/equicordplugins/anammox/README.md | 9 ++++ src/equicordplugins/atSomeone/README.md | 10 ++++ src/equicordplugins/autoJump/README.md | 3 ++ .../bannersEverywhere/README.md | 5 ++ .../betterActivities/README.md | 7 +++ .../betterAudioPlayer/README.md | 3 ++ .../betterBanReasons/README.md | 7 +++ .../betterBlockedUsers/README.md | 3 ++ src/equicordplugins/betterCommands/README.md | 3 ++ src/equicordplugins/betterInvites/README.md | 11 +++++ .../betterPlusReacts/README.md | 3 ++ .../index.ts | 1 - src/equicordplugins/blockKrisp/README.md | 5 ++ src/equicordplugins/bypassPinPrompt/README.md | 3 ++ src/equicordplugins/bypassStatus/README.md | 3 ++ src/equicordplugins/channelBadges/README.md | 3 ++ src/equicordplugins/channelTabs/README.md | 7 +++ .../characterCounter/README.md | 3 ++ .../cleanChannelName/README.md | 3 ++ src/equicordplugins/clientSideBlock/README.md | 3 ++ src/equicordplugins/clientSideBlock/index.tsx | 8 --- .../README.md | 3 ++ src/equicordplugins/commandPalette/README.md | 3 ++ src/equicordplugins/commandPalette/doc.md | 49 +++++++++++++++++++ src/equicordplugins/contentWarning/README.md | 3 ++ src/equicordplugins/contentWarning/index.tsx | 2 +- .../copyProfileColors/README.md | 3 ++ src/equicordplugins/copyStatusUrls/README.md | 3 ++ src/equicordplugins/copyUserMention/README.md | 3 ++ .../customFolderIcons/README.md | 11 +++++ src/equicordplugins/customSounds/README.md | 3 ++ .../customTimestamps/README.md | 3 ++ .../customUserColors/README.md | 3 ++ src/equicordplugins/cuteAnimeBoys/README.md | 3 ++ src/equicordplugins/cuteNekos/README.md | 3 ++ src/equicordplugins/cutePats/README.md | 3 ++ src/equicordplugins/demonstration/README.md | 3 ++ src/equicordplugins/disableCameras/README.md | 3 ++ .../discordDevBanner/README.md | 3 ++ src/equicordplugins/dontFilterMe/README.md | 3 ++ src/equicordplugins/downloadify/README.md | 3 ++ src/equicordplugins/equicordHelper/README.md | 3 ++ src/equicordplugins/equicordToolbox/README.md | 3 ++ src/equicordplugins/equissant/README.md | 3 ++ src/equicordplugins/exportContacts/README.md | 3 ++ src/equicordplugins/exportMessages/README.md | 3 ++ .../fakeProfileThemesAndEffects/README.md | 3 ++ .../fastDeleteChannels/README.md | 3 ++ src/equicordplugins/findReply/README.md | 5 ++ .../fixFileExtensions/README.MD | 3 ++ src/equicordplugins/followVoiceUser/README.md | 3 ++ src/equicordplugins/fontLoader/README.md | 3 ++ src/equicordplugins/forwardAnywhere/README.md | 3 ++ src/equicordplugins/freaky/README.md | 3 ++ .../frequentQuickSwitcher/README.md | 3 ++ src/equicordplugins/friendCloud/README.md | 7 +++ src/equicordplugins/friendCodes/README.md | 3 ++ src/equicordplugins/friendTags/README.md | 3 ++ src/equicordplugins/friendshipRanks/README.md | 3 ++ src/equicordplugins/fullVcPfp/README.md | 3 ++ src/equicordplugins/ignoreTerms/README.md | 5 ++ .../messageLinkTooltip/README.md | 3 ++ .../README.md | 3 ++ .../index.tsx | 0 src/equicordplugins/sidebarChat/README.md | 5 ++ .../statusWhilePlaying.desktop/README.md | 5 ++ src/equicordplugins/teX/README.md | 8 +++ .../timelessClips.desktop/README.md | 3 ++ src/equicordplugins/title/README.md | 3 ++ .../unreadBadgeCount/README.md | 7 +++ src/equicordplugins/viewRawVariant/README.md | 3 ++ src/equicordplugins/voiceChatUtils/README.md | 5 ++ src/equicordplugins/wallpaperFree/README.md | 7 +++ src/equicordplugins/webpackTarball/README.md | 5 ++ .../accountPanelServerProfile/README.md | 7 +++ src/plugins/alwaysExpandRoles/README.md | 3 ++ src/plugins/appleMusic.desktop/README.md | 9 ++++ src/plugins/betterFolders/README.md | 11 +++++ src/plugins/betterRoleContext/README.md | 6 +++ src/plugins/betterSessions/README.md | 5 ++ src/plugins/betterSettings/README.md | 9 ++++ src/plugins/clientTheme/README.md | 7 +++ src/plugins/consoleJanitor/README.md | 5 ++ src/plugins/copyEmojiMarkdown/README.md | 5 ++ src/plugins/copyFileContents/README.md | 5 ++ src/plugins/copyStickerLinks/README.md | 5 ++ src/plugins/customIdle/README.md | 5 ++ src/plugins/dearrow/README.md | 5 ++ src/plugins/decor/README.md | 17 +++++++ src/plugins/favEmojiFirst/README.md | 6 +++ src/plugins/favGifSearch/README.md | 5 ++ src/plugins/fixImagesQuality/README.md | 3 ++ .../fixYoutubeEmbeds.desktop/README.md | 5 ++ src/plugins/friendsSince/README.md | 5 ++ src/plugins/fullSearchContext/README.md | 5 ++ src/plugins/ignoreActivities/README.md | 13 +++++ src/plugins/imageLink/README.md | 3 ++ src/plugins/imageZoom/README.md | 6 +++ src/plugins/implicitRelationships/README.md | 7 +++ src/plugins/mentionAvatars/README.md | 6 +++ src/plugins/messageClickActions/README.md | 5 ++ src/plugins/messageLatency/README.md | 31 ++++++++++++ src/plugins/noMaskedUrlPaste/README.md | 3 ++ src/plugins/noUnblockToJump/README.md | 5 ++ src/plugins/notificationVolume/README.md | 3 ++ .../{index.tsx => index.ts} | 0 src/plugins/onePingPerDM/README.md | 7 +++ src/plugins/openInApp/README.md | 11 +++++ src/plugins/pauseInvitesForever/README.md | 5 ++ src/plugins/permissionFreeWill/README.md | 9 ++++ src/plugins/previewMessage/README.md | 6 +++ src/plugins/quickMention/README.md | 5 ++ src/plugins/quickReply/README.md | 6 +++ src/plugins/replaceGoogleSearch/README.md | 5 ++ src/plugins/replyTimestamp/README.md | 5 ++ src/plugins/seeSummaries/README.md | 9 ++++ src/plugins/serverInfo/README.md | 7 +++ src/plugins/showHiddenThings/README.md | 19 +++++++ src/plugins/showTimeoutDuration/README.md | 8 +++ src/plugins/stickerPaste/README.md | 3 ++ src/plugins/superReactionTweaks/README.md | 11 +++++ src/plugins/themeAttributes/README.md | 29 +++++++++++ src/plugins/unlockedAvatarZoom/README.md | 5 ++ src/plugins/userMessagesPronouns/README.md | 5 ++ src/plugins/userVoiceShow/README.md | 7 +++ src/plugins/validReply/README.md | 7 +++ src/plugins/volumeBooster/README.md | 9 ++++ src/plugins/whoReacted/README.md | 5 ++ src/plugins/xsOverlay/README.md | 14 ++++++ src/plugins/youtubeAdblock.desktop/README.md | 7 +++ 135 files changed, 771 insertions(+), 10 deletions(-) rename src/equicordplugins/{ABSRPC => absRPC}/index.tsx (100%) create mode 100644 src/equicordplugins/allCallTimers/README.md create mode 100644 src/equicordplugins/altKrispSwitch/README.md create mode 100644 src/equicordplugins/alwaysExpandProfiles/README.md create mode 100644 src/equicordplugins/amITyping/README.md create mode 100644 src/equicordplugins/anammox/README.md create mode 100644 src/equicordplugins/atSomeone/README.md create mode 100644 src/equicordplugins/autoJump/README.md create mode 100644 src/equicordplugins/bannersEverywhere/README.md create mode 100644 src/equicordplugins/betterActivities/README.md create mode 100644 src/equicordplugins/betterAudioPlayer/README.md create mode 100644 src/equicordplugins/betterBanReasons/README.md create mode 100644 src/equicordplugins/betterBlockedUsers/README.md create mode 100644 src/equicordplugins/betterCommands/README.md create mode 100644 src/equicordplugins/betterInvites/README.md create mode 100644 src/equicordplugins/betterPlusReacts/README.md rename src/equicordplugins/{betterplusreacts => betterPlusReacts}/index.ts (96%) create mode 100644 src/equicordplugins/blockKrisp/README.md create mode 100644 src/equicordplugins/bypassPinPrompt/README.md create mode 100644 src/equicordplugins/bypassStatus/README.md create mode 100644 src/equicordplugins/channelBadges/README.md create mode 100644 src/equicordplugins/channelTabs/README.md create mode 100644 src/equicordplugins/characterCounter/README.md create mode 100644 src/equicordplugins/cleanChannelName/README.md create mode 100644 src/equicordplugins/clientSideBlock/README.md create mode 100644 src/equicordplugins/clipsEnhancements.discordDesktop/README.md create mode 100644 src/equicordplugins/commandPalette/README.md create mode 100644 src/equicordplugins/commandPalette/doc.md create mode 100644 src/equicordplugins/contentWarning/README.md create mode 100644 src/equicordplugins/copyProfileColors/README.md create mode 100644 src/equicordplugins/copyStatusUrls/README.md create mode 100644 src/equicordplugins/copyUserMention/README.md create mode 100644 src/equicordplugins/customFolderIcons/README.md create mode 100644 src/equicordplugins/customSounds/README.md create mode 100644 src/equicordplugins/customTimestamps/README.md create mode 100644 src/equicordplugins/customUserColors/README.md create mode 100644 src/equicordplugins/cuteAnimeBoys/README.md create mode 100644 src/equicordplugins/cuteNekos/README.md create mode 100644 src/equicordplugins/cutePats/README.md create mode 100644 src/equicordplugins/demonstration/README.md create mode 100644 src/equicordplugins/disableCameras/README.md create mode 100644 src/equicordplugins/discordDevBanner/README.md create mode 100644 src/equicordplugins/dontFilterMe/README.md create mode 100644 src/equicordplugins/downloadify/README.md create mode 100644 src/equicordplugins/equicordHelper/README.md create mode 100644 src/equicordplugins/equicordToolbox/README.md create mode 100644 src/equicordplugins/equissant/README.md create mode 100644 src/equicordplugins/exportContacts/README.md create mode 100644 src/equicordplugins/exportMessages/README.md create mode 100644 src/equicordplugins/fakeProfileThemesAndEffects/README.md create mode 100644 src/equicordplugins/fastDeleteChannels/README.md create mode 100644 src/equicordplugins/findReply/README.md create mode 100644 src/equicordplugins/fixFileExtensions/README.MD create mode 100644 src/equicordplugins/followVoiceUser/README.md create mode 100644 src/equicordplugins/fontLoader/README.md create mode 100644 src/equicordplugins/forwardAnywhere/README.md create mode 100644 src/equicordplugins/freaky/README.md create mode 100644 src/equicordplugins/frequentQuickSwitcher/README.md create mode 100644 src/equicordplugins/friendCloud/README.md create mode 100644 src/equicordplugins/friendCodes/README.md create mode 100644 src/equicordplugins/friendTags/README.md create mode 100644 src/equicordplugins/friendshipRanks/README.md create mode 100644 src/equicordplugins/fullVcPfp/README.md create mode 100644 src/equicordplugins/ignoreTerms/README.md create mode 100644 src/equicordplugins/messageLinkTooltip/README.md create mode 100644 src/equicordplugins/notificationTitle.discordDesktop/README.md rename src/equicordplugins/{notificationTitle => notificationTitle.discordDesktop}/index.tsx (100%) create mode 100644 src/equicordplugins/sidebarChat/README.md create mode 100644 src/equicordplugins/statusWhilePlaying.desktop/README.md create mode 100644 src/equicordplugins/teX/README.md create mode 100644 src/equicordplugins/timelessClips.desktop/README.md create mode 100644 src/equicordplugins/title/README.md create mode 100644 src/equicordplugins/unreadBadgeCount/README.md create mode 100644 src/equicordplugins/viewRawVariant/README.md create mode 100644 src/equicordplugins/voiceChatUtils/README.md create mode 100644 src/equicordplugins/wallpaperFree/README.md create mode 100644 src/equicordplugins/webpackTarball/README.md create mode 100644 src/plugins/accountPanelServerProfile/README.md create mode 100644 src/plugins/alwaysExpandRoles/README.md create mode 100644 src/plugins/appleMusic.desktop/README.md create mode 100644 src/plugins/betterFolders/README.md create mode 100644 src/plugins/betterRoleContext/README.md create mode 100644 src/plugins/betterSessions/README.md create mode 100644 src/plugins/betterSettings/README.md create mode 100644 src/plugins/clientTheme/README.md create mode 100644 src/plugins/consoleJanitor/README.md create mode 100644 src/plugins/copyEmojiMarkdown/README.md create mode 100644 src/plugins/copyFileContents/README.md create mode 100644 src/plugins/copyStickerLinks/README.md create mode 100644 src/plugins/customIdle/README.md create mode 100644 src/plugins/dearrow/README.md create mode 100644 src/plugins/decor/README.md create mode 100644 src/plugins/favEmojiFirst/README.md create mode 100644 src/plugins/favGifSearch/README.md create mode 100644 src/plugins/fixImagesQuality/README.md create mode 100644 src/plugins/fixYoutubeEmbeds.desktop/README.md create mode 100644 src/plugins/friendsSince/README.md create mode 100644 src/plugins/fullSearchContext/README.md create mode 100644 src/plugins/ignoreActivities/README.md create mode 100644 src/plugins/imageLink/README.md create mode 100644 src/plugins/imageZoom/README.md create mode 100644 src/plugins/implicitRelationships/README.md create mode 100644 src/plugins/mentionAvatars/README.md create mode 100644 src/plugins/messageClickActions/README.md create mode 100644 src/plugins/messageLatency/README.md create mode 100644 src/plugins/noMaskedUrlPaste/README.md create mode 100644 src/plugins/noUnblockToJump/README.md create mode 100644 src/plugins/notificationVolume/README.md rename src/plugins/notificationVolume/{index.tsx => index.ts} (100%) create mode 100644 src/plugins/onePingPerDM/README.md create mode 100644 src/plugins/openInApp/README.md create mode 100644 src/plugins/pauseInvitesForever/README.md create mode 100644 src/plugins/permissionFreeWill/README.md create mode 100644 src/plugins/previewMessage/README.md create mode 100644 src/plugins/quickMention/README.md create mode 100644 src/plugins/quickReply/README.md create mode 100644 src/plugins/replaceGoogleSearch/README.md create mode 100644 src/plugins/replyTimestamp/README.md create mode 100644 src/plugins/seeSummaries/README.md create mode 100644 src/plugins/serverInfo/README.md create mode 100644 src/plugins/showHiddenThings/README.md create mode 100644 src/plugins/showTimeoutDuration/README.md create mode 100644 src/plugins/stickerPaste/README.md create mode 100644 src/plugins/superReactionTweaks/README.md create mode 100644 src/plugins/themeAttributes/README.md create mode 100644 src/plugins/unlockedAvatarZoom/README.md create mode 100644 src/plugins/userMessagesPronouns/README.md create mode 100644 src/plugins/userVoiceShow/README.md create mode 100644 src/plugins/validReply/README.md create mode 100644 src/plugins/volumeBooster/README.md create mode 100644 src/plugins/whoReacted/README.md create mode 100644 src/plugins/xsOverlay/README.md create mode 100644 src/plugins/youtubeAdblock.desktop/README.md diff --git a/src/equicordplugins/ABSRPC/index.tsx b/src/equicordplugins/absRPC/index.tsx similarity index 100% rename from src/equicordplugins/ABSRPC/index.tsx rename to src/equicordplugins/absRPC/index.tsx diff --git a/src/equicordplugins/allCallTimers/README.md b/src/equicordplugins/allCallTimers/README.md new file mode 100644 index 000000000..3526200cf --- /dev/null +++ b/src/equicordplugins/allCallTimers/README.md @@ -0,0 +1,12 @@ +# AllCallTimers + +Adds a timer to each user in a guild voice call. + +Original author: [AllCallTimersDiscordPlugin](https://github.com/Max-Herbold/AllCallTimersDiscordPlugin) + +image +image +
+image +image +
diff --git a/src/equicordplugins/altKrispSwitch/README.md b/src/equicordplugins/altKrispSwitch/README.md new file mode 100644 index 000000000..04d0c3ccd --- /dev/null +++ b/src/equicordplugins/altKrispSwitch/README.md @@ -0,0 +1,3 @@ +# AltKrispSwitch + +Swaps between None and Krisp instead of Krisp and Standard diff --git a/src/equicordplugins/alwaysExpandProfiles/README.md b/src/equicordplugins/alwaysExpandProfiles/README.md new file mode 100644 index 000000000..e5e63c2c9 --- /dev/null +++ b/src/equicordplugins/alwaysExpandProfiles/README.md @@ -0,0 +1,3 @@ +# AlwaysExpandProfiles + +Always expands a user or bots profile skipping the popout diff --git a/src/equicordplugins/amITyping/README.md b/src/equicordplugins/amITyping/README.md new file mode 100644 index 000000000..3f157dbee --- /dev/null +++ b/src/equicordplugins/amITyping/README.md @@ -0,0 +1,3 @@ +# AmITyping + +Simply shows if you are typing. This is almost exactly what it will look like for other users, so it may show after you have finished typing. diff --git a/src/equicordplugins/anammox/README.md b/src/equicordplugins/anammox/README.md new file mode 100644 index 000000000..568b91021 --- /dev/null +++ b/src/equicordplugins/anammox/README.md @@ -0,0 +1,9 @@ +# Anammox + +Removes Nitro-exclusive stuff from various places: + +- The shops above private chats +- The gift button in the chat box +- The "billing" section in settings + +While this can be done with css, a plugin is able to do it much more thorougly β€” for example, the shops are still accessible via keyboard navigation. diff --git a/src/equicordplugins/atSomeone/README.md b/src/equicordplugins/atSomeone/README.md new file mode 100644 index 000000000..fe035ef79 --- /dev/null +++ b/src/equicordplugins/atSomeone/README.md @@ -0,0 +1,10 @@ +# AtSomeone + +Mention someone randomly + +Due to some limitations, it won't show on autocomplete in dms/groups but it still works. + +Do `@role*` to mention a random person with that role. + +![image](https://cdn.nest.rip/uploads/20743dd7-0484-4f86-a7ed-0d56229ce778.png) +![image](https://cdn.nest.rip/uploads/a96afd16-8dc9-429a-8d63-f915fc28bae0.png) diff --git a/src/equicordplugins/autoJump/README.md b/src/equicordplugins/autoJump/README.md new file mode 100644 index 000000000..e361254ca --- /dev/null +++ b/src/equicordplugins/autoJump/README.md @@ -0,0 +1,3 @@ +# AutoJump + +Jumps to the first message in new messages for a channel diff --git a/src/equicordplugins/bannersEverywhere/README.md b/src/equicordplugins/bannersEverywhere/README.md new file mode 100644 index 000000000..c0a8fbdda --- /dev/null +++ b/src/equicordplugins/bannersEverywhere/README.md @@ -0,0 +1,5 @@ +# BannersEverywhere + +Displays nitro and USRBG banners as a background in the member list + +![](https://cdn.nest.rip/uploads/450f3161-ad26-4965-8e69-33104a2e7cae.png) diff --git a/src/equicordplugins/betterActivities/README.md b/src/equicordplugins/betterActivities/README.md new file mode 100644 index 000000000..b328c6557 --- /dev/null +++ b/src/equicordplugins/betterActivities/README.md @@ -0,0 +1,7 @@ +# BetterActivities + +Shows activity icons in the member list and allows showing all activities + +![Shows activity icons next to the status line in the member list](https://cdn.nest.rip/uploads/2ebf9c64-fb58-4e6c-a916-630db88be8ca.png) +![Shows all activities in the profile via a carousel](https://cdn.nest.rip/uploads/1f33b612-3b36-4aba-961d-0e94a7b31757.png) +![Optionally show all activities as list](https://cdn.nest.rip/uploads/acd042b7-2a57-4875-b7d4-9c558bfb9a0c.png) diff --git a/src/equicordplugins/betterAudioPlayer/README.md b/src/equicordplugins/betterAudioPlayer/README.md new file mode 100644 index 000000000..593a7713c --- /dev/null +++ b/src/equicordplugins/betterAudioPlayer/README.md @@ -0,0 +1,3 @@ +# BetterAudioPlayer + +Adds visualizers to audio attachments diff --git a/src/equicordplugins/betterBanReasons/README.md b/src/equicordplugins/betterBanReasons/README.md new file mode 100644 index 000000000..f5fb10a17 --- /dev/null +++ b/src/equicordplugins/betterBanReasons/README.md @@ -0,0 +1,7 @@ +# BetterBanReasons + +* Create custom reasons to use in the Discord ban modal. +* Show a text input by default instead of the options + +Settings screenshot +Ban modal Screenshot diff --git a/src/equicordplugins/betterBlockedUsers/README.md b/src/equicordplugins/betterBlockedUsers/README.md new file mode 100644 index 000000000..2b580938e --- /dev/null +++ b/src/equicordplugins/betterBlockedUsers/README.md @@ -0,0 +1,3 @@ +# BetterBlockedUsers + +Adds searching and opening the profile of a blocked user diff --git a/src/equicordplugins/betterCommands/README.md b/src/equicordplugins/betterCommands/README.md new file mode 100644 index 000000000..c785ab060 --- /dev/null +++ b/src/equicordplugins/betterCommands/README.md @@ -0,0 +1,3 @@ +# BetterCommands + +Adds/Fixes various things about commands diff --git a/src/equicordplugins/betterInvites/README.md b/src/equicordplugins/betterInvites/README.md new file mode 100644 index 000000000..037a3cff2 --- /dev/null +++ b/src/equicordplugins/betterInvites/README.md @@ -0,0 +1,11 @@ +# BetterInvites + +![Inviter preview](https://cdn.nest.rip/uploads/0438e1e9-405f-479b-a6ec-ed36224c5b28.png) + +![Receiver preview](https://cdn.nest.rip/uploads/0c454567-31ea-48da-9684-adb619bc2098.png) + +# features + +- be able to see the inviter and inviter profile +- preview discoverable servers by clicking the server name +- be able to see the invite relative expiration date diff --git a/src/equicordplugins/betterPlusReacts/README.md b/src/equicordplugins/betterPlusReacts/README.md new file mode 100644 index 000000000..1772d89ab --- /dev/null +++ b/src/equicordplugins/betterPlusReacts/README.md @@ -0,0 +1,3 @@ +# BetterPlusReacts + + diff --git a/src/equicordplugins/betterplusreacts/index.ts b/src/equicordplugins/betterPlusReacts/index.ts similarity index 96% rename from src/equicordplugins/betterplusreacts/index.ts rename to src/equicordplugins/betterPlusReacts/index.ts index a590b0263..2e3f84d2b 100644 --- a/src/equicordplugins/betterplusreacts/index.ts +++ b/src/equicordplugins/betterPlusReacts/index.ts @@ -4,7 +4,6 @@ * SPDX-License-Identifier: GPL-3.0-or-later */ -// https://github.com/Masterjoona/vc-betterplusreacts/blob/main/index.ts import { Devs } from "@utils/constants"; import definePlugin from "@utils/types"; import { MessageStore } from "@webpack/common"; diff --git a/src/equicordplugins/blockKrisp/README.md b/src/equicordplugins/blockKrisp/README.md new file mode 100644 index 000000000..fab38364b --- /dev/null +++ b/src/equicordplugins/blockKrisp/README.md @@ -0,0 +1,5 @@ +# BlockKrisp + +Prevent Krisp from loading + +![Screenshot](https://cdn.nest.rip/uploads/8e08689e-992f-4c48-9cdf-9166304182a5.png) diff --git a/src/equicordplugins/bypassPinPrompt/README.md b/src/equicordplugins/bypassPinPrompt/README.md new file mode 100644 index 000000000..8543fcb01 --- /dev/null +++ b/src/equicordplugins/bypassPinPrompt/README.md @@ -0,0 +1,3 @@ +# BypassPinPrompt + +Bypasses the prompt for pinning messages diff --git a/src/equicordplugins/bypassStatus/README.md b/src/equicordplugins/bypassStatus/README.md new file mode 100644 index 000000000..992401e93 --- /dev/null +++ b/src/equicordplugins/bypassStatus/README.md @@ -0,0 +1,3 @@ +# BypassStatus + +Bypass Status allows you to set specific guilds/channels to still receive notifications and calls from when in a set status. diff --git a/src/equicordplugins/channelBadges/README.md b/src/equicordplugins/channelBadges/README.md new file mode 100644 index 000000000..7f61fd971 --- /dev/null +++ b/src/equicordplugins/channelBadges/README.md @@ -0,0 +1,3 @@ +# ChannelBadges + +Badges for channels based on their type such as text, voice, forum, etc. diff --git a/src/equicordplugins/channelTabs/README.md b/src/equicordplugins/channelTabs/README.md new file mode 100644 index 000000000..d536eee7f --- /dev/null +++ b/src/equicordplugins/channelTabs/README.md @@ -0,0 +1,7 @@ +# ChannelTabs + +Group your commonly visited channels in tabs, like a browser + +![screenshot of the plugin showing how the tabs look, as well as notification dots, typing indicators and a compact tab](https://cdn.nest.rip/uploads/d4435b4b-dbe4-4378-b984-923fba99b79f.png) +![bookmark bar with folders and various channel types](https://cdn.nest.rip/uploads/b2a1b5be-463a-4961-a654-3fb1c8803164.png) +![used together with a custom theme, tabs and bookmarks being dragged around](https://cdn.nest.rip/uploads/09d5af8b-5049-45d1-806c-1a5f702a4647.gif) diff --git a/src/equicordplugins/characterCounter/README.md b/src/equicordplugins/characterCounter/README.md new file mode 100644 index 000000000..2efee28d5 --- /dev/null +++ b/src/equicordplugins/characterCounter/README.md @@ -0,0 +1,3 @@ +# CharacterCounter + +Character counter for chat input diff --git a/src/equicordplugins/cleanChannelName/README.md b/src/equicordplugins/cleanChannelName/README.md new file mode 100644 index 000000000..3211ca4dd --- /dev/null +++ b/src/equicordplugins/cleanChannelName/README.md @@ -0,0 +1,3 @@ +# CleanChannelName + +Cleans emojis and other things out of channel names diff --git a/src/equicordplugins/clientSideBlock/README.md b/src/equicordplugins/clientSideBlock/README.md new file mode 100644 index 000000000..23d9f69a9 --- /dev/null +++ b/src/equicordplugins/clientSideBlock/README.md @@ -0,0 +1,3 @@ +# ClientSideBlock + +Blocks a user client side so they don't know they're blocked diff --git a/src/equicordplugins/clientSideBlock/index.tsx b/src/equicordplugins/clientSideBlock/index.tsx index 91053f293..52ea80223 100644 --- a/src/equicordplugins/clientSideBlock/index.tsx +++ b/src/equicordplugins/clientSideBlock/index.tsx @@ -36,14 +36,6 @@ const settings = definePluginSettings( restartNeeded: true, default: true }, - /* - hideNewUsers: { - type: OptionType.BOOLEAN, - description: "Should content from users with the \"I'm new here, say hi!\" badge be blocked\"", - restartNeeded: true, - default: true - }, - */ blockedReplyDisplay: { type: OptionType.SELECT, description: "What should display instead of the message when someone replies to someone you have hidden", diff --git a/src/equicordplugins/clipsEnhancements.discordDesktop/README.md b/src/equicordplugins/clipsEnhancements.discordDesktop/README.md new file mode 100644 index 000000000..591a84284 --- /dev/null +++ b/src/equicordplugins/clipsEnhancements.discordDesktop/README.md @@ -0,0 +1,3 @@ +# ClipsEnhancements + +Add more Clip FPS and duration options, plus RPC tagging! diff --git a/src/equicordplugins/commandPalette/README.md b/src/equicordplugins/commandPalette/README.md new file mode 100644 index 000000000..81abfe714 --- /dev/null +++ b/src/equicordplugins/commandPalette/README.md @@ -0,0 +1,3 @@ +# CommandPalette + +Navigating with a keyboard diff --git a/src/equicordplugins/commandPalette/doc.md b/src/equicordplugins/commandPalette/doc.md new file mode 100644 index 000000000..014b92832 --- /dev/null +++ b/src/equicordplugins/commandPalette/doc.md @@ -0,0 +1,49 @@ +# Command Palette Actions Guide + +Welcome to the Command Palette Actions Guide! This guide serves to inform you on how to implement your own actions to the CommandPalette plugin. To do so you have two options: hardcoding or utilizing the `registerAction` function. This guide will focus on using the function as its best practice and is what should be used in most situations. + +> [!IMPORTANT] +> While both methods hardcoding and using `registerAction` offer similar implementations, it's recommended to refrain from Hardcoding unless you solely plan on using this locally. + +### Implementing Multiple Choice Modals + +```ts +registerAction({ + id: 'multipleChoiceCommand', + label: 'Multiple Choice', + callback: async () => { + // Open a modal with multiple choices + const choice = await openMultipleChoice([ + { id: 'test1', label: 'Test 1' }, + { id: 'test2', label: 'Test 2' }, + ]); + + // Log the selected choice with its label and ID + console.log(`Selected ${choice.label} with the ID ${choice.id}`); + }, +}); +``` + +- **ID**: A unique identifier for the command/action, ensuring uniqueness across specific options in that modal instance. +- **Label**: The text displayed in the command palette for user recognition. +- **Callback (optional)**: The function executed when the command is triggered. + +Inside the callback, the `openMultipleChoice` function opens a modal with a list of choices. Users can select an option, and upon choosing, a `ButtonAction` type is returned. The user's choice is then logged for reference. + +### Implementing String Input Modals + +```ts +registerAction({ + id: 'stringInputCommand', + label: 'String Input', + callback: async () => { + // Open a modal with a text input + const text = await openSimpleTextInput(); + + // Log the inputted text to console + console.log(`They typed: ${text}`); + }, +}); +``` + +When the `stringInputCommand` is triggered, a modal with a simple text input field appears. Users can input text, and the entered string is returned. In this case, we log their input to console. diff --git a/src/equicordplugins/contentWarning/README.md b/src/equicordplugins/contentWarning/README.md new file mode 100644 index 000000000..156381b42 --- /dev/null +++ b/src/equicordplugins/contentWarning/README.md @@ -0,0 +1,3 @@ +# ContentWarning + +Specify certain words to blur diff --git a/src/equicordplugins/contentWarning/index.tsx b/src/equicordplugins/contentWarning/index.tsx index cf753d783..b1f21118e 100644 --- a/src/equicordplugins/contentWarning/index.tsx +++ b/src/equicordplugins/contentWarning/index.tsx @@ -133,7 +133,7 @@ const settings = definePluginSettings({ export default definePlugin({ name: "ContentWarning", authors: [EquicordDevs.camila314], - description: "Allows you to specify certain trigger words that will be blurred by default. Clicking on the blurred content will reveal it.", + description: "Allows you to specify certain trigger words that will be blurred by default. Hovering on the blurred content will reveal it.", settings, patches: [ { diff --git a/src/equicordplugins/copyProfileColors/README.md b/src/equicordplugins/copyProfileColors/README.md new file mode 100644 index 000000000..c098a1ed4 --- /dev/null +++ b/src/equicordplugins/copyProfileColors/README.md @@ -0,0 +1,3 @@ +# CopyProfileColors + +Allows copying profile colors diff --git a/src/equicordplugins/copyStatusUrls/README.md b/src/equicordplugins/copyStatusUrls/README.md new file mode 100644 index 000000000..102e03086 --- /dev/null +++ b/src/equicordplugins/copyStatusUrls/README.md @@ -0,0 +1,3 @@ +# CopyStatusUrls + +Allows copying status urls diff --git a/src/equicordplugins/copyUserMention/README.md b/src/equicordplugins/copyUserMention/README.md new file mode 100644 index 000000000..3b458fc85 --- /dev/null +++ b/src/equicordplugins/copyUserMention/README.md @@ -0,0 +1,3 @@ +# CopyUserMention + +Allows copying a users mention diff --git a/src/equicordplugins/customFolderIcons/README.md b/src/equicordplugins/customFolderIcons/README.md new file mode 100644 index 000000000..043226ac2 --- /dev/null +++ b/src/equicordplugins/customFolderIcons/README.md @@ -0,0 +1,11 @@ +# CustomFolderIcons + +Allows you to set custom images/svgs as folder icons + +Available as "Change Icon" in the folder context menu + +![the context menu with the "Change Icon" button](https://cdn.nest.rip/uploads/17256db9-8b5c-40a2-acdc-b37449eb9c0f.png) + +![an image of some example custom folder icons](https://cdn.nest.rip/uploads/109163ab-384d-4638-a035-f8743248147f.png) + +![the modal used to customize the folder icons](https://cdn.nest.rip/uploads/594d0792-0961-4766-bca8-74ae56e914fd.png) diff --git a/src/equicordplugins/customSounds/README.md b/src/equicordplugins/customSounds/README.md new file mode 100644 index 000000000..f3b502b68 --- /dev/null +++ b/src/equicordplugins/customSounds/README.md @@ -0,0 +1,3 @@ +# CustomSounds + +Allows customizing discords sounds diff --git a/src/equicordplugins/customTimestamps/README.md b/src/equicordplugins/customTimestamps/README.md new file mode 100644 index 000000000..ce9029eb3 --- /dev/null +++ b/src/equicordplugins/customTimestamps/README.md @@ -0,0 +1,3 @@ +# CustomTimestamps + +Allows custom timestamp formatting diff --git a/src/equicordplugins/customUserColors/README.md b/src/equicordplugins/customUserColors/README.md new file mode 100644 index 000000000..ec9a78515 --- /dev/null +++ b/src/equicordplugins/customUserColors/README.md @@ -0,0 +1,3 @@ +# CustomUserColors + +Like IrcColors except allows setting custom user colors diff --git a/src/equicordplugins/cuteAnimeBoys/README.md b/src/equicordplugins/cuteAnimeBoys/README.md new file mode 100644 index 000000000..68558f80f --- /dev/null +++ b/src/equicordplugins/cuteAnimeBoys/README.md @@ -0,0 +1,3 @@ +# CuteAnimeBoys + +Adds the cuteanimeboys command diff --git a/src/equicordplugins/cuteNekos/README.md b/src/equicordplugins/cuteNekos/README.md new file mode 100644 index 000000000..92e2d3b87 --- /dev/null +++ b/src/equicordplugins/cuteNekos/README.md @@ -0,0 +1,3 @@ +# CuteNekos + +Adds the nekos command diff --git a/src/equicordplugins/cutePats/README.md b/src/equicordplugins/cutePats/README.md new file mode 100644 index 000000000..f05bc65b3 --- /dev/null +++ b/src/equicordplugins/cutePats/README.md @@ -0,0 +1,3 @@ +# CutePats + +Adds the pat command diff --git a/src/equicordplugins/demonstration/README.md b/src/equicordplugins/demonstration/README.md new file mode 100644 index 000000000..431fcf8ab --- /dev/null +++ b/src/equicordplugins/demonstration/README.md @@ -0,0 +1,3 @@ +# Demonstration + +Helps for taking theme screenshots diff --git a/src/equicordplugins/disableCameras/README.md b/src/equicordplugins/disableCameras/README.md new file mode 100644 index 000000000..06d8bc53d --- /dev/null +++ b/src/equicordplugins/disableCameras/README.md @@ -0,0 +1,3 @@ +# DisableCameras + +Disables cameras by default when in a voice call diff --git a/src/equicordplugins/discordDevBanner/README.md b/src/equicordplugins/discordDevBanner/README.md new file mode 100644 index 000000000..4661627a4 --- /dev/null +++ b/src/equicordplugins/discordDevBanner/README.md @@ -0,0 +1,3 @@ +# DiscordDevBanner + +Enables developer banner and adds custom options to display in it diff --git a/src/equicordplugins/dontFilterMe/README.md b/src/equicordplugins/dontFilterMe/README.md new file mode 100644 index 000000000..abf74daf0 --- /dev/null +++ b/src/equicordplugins/dontFilterMe/README.md @@ -0,0 +1,3 @@ +# DontFilterMe + +Warns you if automod might flag your message diff --git a/src/equicordplugins/downloadify/README.md b/src/equicordplugins/downloadify/README.md new file mode 100644 index 000000000..e07224448 --- /dev/null +++ b/src/equicordplugins/downloadify/README.md @@ -0,0 +1,3 @@ +# Downloadify + +Improved downloading for various assets diff --git a/src/equicordplugins/equicordHelper/README.md b/src/equicordplugins/equicordHelper/README.md new file mode 100644 index 000000000..9c1dcab8a --- /dev/null +++ b/src/equicordplugins/equicordHelper/README.md @@ -0,0 +1,3 @@ +# EquicordHelper + +Fixes various issues for crashing and adds other items that might help with support diff --git a/src/equicordplugins/equicordToolbox/README.md b/src/equicordplugins/equicordToolbox/README.md new file mode 100644 index 000000000..1729ba0be --- /dev/null +++ b/src/equicordplugins/equicordToolbox/README.md @@ -0,0 +1,3 @@ +# EquicordToolbox + +Adds a toolbox for various control options diff --git a/src/equicordplugins/equissant/README.md b/src/equicordplugins/equissant/README.md new file mode 100644 index 000000000..d6922399f --- /dev/null +++ b/src/equicordplugins/equissant/README.md @@ -0,0 +1,3 @@ +# Equissant + +Plays crossant every 10 clicks diff --git a/src/equicordplugins/exportContacts/README.md b/src/equicordplugins/exportContacts/README.md new file mode 100644 index 000000000..620b8a296 --- /dev/null +++ b/src/equicordplugins/exportContacts/README.md @@ -0,0 +1,3 @@ +# ExportContacts + +Exports your friends list onto your clipboard diff --git a/src/equicordplugins/exportMessages/README.md b/src/equicordplugins/exportMessages/README.md new file mode 100644 index 000000000..27f6f7ba0 --- /dev/null +++ b/src/equicordplugins/exportMessages/README.md @@ -0,0 +1,3 @@ +# ExportMessages + +Allows exporting a message to a file diff --git a/src/equicordplugins/fakeProfileThemesAndEffects/README.md b/src/equicordplugins/fakeProfileThemesAndEffects/README.md new file mode 100644 index 000000000..91ceedbea --- /dev/null +++ b/src/equicordplugins/fakeProfileThemesAndEffects/README.md @@ -0,0 +1,3 @@ +# FakeProfileThemesAndEffects + +Expanded FakeProfileThemes with effects diff --git a/src/equicordplugins/fastDeleteChannels/README.md b/src/equicordplugins/fastDeleteChannels/README.md new file mode 100644 index 000000000..260d3f074 --- /dev/null +++ b/src/equicordplugins/fastDeleteChannels/README.md @@ -0,0 +1,3 @@ +# FastDeleteChannels + +Adds a trash can icon when pressing specific keybinds to delete a channel/channels instantly diff --git a/src/equicordplugins/findReply/README.md b/src/equicordplugins/findReply/README.md new file mode 100644 index 000000000..d2b7dd18b --- /dev/null +++ b/src/equicordplugins/findReply/README.md @@ -0,0 +1,5 @@ +# FindReply + +Lets you jump to the replies to a message. Makes it easier to follow past conversations/Q&A's. + +![](https://cdn.nest.rip/uploads/1a49e773-acae-49aa-9444-f340b828cbba.gif) diff --git a/src/equicordplugins/fixFileExtensions/README.MD b/src/equicordplugins/fixFileExtensions/README.MD new file mode 100644 index 000000000..dd3148499 --- /dev/null +++ b/src/equicordplugins/fixFileExtensions/README.MD @@ -0,0 +1,3 @@ +# FixFileExtensions + +Fixes file extensions by renaming them to work on discord diff --git a/src/equicordplugins/followVoiceUser/README.md b/src/equicordplugins/followVoiceUser/README.md new file mode 100644 index 000000000..c6d298b78 --- /dev/null +++ b/src/equicordplugins/followVoiceUser/README.md @@ -0,0 +1,3 @@ +# FollowVoiceUser + +Allows following a friend through voice chats diff --git a/src/equicordplugins/fontLoader/README.md b/src/equicordplugins/fontLoader/README.md new file mode 100644 index 000000000..5fb04a62b --- /dev/null +++ b/src/equicordplugins/fontLoader/README.md @@ -0,0 +1,3 @@ +# FontLoader + +Allows loading and using different fonts from Google diff --git a/src/equicordplugins/forwardAnywhere/README.md b/src/equicordplugins/forwardAnywhere/README.md new file mode 100644 index 000000000..23bf9ca07 --- /dev/null +++ b/src/equicordplugins/forwardAnywhere/README.md @@ -0,0 +1,3 @@ +# ForwardAnywhere + +Allows forwarding a message anywhere diff --git a/src/equicordplugins/freaky/README.md b/src/equicordplugins/freaky/README.md new file mode 100644 index 000000000..7ea56617d --- /dev/null +++ b/src/equicordplugins/freaky/README.md @@ -0,0 +1,3 @@ +# Freaky + +Changes your text to look like this 𝓔𝓺𝓾𝓲𝓬𝓸𝓻𝓭 diff --git a/src/equicordplugins/frequentQuickSwitcher/README.md b/src/equicordplugins/frequentQuickSwitcher/README.md new file mode 100644 index 000000000..b463d4d47 --- /dev/null +++ b/src/equicordplugins/frequentQuickSwitcher/README.md @@ -0,0 +1,3 @@ +# FrequentQuickSwitcher + +Changes quick switcher to be your most frequent channels diff --git a/src/equicordplugins/friendCloud/README.md b/src/equicordplugins/friendCloud/README.md new file mode 100644 index 000000000..4e6b12fd7 --- /dev/null +++ b/src/equicordplugins/friendCloud/README.md @@ -0,0 +1,7 @@ +# FriendCloud + +Visualize the users you interact with most in a cloud! + +Simply run the `/friendcloud` command to generate a cloud of your friends based on your interactions with them. + +Use the `count` option to set the number of users to display. diff --git a/src/equicordplugins/friendCodes/README.md b/src/equicordplugins/friendCodes/README.md new file mode 100644 index 000000000..6a6ac36c6 --- /dev/null +++ b/src/equicordplugins/friendCodes/README.md @@ -0,0 +1,3 @@ +# FriendCodes + +Easily generate friend codes diff --git a/src/equicordplugins/friendTags/README.md b/src/equicordplugins/friendTags/README.md new file mode 100644 index 000000000..f2fa98cf4 --- /dev/null +++ b/src/equicordplugins/friendTags/README.md @@ -0,0 +1,3 @@ +# FriendTags + +Allows filtering friends in quick switcher diff --git a/src/equicordplugins/friendshipRanks/README.md b/src/equicordplugins/friendshipRanks/README.md new file mode 100644 index 000000000..0efc4b738 --- /dev/null +++ b/src/equicordplugins/friendshipRanks/README.md @@ -0,0 +1,3 @@ +# FriendshipRanks + +Adds badges to show how long you've been friends with a user diff --git a/src/equicordplugins/fullVcPfp/README.md b/src/equicordplugins/fullVcPfp/README.md new file mode 100644 index 000000000..d25ed8258 --- /dev/null +++ b/src/equicordplugins/fullVcPfp/README.md @@ -0,0 +1,3 @@ +# FullVCPFP + +Makes profile pictures show fully diff --git a/src/equicordplugins/ignoreTerms/README.md b/src/equicordplugins/ignoreTerms/README.md new file mode 100644 index 000000000..d2215771e --- /dev/null +++ b/src/equicordplugins/ignoreTerms/README.md @@ -0,0 +1,5 @@ +# IgnoreTerms + +Ignore Discord's new terms of service + +![Screenshot](https://cdn.nest.rip/uploads/aa0b1baf-6256-4c31-9bee-73cdaa24aed5.png) diff --git a/src/equicordplugins/messageLinkTooltip/README.md b/src/equicordplugins/messageLinkTooltip/README.md new file mode 100644 index 000000000..6fa4e038a --- /dev/null +++ b/src/equicordplugins/messageLinkTooltip/README.md @@ -0,0 +1,3 @@ +# MessageLinkTooltip + +MessageLinkEmbed but with space constrants diff --git a/src/equicordplugins/notificationTitle.discordDesktop/README.md b/src/equicordplugins/notificationTitle.discordDesktop/README.md new file mode 100644 index 000000000..a885e8c1a --- /dev/null +++ b/src/equicordplugins/notificationTitle.discordDesktop/README.md @@ -0,0 +1,3 @@ +# NotificationTitle + +Improved Desktop Notifications diff --git a/src/equicordplugins/notificationTitle/index.tsx b/src/equicordplugins/notificationTitle.discordDesktop/index.tsx similarity index 100% rename from src/equicordplugins/notificationTitle/index.tsx rename to src/equicordplugins/notificationTitle.discordDesktop/index.tsx diff --git a/src/equicordplugins/sidebarChat/README.md b/src/equicordplugins/sidebarChat/README.md new file mode 100644 index 000000000..3fdf2412b --- /dev/null +++ b/src/equicordplugins/sidebarChat/README.md @@ -0,0 +1,5 @@ +# SidebarChat + +View another channel, dm or group dm as a resizable sidebar. + +![vencord and aliucord support channels both open](https://cdn.nest.rip/uploads/bc92a8bd-fc25-4775-9744-360de3f1b872.png) diff --git a/src/equicordplugins/statusWhilePlaying.desktop/README.md b/src/equicordplugins/statusWhilePlaying.desktop/README.md new file mode 100644 index 000000000..5e3abf287 --- /dev/null +++ b/src/equicordplugins/statusWhilePlaying.desktop/README.md @@ -0,0 +1,5 @@ +# StatusWhilePlaying + +This plugin automatically updates your online status (online, idle, dnd) when launching games. + +It will change your status back to the prior status when the game is closed. diff --git a/src/equicordplugins/teX/README.md b/src/equicordplugins/teX/README.md new file mode 100644 index 000000000..128f5608d --- /dev/null +++ b/src/equicordplugins/teX/README.md @@ -0,0 +1,8 @@ +# TeX + +Renders TeX formulas. +`` `$\sin x$` `` renders as $\sin x$, +`` `$$x = \frac{-b \pm \sqrt{b^2 - 4ac}}{2a}$$` `` renders as $\displaystyle x = \frac{-b \pm \sqrt{b^2 - 4ac}}{2a}$. +The latter is intentionally set to an inline-block instead of a block, to save space. + +This plugin requires external libraries, which will not work in the userscript version of Vencord without additional workarounds. diff --git a/src/equicordplugins/timelessClips.desktop/README.md b/src/equicordplugins/timelessClips.desktop/README.md new file mode 100644 index 000000000..94a328f5b --- /dev/null +++ b/src/equicordplugins/timelessClips.desktop/README.md @@ -0,0 +1,3 @@ +# TimelessClips + +Custom clip length diff --git a/src/equicordplugins/title/README.md b/src/equicordplugins/title/README.md new file mode 100644 index 000000000..62e991345 --- /dev/null +++ b/src/equicordplugins/title/README.md @@ -0,0 +1,3 @@ +# Title + +In the browser version, the window/tab title is always prefixed with "Discord -". This plugin lets you change or remove this prefix. diff --git a/src/equicordplugins/unreadBadgeCount/README.md b/src/equicordplugins/unreadBadgeCount/README.md new file mode 100644 index 000000000..21e362f77 --- /dev/null +++ b/src/equicordplugins/unreadBadgeCount/README.md @@ -0,0 +1,7 @@ +# UnreadCountBadge + +Show a badge in the channel list for unread messages. + +you have to load the channel or get new messages in that channel for it show idk why maybe sometime ill figure it out + +![image](https://why-are-you.gay/HpPVuwSV) diff --git a/src/equicordplugins/viewRawVariant/README.md b/src/equicordplugins/viewRawVariant/README.md new file mode 100644 index 000000000..20d9b56fc --- /dev/null +++ b/src/equicordplugins/viewRawVariant/README.md @@ -0,0 +1,3 @@ +# ViewRawVariant + +Improved ViewRaw with more features and places diff --git a/src/equicordplugins/voiceChatUtils/README.md b/src/equicordplugins/voiceChatUtils/README.md new file mode 100644 index 000000000..7f0345312 --- /dev/null +++ b/src/equicordplugins/voiceChatUtils/README.md @@ -0,0 +1,5 @@ +# VoiceChatUtilities + +Allows you to perform multiple actions on an entire channel (move, mute, disconnect, etc.) + +![Screenshot](https://cdn.nest.rip/uploads/9d6d2b72-0346-4951-90f3-6cab963728eb.png) diff --git a/src/equicordplugins/wallpaperFree/README.md b/src/equicordplugins/wallpaperFree/README.md new file mode 100644 index 000000000..dcb59fbf2 --- /dev/null +++ b/src/equicordplugins/wallpaperFree/README.md @@ -0,0 +1,7 @@ +# WallpaperFree + +Recreation of the old DM wallpaper experiment; Set a background image for any channel, user or server. + + + +*ignore the Discord wallpaper picker it is removed* diff --git a/src/equicordplugins/webpackTarball/README.md b/src/equicordplugins/webpackTarball/README.md new file mode 100644 index 000000000..23c64c3ed --- /dev/null +++ b/src/equicordplugins/webpackTarball/README.md @@ -0,0 +1,5 @@ +# WebpackTarball + +Adds an option to the Vencord Toolbox that lets you grab Discord's webpack as a tarball, which is very convenient for inspecting the code. + +The reason it's a tarball instead of for example a zip is just simplicity β€” the tar file writer is only like 60ish lines. diff --git a/src/plugins/accountPanelServerProfile/README.md b/src/plugins/accountPanelServerProfile/README.md new file mode 100644 index 000000000..f837864b7 --- /dev/null +++ b/src/plugins/accountPanelServerProfile/README.md @@ -0,0 +1,7 @@ +# AccountPanelServerProfile + +Right click your account panel in the bottom left to view your profile in the current server + +![](https://github.com/user-attachments/assets/3228497d-488f-479c-93d2-a32ccdb08f0f) + +![](https://github.com/user-attachments/assets/6fc45363-d95f-4810-812f-2f9fb28b41b5) diff --git a/src/plugins/alwaysExpandRoles/README.md b/src/plugins/alwaysExpandRoles/README.md new file mode 100644 index 000000000..344268cbf --- /dev/null +++ b/src/plugins/alwaysExpandRoles/README.md @@ -0,0 +1,3 @@ +# Always Expand Roles + +Always expands the role list in profile popouts diff --git a/src/plugins/appleMusic.desktop/README.md b/src/plugins/appleMusic.desktop/README.md new file mode 100644 index 000000000..52ab93bfd --- /dev/null +++ b/src/plugins/appleMusic.desktop/README.md @@ -0,0 +1,9 @@ +# AppleMusicRichPresence + +This plugin enables Discord rich presence for your Apple Music! (This only works on macOS with the Music app.) + +![Screenshot of the activity in Discord](https://github.com/Vendicated/Vencord/assets/70191398/1f811090-ab5f-4060-a9ee-d0ac44a1d3c0) + +## Configuration + +For the customizable activity format strings, you can use several special strings to include track data in activities! `{name}` is replaced with the track name; `{artist}` is replaced with the artist(s)' name(s); and `{album}` is replaced with the album name. diff --git a/src/plugins/betterFolders/README.md b/src/plugins/betterFolders/README.md new file mode 100644 index 000000000..658c6f1db --- /dev/null +++ b/src/plugins/betterFolders/README.md @@ -0,0 +1,11 @@ +# Better Folders + +Better Folders offers a variety of options to improve your folder experience + +Always show the folder icon, regardless of if the folder is open or not + +Only have one folder open at a time + +Open folders in a sidebar: + +![A folder open in a separate sidebar](https://github.com/user-attachments/assets/432d3146-8091-4bae-9c1e-c19046c72947) diff --git a/src/plugins/betterRoleContext/README.md b/src/plugins/betterRoleContext/README.md new file mode 100644 index 000000000..e54e1e313 --- /dev/null +++ b/src/plugins/betterRoleContext/README.md @@ -0,0 +1,6 @@ +# BetterRoleContext + +Adds options to copy role color, edit role and view role icon when right clicking roles in the user profile + +![](https://github.com/Vendicated/Vencord/assets/45497981/354220a4-09f3-4c5f-a28e-4b19ca775190) + diff --git a/src/plugins/betterSessions/README.md b/src/plugins/betterSessions/README.md new file mode 100644 index 000000000..cf13e6c50 --- /dev/null +++ b/src/plugins/betterSessions/README.md @@ -0,0 +1,5 @@ +# BetterSessions + +Enhances the sessions (devices) menu. Allows you to view exact timestamps, give each session a custom name, and receive notifications about new sessions. + +![](https://github.com/Vendicated/Vencord/assets/9750071/4a44b617-bb8f-4dcb-93f1-b7d2575ed3d8) diff --git a/src/plugins/betterSettings/README.md b/src/plugins/betterSettings/README.md new file mode 100644 index 000000000..127c6ce76 --- /dev/null +++ b/src/plugins/betterSettings/README.md @@ -0,0 +1,9 @@ +# BetterSettings + +Improves Discord's Settings via multiple (toggleable) changes: +- makes opening settings much faster +- removes the scuffed transition animation +- organises the settings cog context menu into categories + +![](https://github.com/Vendicated/Vencord/assets/45497981/e8d67a95-3909-4be5-8281-8cf9d2f1c30e) + diff --git a/src/plugins/clientTheme/README.md b/src/plugins/clientTheme/README.md new file mode 100644 index 000000000..e2723f8bc --- /dev/null +++ b/src/plugins/clientTheme/README.md @@ -0,0 +1,7 @@ +# Client Theme + +Revival of the old client theme experiment (The one that came before the sucky one that we actually got) + +![the ClientTheme theme colour picker](https://user-images.githubusercontent.com/37855219/230238053-e90b7098-373a-459a-bb8c-c24e82f69270.png) + +https://github.com/Vendicated/Vencord/assets/45497981/6c1bcb3b-e0c7-4a02-b0b8-c4c5cd954f38 diff --git a/src/plugins/consoleJanitor/README.md b/src/plugins/consoleJanitor/README.md new file mode 100644 index 000000000..9737f53d1 --- /dev/null +++ b/src/plugins/consoleJanitor/README.md @@ -0,0 +1,5 @@ +# ConsoleJanitor + +Disables annoying console messages/errors. This plugin mainly removes errors/warnings that happen all the time and Discord logger messages. + +One of the disabled messages is the "Window state not initialized" warning, for example. diff --git a/src/plugins/copyEmojiMarkdown/README.md b/src/plugins/copyEmojiMarkdown/README.md new file mode 100644 index 000000000..9e62e6635 --- /dev/null +++ b/src/plugins/copyEmojiMarkdown/README.md @@ -0,0 +1,5 @@ +# CopyEmojiMarkdown + +Allows you to copy emojis as formatted string. Custom emojis will be copied as `<:trolley:1024751352028602449>`, default emojis as `πŸ›’` + +![](https://github.com/Vendicated/Vencord/assets/45497981/417f345a-7031-4fe7-8e42-e238870cd547) diff --git a/src/plugins/copyFileContents/README.md b/src/plugins/copyFileContents/README.md new file mode 100644 index 000000000..18dc2d3df --- /dev/null +++ b/src/plugins/copyFileContents/README.md @@ -0,0 +1,5 @@ +# CopyFileContents + +Adds a button to text file attachments to copy their contents. + +![](https://github.com/user-attachments/assets/b1a0f6f4-106f-4953-94d9-4c5ef5810bca) diff --git a/src/plugins/copyStickerLinks/README.md b/src/plugins/copyStickerLinks/README.md new file mode 100644 index 000000000..40e632322 --- /dev/null +++ b/src/plugins/copyStickerLinks/README.md @@ -0,0 +1,5 @@ +# CopyStickerLinks + +Adds "Copy Link" and "Open Link" options to the context menu of stickers! + +![](https://github.com/user-attachments/assets/a0982d5c-ab83-458b-9ca3-834803e0782e) diff --git a/src/plugins/customIdle/README.md b/src/plugins/customIdle/README.md new file mode 100644 index 000000000..63bf87d89 --- /dev/null +++ b/src/plugins/customIdle/README.md @@ -0,0 +1,5 @@ +# CustomIdle + +Lets you change the time until your status gets automatically set to idle. You can also prevent idling altogether. + +![Plugin Configuration](https://github.com/Vendicated/Vencord/assets/45801973/4e5259b2-18e0-42e5-b69f-efc672ce1e0b) diff --git a/src/plugins/dearrow/README.md b/src/plugins/dearrow/README.md new file mode 100644 index 000000000..81a762c70 --- /dev/null +++ b/src/plugins/dearrow/README.md @@ -0,0 +1,5 @@ +# Dearrow + +Makes YouTube embed titles and thumbnails less sensationalist, powered by [Dearrow](https://dearrow.ajay.app/) + +https://github.com/Vendicated/Vencord/assets/45497981/7bf81108-102d-47c5-8ba5-357db4db1283 diff --git a/src/plugins/decor/README.md b/src/plugins/decor/README.md new file mode 100644 index 000000000..467a61457 --- /dev/null +++ b/src/plugins/decor/README.md @@ -0,0 +1,17 @@ +# Decor + +Custom avatar decorations! + +![Custom decorations in chat](https://github.com/Vendicated/Vencord/assets/30497388/b0c4c4c8-8723-42a8-b50f-195ad4e26136) + +Create and use your own custom avatar decorations, or pick your favorite from the presets. + +You'll be able to see the custom avatar decorations of other users of this plugin, and they'll be able to see your custom avatar decoration. + +You can select and manage your custom avatar decorations under the "Profiles" page in settings, or in the plugin settings. + +![Custom decorations management](https://github.com/Vendicated/Vencord/assets/30497388/74fe8a9e-a2a2-4b29-bc10-9eaa58208ad4) + +Review the [guidelines](https://github.com/decor-discord/.github/blob/main/GUIDELINES.md) before creating your own custom avatar decoration. + +Join the [Discord server](https://discord.gg/dXp2SdxDcP) for support and notifications on your decoration's review. diff --git a/src/plugins/favEmojiFirst/README.md b/src/plugins/favEmojiFirst/README.md new file mode 100644 index 000000000..10a62a6be --- /dev/null +++ b/src/plugins/favEmojiFirst/README.md @@ -0,0 +1,6 @@ +# FavoriteEmojiFirst + +Puts your favorite emoji first in the emoji autocomplete. + +![a screenshot of the favourite emojis section](https://github.com/Vendicated/Vencord/assets/45497981/419c8c16-1afc-46e0-9cc2-20b9c3489711) +![a comparison of the emoji picker before and after enabling this plugin](https://github.com/Vendicated/Vencord/assets/45497981/4f57626d-cfc6-4155-a47c-2eac191231bb) diff --git a/src/plugins/favGifSearch/README.md b/src/plugins/favGifSearch/README.md new file mode 100644 index 000000000..af120dace --- /dev/null +++ b/src/plugins/favGifSearch/README.md @@ -0,0 +1,5 @@ +# FavoriteGifSearch + +Adds a search bar to favorite gifs. + +![Screenshot](https://github.com/Vendicated/Vencord/assets/45497981/19552adc-d921-4153-976e-e9361dc8fdaf) diff --git a/src/plugins/fixImagesQuality/README.md b/src/plugins/fixImagesQuality/README.md new file mode 100644 index 000000000..8e3cf6548 --- /dev/null +++ b/src/plugins/fixImagesQuality/README.md @@ -0,0 +1,3 @@ +# Fix Images Quality + +Prevents images from being loaded as webp, which can cause quality loss diff --git a/src/plugins/fixYoutubeEmbeds.desktop/README.md b/src/plugins/fixYoutubeEmbeds.desktop/README.md new file mode 100644 index 000000000..875de9e28 --- /dev/null +++ b/src/plugins/fixYoutubeEmbeds.desktop/README.md @@ -0,0 +1,5 @@ +# FixYoutubeEmbeds + +Bypasses youtube videos being blocked from display on Discord (for example by UMG) + +![](https://github.com/Vendicated/Vencord/assets/45497981/7a5fdcaa-217c-4c63-acae-f0d6af2f79be) diff --git a/src/plugins/friendsSince/README.md b/src/plugins/friendsSince/README.md new file mode 100644 index 000000000..ccbb09b67 --- /dev/null +++ b/src/plugins/friendsSince/README.md @@ -0,0 +1,5 @@ +# FriendsSince + +Shows when you became friends with someone in the user popout + +![](https://github.com/Vendicated/Vencord/assets/45497981/bb258188-ab48-4c4d-9858-1e90ba41e926) diff --git a/src/plugins/fullSearchContext/README.md b/src/plugins/fullSearchContext/README.md new file mode 100644 index 000000000..4bc5feb5a --- /dev/null +++ b/src/plugins/fullSearchContext/README.md @@ -0,0 +1,5 @@ +# FullSearchContext + +Makes the message context menu in message search results have all options you'd expect. + +![](https://github.com/user-attachments/assets/472d1327-3935-44c7-b7c4-0978b5348550) diff --git a/src/plugins/ignoreActivities/README.md b/src/plugins/ignoreActivities/README.md new file mode 100644 index 000000000..abc75720f --- /dev/null +++ b/src/plugins/ignoreActivities/README.md @@ -0,0 +1,13 @@ +# IgnoreActivities + +Ignore activities from showing up on your status ONLY. You can configure which ones are specifically ignored from the Registered Games and Activities tabs, or use the general settings. + +![](https://github.com/user-attachments/assets/f0c19060-0ecf-4f1c-8165-a5aa40143c82) + +![](https://github.com/user-attachments/assets/73c3fa7a-5b90-41ee-a4d6-91fa76458b74) + +![](https://github.com/user-attachments/assets/1ab3fe73-3911-48d1-8a08-e976af614b41) + +The activity stays showing as a detected game even if ignored, differently from the stock Toggle Detection button from Discord: + +![](https://github.com/user-attachments/assets/08ea60c3-3a31-42de-ae4c-7535fbf1b45a) diff --git a/src/plugins/imageLink/README.md b/src/plugins/imageLink/README.md new file mode 100644 index 000000000..add657948 --- /dev/null +++ b/src/plugins/imageLink/README.md @@ -0,0 +1,3 @@ +# ImageLink + +If a message consists of only a link to an image, Discord hides the link and shows only the image embed. This plugin makes the link show regardless. diff --git a/src/plugins/imageZoom/README.md b/src/plugins/imageZoom/README.md new file mode 100644 index 000000000..75cfd4fd8 --- /dev/null +++ b/src/plugins/imageZoom/README.md @@ -0,0 +1,6 @@ +# ImageZoom + +Lets you zoom in to images and gifs. Use scroll wheel to zoom in and shift + scroll wheel to increase lens radius / size + +![the plugin in action](https://github.com/Vendicated/Vencord/assets/45497981/408cd77d-c5f4-40bc-8de2-f977a31b3e5f) +![the context menu options offered by the plugin](https://github.com/Vendicated/Vencord/assets/45497981/3bede636-f1ce-493f-af46-788b920cb81c) diff --git a/src/plugins/implicitRelationships/README.md b/src/plugins/implicitRelationships/README.md new file mode 100644 index 000000000..b560bf2c4 --- /dev/null +++ b/src/plugins/implicitRelationships/README.md @@ -0,0 +1,7 @@ +# ImplicitRelationships + +Shows your implicit relationships in the Friends tab. + +Implicit relationships on Discord are people with whom you've frecently interacted and don't have a relationship with. Even though Discord thinks you should be friends with them, you haven't added them as friends! + +![](https://camo.githubusercontent.com/6927161ee0c933f7ef6d61f243cca3e6ea4c8db9d1becd8cbf73c45e1bd0d127/68747470733a2f2f692e646f6c66692e65732f7055447859464662674d2e706e673f6b65793d736e3950343936416c32444c7072) diff --git a/src/plugins/mentionAvatars/README.md b/src/plugins/mentionAvatars/README.md new file mode 100644 index 000000000..5e553419e --- /dev/null +++ b/src/plugins/mentionAvatars/README.md @@ -0,0 +1,6 @@ +# MentionAvatars + +Shows user avatars and role icons inside mentions + +![](https://github.com/user-attachments/assets/fc76ea47-5e19-4063-a592-c57785a75cc7) +![](https://github.com/user-attachments/assets/76c4c3d9-7cde-42db-ba84-903cbb40c163) diff --git a/src/plugins/messageClickActions/README.md b/src/plugins/messageClickActions/README.md new file mode 100644 index 000000000..5162105a1 --- /dev/null +++ b/src/plugins/messageClickActions/README.md @@ -0,0 +1,5 @@ +# MessageClickActions + +Allows you to double click to edit/reply to a message or delete it if you hold the backspace key + +![](https://github.com/Vendicated/Vencord/assets/55940580/6885aca2-4021-4910-b636-bb40f877a816) diff --git a/src/plugins/messageLatency/README.md b/src/plugins/messageLatency/README.md new file mode 100644 index 000000000..8d2a776cd --- /dev/null +++ b/src/plugins/messageLatency/README.md @@ -0,0 +1,31 @@ +# MessageLatency + +Displays an indicator for messages that took β‰₯n seconds to send. + +> **NOTE** +> +> - This plugin only applies to messages received after opening the channel +> - False positives can exist if the user's system clock has drifted. +> - Grouped messages only display latency of the first message + +## Demo + +### Chat View + +![chat-view](https://github.com/Vendicated/Vencord/assets/82430093/69430881-60b3-422f-aa3d-c62953837566) + +### Clock -ve Drift + +![pissbot-on-top](https://github.com/Vendicated/Vencord/assets/82430093/d9248b66-e761-4872-8829-e8bf4fea6ec8) + +### Clock +ve Drift + +![dumb-ai](https://github.com/Vendicated/Vencord/assets/82430093/0e9783cf-51d5-4559-ae10-42399e7d4099) + +### Connection Delay + +![who-this](https://github.com/Vendicated/Vencord/assets/82430093/fd68873d-8630-42cc-a166-e9063d2718b2) + +### Icons + +![icons](https://github.com/Vendicated/Vencord/assets/82430093/17630bd9-44ee-4967-bcdf-3315eb6eca85) diff --git a/src/plugins/noMaskedUrlPaste/README.md b/src/plugins/noMaskedUrlPaste/README.md new file mode 100644 index 000000000..36707ac93 --- /dev/null +++ b/src/plugins/noMaskedUrlPaste/README.md @@ -0,0 +1,3 @@ +# NoMaskedUrlPaste + +Pasting a link while you have text selected will NOT paste your link as a masked link. diff --git a/src/plugins/noUnblockToJump/README.md b/src/plugins/noUnblockToJump/README.md new file mode 100644 index 000000000..630ef7dc2 --- /dev/null +++ b/src/plugins/noUnblockToJump/README.md @@ -0,0 +1,5 @@ +# No Unblock To Jump + +Removes the popup preventing you to jump to a message from a blocked/ignored user or likely spammer (eg: in search results) + +![A modal popup telling you to unblock a user to jump their message](https://github.com/user-attachments/assets/0e4b859d-f3b3-4101-9a83-829afb473d1e) diff --git a/src/plugins/notificationVolume/README.md b/src/plugins/notificationVolume/README.md new file mode 100644 index 000000000..05e5af4e2 --- /dev/null +++ b/src/plugins/notificationVolume/README.md @@ -0,0 +1,3 @@ +# NotificationVolume + +Set a separate volume for notifications and in-app sounds (e.g. messages, call sound, mute/unmute) helping your ears stay healthy for many years to come. diff --git a/src/plugins/notificationVolume/index.tsx b/src/plugins/notificationVolume/index.ts similarity index 100% rename from src/plugins/notificationVolume/index.tsx rename to src/plugins/notificationVolume/index.ts diff --git a/src/plugins/onePingPerDM/README.md b/src/plugins/onePingPerDM/README.md new file mode 100644 index 000000000..43f89b72c --- /dev/null +++ b/src/plugins/onePingPerDM/README.md @@ -0,0 +1,7 @@ +# OnePingPerDM +If unread messages are sent by a user in DMs multiple times, you'll only receive one audio ping. Read the messages to reset the limit + +## Purpose +- Prevents ping audio spam in DMs +- Be able to distinguish more than one ping as multiple users +- Be less annoyed while gaming diff --git a/src/plugins/openInApp/README.md b/src/plugins/openInApp/README.md new file mode 100644 index 000000000..2b1385c62 --- /dev/null +++ b/src/plugins/openInApp/README.md @@ -0,0 +1,11 @@ +# OpenInApp + +Open links in their respective apps instead of your browser + +## Currently supports: + +- Spotify +- Steam +- EpicGames +- Tidal +- Apple Music (iTunes) diff --git a/src/plugins/pauseInvitesForever/README.md b/src/plugins/pauseInvitesForever/README.md new file mode 100644 index 000000000..7ccf0b72f --- /dev/null +++ b/src/plugins/pauseInvitesForever/README.md @@ -0,0 +1,5 @@ +# PauseInvitesForever + +Adds a button to the Security Actions modal to to pause invites indefinitely. + +![](https://github.com/Vendicated/Vencord/assets/47677887/e5ba40a3-cb08-462a-8615-fb74dd54c790) diff --git a/src/plugins/permissionFreeWill/README.md b/src/plugins/permissionFreeWill/README.md new file mode 100644 index 000000000..ca30575ff --- /dev/null +++ b/src/plugins/permissionFreeWill/README.md @@ -0,0 +1,9 @@ +# PermissionFreeWill + +Removes the client-side restrictions that prevent editing channel permissions, such as permission lockouts ("Pretty sure +you don't want to do this") and onboarding requirements ("Making this change will make your server incompatible [...]") + +## Warning + +This plugin will let you create permissions in servers that **WILL** lock you out of channels until an administrator +can resolve it for you. Please be careful with the overwrites you are making and check carefully. diff --git a/src/plugins/previewMessage/README.md b/src/plugins/previewMessage/README.md new file mode 100644 index 000000000..34eee3f61 --- /dev/null +++ b/src/plugins/previewMessage/README.md @@ -0,0 +1,6 @@ +# PreviewMessage + +Lets you preview your message before sending it. + +![the plugin in action](https://github.com/Vendicated/Vencord/assets/45497981/3ce32860-e5cd-4ea2-bdab-e121f1703579) + diff --git a/src/plugins/quickMention/README.md b/src/plugins/quickMention/README.md new file mode 100644 index 000000000..fd862610d --- /dev/null +++ b/src/plugins/quickMention/README.md @@ -0,0 +1,5 @@ +# QuickMention + +Adds a mention icon to the messages action bar + +![](https://github.com/Vendicated/Vencord/assets/55940580/82d3fec7-4196-4917-b3c2-6e652b2aff9e) diff --git a/src/plugins/quickReply/README.md b/src/plugins/quickReply/README.md new file mode 100644 index 000000000..bd3067910 --- /dev/null +++ b/src/plugins/quickReply/README.md @@ -0,0 +1,6 @@ +# QuickReply + +Reply to (ctrl + up/down) and edit (ctrl + shift + up/down) messages via keybinds + +![](https://github.com/Vendicated/Vencord/assets/55940580/df79a27a-6529-4c70-8870-3c17d3637e4f) + diff --git a/src/plugins/replaceGoogleSearch/README.md b/src/plugins/replaceGoogleSearch/README.md new file mode 100644 index 000000000..1ab30212e --- /dev/null +++ b/src/plugins/replaceGoogleSearch/README.md @@ -0,0 +1,5 @@ +# ReplaceGoogleSearch + +Replaces the Google search with different Engines + +![Visualization](https://github.com/Vendicated/Vencord/assets/61953774/8b8158d2-0407-4d7b-9dff-a8b9bdc1a122) diff --git a/src/plugins/replyTimestamp/README.md b/src/plugins/replyTimestamp/README.md new file mode 100644 index 000000000..b7952bf3a --- /dev/null +++ b/src/plugins/replyTimestamp/README.md @@ -0,0 +1,5 @@ +# ReplyTimestamp + +Shows timestamps on the previews of replied-to messages. Pretty simple. + +![](https://github.com/Vendicated/Vencord/assets/1547062/62e2b67a-e567-4c7a-884d-4640f897f7e0) diff --git a/src/plugins/seeSummaries/README.md b/src/plugins/seeSummaries/README.md new file mode 100644 index 000000000..1c8b6c793 --- /dev/null +++ b/src/plugins/seeSummaries/README.md @@ -0,0 +1,9 @@ +# Summaries + +Enables Discord's experimental Summaries feature on every server, displaying AI generated summaries of conversations. + +Read more about summaries in the [official Discord help article](https://support.discord.com/hc/en-us/articles/12926016807575-In-Channel-Conversation-Summaries)! + +Note that this plugin can't fetch old summaries, it can only display ones created while your Discord is running with the plugin enabled. + +![](https://github.com/Vendicated/Vencord/assets/45497981/bd931b0c-2e85-4c10-9f7c-8ba01eb55745) diff --git a/src/plugins/serverInfo/README.md b/src/plugins/serverInfo/README.md new file mode 100644 index 000000000..98c9013e0 --- /dev/null +++ b/src/plugins/serverInfo/README.md @@ -0,0 +1,7 @@ +# ServerInfo + +Allows you to view info about servers and see friends and blocked users + +![](https://github.com/Vendicated/Vencord/assets/45497981/a49783b5-e8fc-41d8-968f-58600e9f6580) +![](https://github.com/Vendicated/Vencord/assets/45497981/5efc158a-e671-4196-a15a-77edf79a2630) +![Available as "Server Profile" option in the server context menu](https://github.com/Vendicated/Vencord/assets/45497981/f43be943-6dc4-4232-9709-fbeb382d8e54) diff --git a/src/plugins/showHiddenThings/README.md b/src/plugins/showHiddenThings/README.md new file mode 100644 index 000000000..753e5c148 --- /dev/null +++ b/src/plugins/showHiddenThings/README.md @@ -0,0 +1,19 @@ +# ShowHiddenThings + +Displays various hidden & moderator-only things regardless of permissions. + +## Features + +- Show member timeout icons in chat +![](https://github.com/Vendicated/Vencord/assets/47677887/75e1f6ba-8921-4188-9c2d-c9c3f9d07101) + +- Show the invites paused tooltip in the server list +![](https://github.com/Vendicated/Vencord/assets/47677887/b6a923d2-ac55-40d9-b4f8-fa6fc117148b) + +- Show the member mod view context menu item in all servers + +![](https://github.com/Vendicated/Vencord/assets/47677887/3dac95dd-841c-4c15-ad87-2db7bd1e4dab) + +- Disable filters in Server Discovery search that hide servers that don't meet discovery criteria + +- Disable filters in Server Discovery search that hide NSFW & disallowed servers diff --git a/src/plugins/showTimeoutDuration/README.md b/src/plugins/showTimeoutDuration/README.md new file mode 100644 index 000000000..137802473 --- /dev/null +++ b/src/plugins/showTimeoutDuration/README.md @@ -0,0 +1,8 @@ +# ShowTimeoutDuration + +Displays how much longer a user's timeout will last. +Either in the timeout icon tooltip, or next to it, configurable via settings! + +![indicator in tooltip](https://github.com/Vendicated/Vencord/assets/45497981/606588a3-2646-40d9-8800-b6307f650136) + +![indicator next to timeout icon](https://github.com/Vendicated/Vencord/assets/45497981/ab9d2101-0fdc-4143-9310-9488f056eeee) diff --git a/src/plugins/stickerPaste/README.md b/src/plugins/stickerPaste/README.md new file mode 100644 index 000000000..a29170272 --- /dev/null +++ b/src/plugins/stickerPaste/README.md @@ -0,0 +1,3 @@ +# StickerPaste + +Makes picking a sticker in the sticker picker insert it into the chatbox instead of instantly sending. diff --git a/src/plugins/superReactionTweaks/README.md b/src/plugins/superReactionTweaks/README.md new file mode 100644 index 000000000..624ab8669 --- /dev/null +++ b/src/plugins/superReactionTweaks/README.md @@ -0,0 +1,11 @@ +# Super Reaction Tweaks + +This plugin applies configurable various tweaks to super reactions. + +![Screenshot](https://user-images.githubusercontent.com/22851444/281598795-58f07116-9f95-4f64-940b-23a5499f2302.png) + +## Features: + +**Super React By Default** - The reaction picker will default to super reactions instead of normal reactions. + +**Super Reaction Play Limit** - Allows you to decide how many super reaction animations can play at once, including removing the limit entirely. diff --git a/src/plugins/themeAttributes/README.md b/src/plugins/themeAttributes/README.md new file mode 100644 index 000000000..89001aae4 --- /dev/null +++ b/src/plugins/themeAttributes/README.md @@ -0,0 +1,29 @@ +# ThemeAttributes + +This plugin adds data attributes and CSS variables to various elements inside Discord + +This allows themes to more easily theme those elements or even do things that otherwise wouldn't be possible + +## Available Attributes + +### All Tab Bars (User Settings, Server Settings, etc) + +`data-tab-id` contains the id of that tab + +![image](https://github.com/Vendicated/Vencord/assets/45497981/1263b782-f673-4f09-820c-4cc366d062ad) + +### Chat Messages + +- `data-author-id` contains the id of the author +- `data-author-username` contains the username of the author +- `data-is-self` is a boolean indicating whether this is the current user's message + +![image](https://github.com/Vendicated/Vencord/assets/45497981/34bd5053-3381-402f-82b2-9c812cc7e122) + +## CSS Variables + +### Avatars + +`--avatar-url-` contains a URL for the users avatar with the size attribute adjusted for the resolutions `128, 256, 512, 1024, 2048, 4096`. + +![image](https://github.com/Vendicated/Vencord/assets/26598490/192ddac0-c827-472f-9933-fa99ff36f723) diff --git a/src/plugins/unlockedAvatarZoom/README.md b/src/plugins/unlockedAvatarZoom/README.md new file mode 100644 index 000000000..9ecd0b1a4 --- /dev/null +++ b/src/plugins/unlockedAvatarZoom/README.md @@ -0,0 +1,5 @@ +# UnlockedAvatarZoom + +Allows you to zoom in further in the image crop tool when changing your avatar + +![](https://raw.githubusercontent.com/Vencord/plugin-assets/main/UnlockedAvatarZoom/demo.avif) diff --git a/src/plugins/userMessagesPronouns/README.md b/src/plugins/userMessagesPronouns/README.md new file mode 100644 index 000000000..455f9c443 --- /dev/null +++ b/src/plugins/userMessagesPronouns/README.md @@ -0,0 +1,5 @@ +User Messages Pronouns + +Adds pronouns to chat user messages + +![](https://github.com/user-attachments/assets/34dc373d-faf4-4420-b49b-08b2647baa3b) diff --git a/src/plugins/userVoiceShow/README.md b/src/plugins/userVoiceShow/README.md new file mode 100644 index 000000000..a0eb7c43e --- /dev/null +++ b/src/plugins/userVoiceShow/README.md @@ -0,0 +1,7 @@ +# User Voice Show + +Shows an indicator when a user is in a Voice Channel + +![a preview of the indicator in the user profile](https://github.com/user-attachments/assets/48f825e4-fad5-40d7-bb4f-41d5e595aae0) + +![a preview of the indicator in the member list](https://github.com/user-attachments/assets/51be081d-7bbb-45c5-8533-d565228e50c1) diff --git a/src/plugins/validReply/README.md b/src/plugins/validReply/README.md new file mode 100644 index 000000000..49e313cf5 --- /dev/null +++ b/src/plugins/validReply/README.md @@ -0,0 +1,7 @@ +# ValidReply + +Fixes referenced (replied to) messages showing as "Message could not be loaded". + +Hover the text to load the message! + +![](https://github.com/Vendicated/Vencord/assets/45801973/d3286acf-e822-4b7f-a4e7-8ced18f581af) diff --git a/src/plugins/volumeBooster/README.md b/src/plugins/volumeBooster/README.md new file mode 100644 index 000000000..62bd5ab52 --- /dev/null +++ b/src/plugins/volumeBooster/README.md @@ -0,0 +1,9 @@ +# Volume Booster + +Allows you to boost the volume over 200% on desktop and over 100% on other clients. + +Works on users, bots, and streams! + +![the volume being moved up to 270% on vesktop](https://github.com/user-attachments/assets/793e012e-c069-4fa4-a3d5-61c2f55edd3e) + +![the volume being moved up to 297% on a stream](https://github.com/user-attachments/assets/77463eb9-2537-4821-a3ab-82f60633ccbc) diff --git a/src/plugins/whoReacted/README.md b/src/plugins/whoReacted/README.md new file mode 100644 index 000000000..ee4227ab8 --- /dev/null +++ b/src/plugins/whoReacted/README.md @@ -0,0 +1,5 @@ +# WhoReacted + +Next to each reaction, display each user's avatar. Each avatar can be clicked and will open the profile. + +![](https://github.com/Vendicated/Vencord/assets/57493648/97fec9e8-396f-4f5e-916e-1ec21445113d) diff --git a/src/plugins/xsOverlay/README.md b/src/plugins/xsOverlay/README.md new file mode 100644 index 000000000..7f517c173 --- /dev/null +++ b/src/plugins/xsOverlay/README.md @@ -0,0 +1,14 @@ +# XSOverlay Notifier + +Sends Discord messages to [XSOverlay](https://store.steampowered.com/app/1173510/XSOverlay/) for easier viewing while using VR. + +## Preview + +![Resulting notification inside XSOverlay](https://github.com/Vendicated/Vencord/assets/24845294/205d2055-bb4a-44e4-b7e3-265391bccd40) + +![Test notification inside XSOverlay](https://github.com/user-attachments/assets/d3b0c387-1d67-4697-a470-d4a927e228f4) + +## Usage +- Enable this plugin +- Set port and plugin settings as desired (defaults should work fine) +- Open SteamVR and XSOverlay diff --git a/src/plugins/youtubeAdblock.desktop/README.md b/src/plugins/youtubeAdblock.desktop/README.md new file mode 100644 index 000000000..d6235f36e --- /dev/null +++ b/src/plugins/youtubeAdblock.desktop/README.md @@ -0,0 +1,7 @@ +# WatchTogetherAdblock + +Block ads in YouTube embeds and the WatchTogether activity via AdGuard + +Note that this only works for yourself, other users in the activity will still see ads. + +Powered by a modified version of [Adguard's BlockYoutubeAdsShortcut](https://github.com/AdguardTeam/BlockYouTubeAdsShortcut) From 71f1a29840fc4e924c22e0ff148b7b208800749c Mon Sep 17 00:00:00 2001 From: thororen1234 <78185467+thororen1234@users.noreply.github.com> Date: Fri, 22 Aug 2025 14:23:44 -0400 Subject: [PATCH 20/21] Fix Patches --- .../alwaysExpandProfiles/index.ts | 16 +++++----------- src/equicordplugins/downloadify/index.tsx | 2 +- 2 files changed, 6 insertions(+), 12 deletions(-) diff --git a/src/equicordplugins/alwaysExpandProfiles/index.ts b/src/equicordplugins/alwaysExpandProfiles/index.ts index b52b3296d..f54c2904a 100644 --- a/src/equicordplugins/alwaysExpandProfiles/index.ts +++ b/src/equicordplugins/alwaysExpandProfiles/index.ts @@ -17,18 +17,12 @@ export default definePlugin({ authors: [EquicordDevs.thororen], patches: [ { - find: '"UserProfilePopout"})', + find: '"view-profile"', replacement: { - match: /(?<=user:(\i).*?"PRESS_VIEW_PROFILE".{0,150})return/, - replace: "return $self.openUserProfile($1);" - } - }, - { - find: '"BotUserProfilePopout"', - replacement: { - match: /(?<=user:(\i).*?"PRESS_VIEW_PROFILE".{0,100})return/, - replace: "return $self.openUserProfile($1);" - } + match: /(user:(\i).*?"PRESS_VIEW_PROFILE".{0,200})return/, + replace: "$1return $self.openUserProfile($2);" + }, + all: true }, ], openUserProfile(user: User) { diff --git a/src/equicordplugins/downloadify/index.tsx b/src/equicordplugins/downloadify/index.tsx index 6c4216f9b..7a09fc6c4 100644 --- a/src/equicordplugins/downloadify/index.tsx +++ b/src/equicordplugins/downloadify/index.tsx @@ -33,7 +33,7 @@ export default definePlugin({ patches: [ { // Pass the guild ID to the profile modal context. Used by the next patch. - find: "animate}):null", + find: "clanTagChiplet}),", replacement: { match: /("right",)(avatarUrl:null)/, replace: "$1guildId:arguments[0]?.channel?.guild_id,$2" From 610565c0c38ed50d10d9ebe93d246c9dbe19434e Mon Sep 17 00:00:00 2001 From: Etorix <92535668+EtorixDev@users.noreply.github.com> Date: Fri, 22 Aug 2025 11:24:42 -0700 Subject: [PATCH 21/21] Add some READMEs. (#357) * Add READMEs. * Add READMEs. --- src/equicordplugins/downloadify/README.md | 12 +++- src/equicordplugins/questify/README.md | 68 +++++++++++++++++++++++ 2 files changed, 79 insertions(+), 1 deletion(-) create mode 100644 src/equicordplugins/questify/README.md diff --git a/src/equicordplugins/downloadify/README.md b/src/equicordplugins/downloadify/README.md index e07224448..f816a56f9 100644 --- a/src/equicordplugins/downloadify/README.md +++ b/src/equicordplugins/downloadify/README.md @@ -1,3 +1,13 @@ # Downloadify -Improved downloading for various assets +Download various assets directly in Discord without having to open a browser or dig through HTML. + +## Features +1. Download non-image files such as ZIP files directly inside of Discord. + - Allow or disallow unicode characters in file names. + - Various image assets are available in multiple formats as Discord makes converted copies. + - APNG and AWEBP are presented with the `.apng` and `.awebp` extensions during download in order to differentiate them from static PNG and WEBP variants, but upon download their extensions are converted back to `.png` and `.webp` respectively. +2. Show a notification when downloads start and finish with a customizable duration. +3. Add a download button to voice messages. +4. Set a default directory or pick each time. + - Pick whether to overwrite existing files or not. \ No newline at end of file diff --git a/src/equicordplugins/questify/README.md b/src/equicordplugins/questify/README.md new file mode 100644 index 000000000..d58dbd5c3 --- /dev/null +++ b/src/equicordplugins/questify/README.md @@ -0,0 +1,68 @@ +# Questify + +Enhance your Quest experience with a suite of features, or disable them entirely if they're not your thing. + +![An example of the Restyle and Reorder features.](https://github.com/user-attachments/assets/452ba894-d0e0-46d5-894a-af901c42d8e5) + +## Features +1. Quest Features + - Modify specific Quest features. + - Allows for disabling the following Quest related features: + - Everything β€” All of the below is disabled. + - Discovery Tab β€” Quests tab becomes hidden. + - Fetching Quests β€” Will prevent fetching Quests resulting in a blank Quests page. + - Badge on User Profiles β€” Hides the profile badge which doubles as a shortcut to the Quests page. + - Popup Above User Panel β€” Hides the new Quest promotion that appears above your profile panel. + - Gift Inventory Relocation Notice β€” Hides the text in the gift inventory which doubles as a shortcut to the Quests page. + - Friends List Active Now Promotion β€” Hides the promotion that displays when your friends play a game which has an active Quest. + - Allows for modifying the following Quest related features: + - Complete Video Quests in Background β€” Click the watch button on video Quests and wait for the video duration to pass. The first click will start the Quest without opening the video modal. Subsequent clicks will show the modal as normal. + - Complete Game Quests in Background β€” Click the accept button on game Quests and wait for the play duration to pass. + - Make Mobile Quests Desktop Compatible β€” Make mobile-only video Quests completable on Desktop. +2. Quests Button + - Show a Quest button in the server list with an optional indicator for unclaimed Quests. + - Button Visibility + - Always β€” Always display the button. + - Unclaimed β€” Only display the button when you have unclaimed Quests. + - Never β€” Never display the button. + - Unclaimed Indicator + - Pill β€” Shows an "unread" pill when there are unclaimed Quests. + - Badge β€” Shows the number of unclaimed Quests as a "ping" badge. + - Both β€” Both of the above. + - None β€” Show no indication of unclaimed Quests. + - Badge Color + - Use the color picker to pick a custom badge color or use the default Discord ping badge color by clicking `Disable`. + - Left / Middle / Right Click Actions + - Open Quests β€” Navigate to the Quests tab in Discovery. + - Plugin Settings β€” Open the plugin settings. + - Context Menu β€” Show the options `Mark All Ignored`, `Reset Ignored List`, and `Fetch Quests`. + - Nothing β€” Do nothing. +3. Fetching Quests + - Configure how often to fetch Quests from Discord and set up alerts for new Quests. + - By default, Discord only fetches Quests on load and when visiting the Quests page. This means that without a fetch interval defined below, this plugin will become unaware of new Quests added throughout the day. + - This relies on the Quest Button being enabled and set to either `Unclaimed`, or set to `Always` with unclaimed `Pill`, `Badge`, or `Both` indicators enabled. Otherwise, there is no reason to periodically fetch Quests. + - Also, if `Fetching Quests` is blocked in the `Quest Features` setting, this will not work. + - Fetch Interval + - Any time between 30 minutes and 12 hours. Defaults to every 45 minutes. + - Alert Sound + - Pick a default Discord sound to play when new Quests are fetched, or provide a custom audio URL. The audio must be hosted on a domain whitelisted by the Vencord CSP, such as `catbox`. + - If the provided URL is not on a supported domain, you will receive an error. If it *is* on a supported domain, but the audio file is invalid, it will silently fail. To ensure your audio file plays fine, click the audio preview button in the plugin settings. +4. Restyle Quests + - Highlight Quests with optional theme colors for visibility. + - Unclaimed / Claimed / Ignored / Expired + - Use the color picker to pick a custom Quest tile color or use the default Discord coloring by clicking `Disable`. + - Gradient Style + - Intense Restyle Gradient β€” A more intense gradient than default in order to hide hard edges under the gradient. + - Default Restyle Gradient β€” The default gradient intensity with the restyle color. + - Subtle Black Gradient β€” A much less intense black gradient for contrast with the Quest assets. + - No Gradient β€” Remove the gradient altogether. May cause the Quest assets to be difficult to see. + - Asset Preload + - Load All Quest Assets On Page Load β€” As soon as the Quest page is opened, load all Quest assets on the page. + - Load Quest Assets During Page Scroll β€” Keep the default behavior of loading Quest assets as they come into view. +5. Reorder Quests + - Sort Quests by their status. + - Comma-separated list must contain all of: `UNCLAIMED, CLAIMED, IGNORED, EXPIRED`. + - Subsort status groups by Quest added date, expiring date, or claimed date. + - Ignored Quest Profile + - Share ignored quests between accounts on a single client or keep them separate. + - Ignore Quests by opening their context menu and selecting `Mark as Ignored`. \ No newline at end of file