mirror of
https://github.com/Equicord/Equicord.git
synced 2025-06-07 21:53:04 -04:00
Merge branch 'dev' into dev2
This commit is contained in:
commit
10ecc2e251
246 changed files with 6010 additions and 3000 deletions
13
.github/workflows/build.yml
vendored
13
.github/workflows/build.yml
vendored
|
@ -16,7 +16,7 @@ permissions: write-all
|
|||
jobs:
|
||||
Build:
|
||||
name: Build Equicord
|
||||
runs-on: self-hosted
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
@ -39,7 +39,7 @@ jobs:
|
|||
run: pnpm buildStandalone
|
||||
|
||||
- name: Generate plugin list
|
||||
run: pnpm generatePluginJson dist/vencordplugins.json
|
||||
run: pnpm generatePluginJson dist/plugins.json
|
||||
|
||||
- name: Generate Equicord plugin list
|
||||
run: pnpm generateEquicordPluginJson dist/equicordplugins.json
|
||||
|
@ -66,15 +66,6 @@ jobs:
|
|||
rm release/package.json
|
||||
rm release/*.map
|
||||
|
||||
- name: get-npm-version
|
||||
id: package-version
|
||||
uses: martinbeentjes/npm-get-version-action@v1.3.1
|
||||
|
||||
- name: Upload Equicord Tagged
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
run: |
|
||||
gh release upload v${{ steps.package-version.outputs.current-version}} --clobber dist/release/*
|
||||
|
||||
- name: Upload Equicord Stable
|
||||
if: ${{ github.ref_name == 'main' }}
|
||||
run: |
|
||||
|
|
72
.github/workflows/nixosBuild.yml
vendored
Normal file
72
.github/workflows/nixosBuild.yml
vendored
Normal file
|
@ -0,0 +1,72 @@
|
|||
name: NixOS Build
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: 0 0 * * *
|
||||
env:
|
||||
FORCE_COLOR: true
|
||||
GITHUB_TOKEN: ${{ secrets.ETOKEN }}
|
||||
|
||||
permissions: write-all
|
||||
|
||||
jobs:
|
||||
Build:
|
||||
name: Build Equicord
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- 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 --no-frozen-lockfile
|
||||
|
||||
- name: Build web
|
||||
run: pnpm buildWebStandalone
|
||||
|
||||
- name: Build
|
||||
run: pnpm buildStandalone
|
||||
|
||||
- name: Generate plugin list
|
||||
run: pnpm generatePluginJson dist/vencordplugins.json
|
||||
|
||||
- name: Generate Equicord plugin list
|
||||
run: pnpm generateEquicordPluginJson dist/equicordplugins.json
|
||||
|
||||
- name: Collect files to be released
|
||||
run: |
|
||||
cd dist
|
||||
mkdir release
|
||||
|
||||
cp browser/browser.* release
|
||||
cp Vencord.user.{js,js.LEGAL.txt} release
|
||||
|
||||
# copy the plugin data jsons, the extension zips and the desktop/vesktop asars
|
||||
cp *.{json,zip,asar} release
|
||||
|
||||
# legacy un-asared files
|
||||
cp desktop/* release
|
||||
for file in equibop/*; do
|
||||
filename=$(basename "$file")
|
||||
cp "$file" "release/equibop${filename^}"
|
||||
done
|
||||
|
||||
find release -size 0 -delete
|
||||
rm release/package.json
|
||||
rm release/*.map
|
||||
|
||||
- name: Get current date
|
||||
id: date
|
||||
run: echo "::set-output name=date::$(date +'%Y-%m-%d')"
|
||||
|
||||
- name: Upload Equicord Stable
|
||||
run: |
|
||||
gh release create ${{ steps.date.outputs.date }} --latest=false
|
||||
gh release upload ${{ steps.date.outputs.date }} --clobber dist/release/*
|
8
.github/workflows/reportBrokenPlugins.yml
vendored
8
.github/workflows/reportBrokenPlugins.yml
vendored
|
@ -10,6 +10,10 @@ on:
|
|||
- stable
|
||||
- canary
|
||||
default: both
|
||||
webhook_url:
|
||||
type: string
|
||||
description: "Webhook URL that the report will be posted to. This will be visible for everyone, so DO NOT pass sensitive webhooks like discord webhook. This is meant to be used by Venbot."
|
||||
required: false
|
||||
schedule:
|
||||
# # Every day at midnight
|
||||
- cron: 0 0 * * *
|
||||
|
@ -17,7 +21,7 @@ on:
|
|||
jobs:
|
||||
TestPlugins:
|
||||
name: Test Patches
|
||||
runs-on: self-hosted
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
@ -87,5 +91,5 @@ jobs:
|
|||
cat "$stable_output_file" "$canary_output_file" >> $GITHUB_STEP_SUMMARY
|
||||
exit $exit_code
|
||||
env:
|
||||
WEBHOOK_URL: ${{ secrets.WEBHOOK }}
|
||||
WEBHOOK_URL: ${{ inputs.webhook_url || secrets.WEBHOOK_URL }}
|
||||
WEBHOOK_SECRET: ${{ secrets.WEBHOOK_SECRET }}
|
||||
|
|
30
README.md
30
README.md
|
@ -1,4 +1,4 @@
|
|||
# Equicord [<img src="./browser/icon.png" width="225" align="left" alt="Equicord">](https://github.com/Equicord/Equicord)
|
||||
# [<img src="./browser/icon.png" width="40" align="left" alt="Equicord">](https://github.com/Equicord/Equicord) Equicord
|
||||
|
||||
[](https://github.com/Equicord/Equibop)
|
||||
[](https://github.com/Equicord/Equicord/actions/workflows/test.yml)
|
||||
|
@ -11,7 +11,7 @@ You can join our [discord server](https://discord.gg/5Xh2W87egW) for commits, ch
|
|||
### Extra included plugins
|
||||
|
||||
<details>
|
||||
<summary>163 additional plugins</summary>
|
||||
<summary>175 additional plugins</summary>
|
||||
|
||||
### All Platforms
|
||||
|
||||
|
@ -25,20 +25,21 @@ You can join our [discord server](https://discord.gg/5Xh2W87egW) for commits, ch
|
|||
- BetterActivities by D3SOX, Arjix, AutumnVN
|
||||
- BetterAudioPlayer by creations
|
||||
- BetterBanReasons by Inbestigator
|
||||
- BetterBlockedUsers by TheArmagan & Elvyra
|
||||
- BetterBlockedUsers by TheArmagan
|
||||
- BetterInvites by iamme
|
||||
- BetterPlusReacts by Joona
|
||||
- BetterQuickReact by Ven & Sqaaakoi
|
||||
- BlockKeywords by catcraft
|
||||
- BlockKrisp by D3SOX
|
||||
- BypassPinPrompt by thororen
|
||||
- BypassStatus by Inbestigator & thororen
|
||||
- ChannelBadges by Creations
|
||||
- ChannelTabs by TheSun, TheKodeToad, keifufu, Nickyux
|
||||
- CharacterCounter by Creations & Panniku
|
||||
- CleanChannelName by AutumnVN
|
||||
- ClientSideBlock by Samwich
|
||||
- ClipsEnhancements by niko
|
||||
- CommandPalette by Ethan
|
||||
- CopyStickerLinks by Byeoon
|
||||
- CopyUserMention by Cortex & castdrian
|
||||
- CustomSounds by TheKodeToad & SpikeHD
|
||||
- CustomTimestamps by Rini & nvhrr
|
||||
|
@ -51,17 +52,18 @@ You can join our [discord server](https://discord.gg/5Xh2W87egW) for commits, ch
|
|||
- DisableAnimations by S€th
|
||||
- DisableCameras by Joona
|
||||
- DontFilterMe by Samwich
|
||||
- EmojiDumper by Cortex, Samwich, Woosh
|
||||
- Encryptcord by Inbestigator
|
||||
- EquicordCSS by thororen, Panniku, Dablulite, Coolesding, MiniDiscordThemes, LuckFire, gold_me
|
||||
- EquicordHelper by thororen & nyx
|
||||
- Equissant by SomeAspy & thororen
|
||||
- ExportContacts by dat_insanity
|
||||
- FakeProfileThemesAndEffects by ryan
|
||||
- CopyProfileColors by Crxa
|
||||
- FastDeleteChannels by thororen
|
||||
- FindReply by newwares
|
||||
- FixFileExtensions by thororen
|
||||
- FollowVoiceUser by TheArmagan
|
||||
- FontLoader by vmohammad
|
||||
- ForwardAnywhere by thororen
|
||||
- Freaky by nyx
|
||||
- FrequentQuickSwitcher by Samwich
|
||||
- FriendCodes by HypedDomi
|
||||
|
@ -75,6 +77,7 @@ You can join our [discord server](https://discord.gg/5Xh2W87egW) for commits, ch
|
|||
- Glide by Samwich
|
||||
- GlobalBadges by HypedDomi & Hosted by Wolfie
|
||||
- GoogleThat by Samwich
|
||||
- GuildPickerDumper by Cortex, Samwich, Synth, thororen
|
||||
- HideChatButtons by iamme
|
||||
- HideServers by bepvte
|
||||
- HolyNotes by Wolfie
|
||||
|
@ -87,6 +90,7 @@ You can join our [discord server](https://discord.gg/5Xh2W87egW) for commits, ch
|
|||
- IgnoreTerms by D3SOX
|
||||
- ImagePreview by Creations
|
||||
- ImgToGif by zyqunix
|
||||
- Ingtoninator by zyqunix
|
||||
- InRole by nin0dev
|
||||
- InstantScreenshare by HAHALOSAH & thororen
|
||||
- IRememberYou by zoodogood
|
||||
|
@ -94,6 +98,7 @@ You can join our [discord server](https://discord.gg/5Xh2W87egW) for commits, ch
|
|||
- JumpToStart by Samwich
|
||||
- KeyboardSounds by HypedDomi
|
||||
- KeywordNotify by camila314 & x3rt
|
||||
- LastActive by Crxa
|
||||
- LimitMiddleClickPaste by no dev listed
|
||||
- LoginWithQR by nexpid
|
||||
- MediaPlaybackSpeed by D3SOX
|
||||
|
@ -105,6 +110,8 @@ You can join our [discord server](https://discord.gg/5Xh2W87egW) for commits, ch
|
|||
- MessageTranslate by Samwich
|
||||
- ModalFade by Kyuuhachi
|
||||
- MoreStickers by Leko & Arjix
|
||||
- MoreUserTags by Cyn, TheSun, RyanCaoDev, LordElias, AutumnVN, hen
|
||||
- Morse by zyqunix
|
||||
- NeverPausePreviews by vappstar
|
||||
- NewPluginsManager by Sqaaakoi
|
||||
- NoAppsAllowed by kvba
|
||||
|
@ -113,9 +120,9 @@ You can join our [discord server](https://discord.gg/5Xh2W87egW) for commits, ch
|
|||
- NoMirroredCamera by Nyx
|
||||
- NoModalAnimation by AutumnVN
|
||||
- NoNitroUpsell by thororen
|
||||
- NoOnboarding by omaw & Glitch
|
||||
- NoRoleHeaders by Samwich
|
||||
- NotificationTitle by Kyuuhachi
|
||||
- OnePingPerDM by ProffDea
|
||||
- PingNotifications by smuki
|
||||
- PinIcon by iamme
|
||||
- PlatformSpoofer by Drag
|
||||
|
@ -124,7 +131,7 @@ You can join our [discord server](https://discord.gg/5Xh2W87egW) for commits, ch
|
|||
- QuestCompleter by Amia
|
||||
- QuestionMarkReplacement by nyx
|
||||
- Quoter by Samwich
|
||||
- RandomVoice by xijexo & omaw
|
||||
- RandomVoice by xijexo, omaw, thororen
|
||||
- Remix by MrDiamond
|
||||
- RemixMe by kvba
|
||||
- RepeatMessage by Tolgchu
|
||||
|
@ -138,7 +145,10 @@ You can join our [discord server](https://discord.gg/5Xh2W87egW) for commits, ch
|
|||
- SidebarChat by Joona
|
||||
- Signature by Ven, Rini, ImBanana, KrystalSkull
|
||||
- Slap by Korbo
|
||||
- Soggy by sliwka
|
||||
- SoundBoardLogger by Moxxie, fres, echo, maintained by thororen
|
||||
- SplitLargeMessages by Reycko
|
||||
- SpotifyActivityToggle by thororen
|
||||
- SpotifyLyrics by Joona
|
||||
- StatsfmPresence by Crxa
|
||||
- StatusPresets by iamme
|
||||
|
@ -150,6 +160,7 @@ You can join our [discord server](https://discord.gg/5Xh2W87egW) for commits, ch
|
|||
- ThemeLibrary by Fafa
|
||||
- Timezones by Aria
|
||||
- Title by Kyuuhachi
|
||||
- ToastNotifications by Skully, Ethan, Buzzy
|
||||
- ToggleVideoBind by mochie
|
||||
- TosuRPC by AutumnVN
|
||||
- Translate+ by Prince527 & Ven
|
||||
|
@ -166,6 +177,7 @@ You can join our [discord server](https://discord.gg/5Xh2W87egW) for commits, ch
|
|||
- ViewRawVariant by Kyuuhachi
|
||||
- VoiceChatUtilities by D3SOX
|
||||
- VoiceJoinMessages by Sqaaakoi & maintained by thororen
|
||||
- WallpaperFree by Joona
|
||||
- WebpackTarball by Kyuuhachi
|
||||
- WhitelistedEmojis by Creations
|
||||
- WhosWatching by fres
|
||||
|
@ -184,6 +196,7 @@ You can join our [discord server](https://discord.gg/5Xh2W87egW) for commits, ch
|
|||
|
||||
### Discord Desktop Only
|
||||
|
||||
- ClipsEnhancements by niko
|
||||
- MediaDownloader by Colorman
|
||||
- StatusWhilePlaying by thororen
|
||||
|
||||
|
@ -209,7 +222,6 @@ MacOS
|
|||
Linux
|
||||
|
||||
- [GUI-X11](https://github.com/Equicord/Equilotl/releases/latest/download/Equilotl-x11)
|
||||
- [GUI-Wayland](https://github.com/Equicord/Equilotl/releases/latest/download/Equilotl-wayland)
|
||||
- [CLI](https://github.com/Equicord/Equilotl/releases/latest/download/EquilotlCli-Linux)
|
||||
- [AUR](https://aur.archlinux.org/packages?O=0&K=equicord)
|
||||
|
||||
|
|
|
@ -17,9 +17,10 @@
|
|||
*/
|
||||
|
||||
function parseHeaders(headers) {
|
||||
const result = new Headers();
|
||||
if (!headers)
|
||||
return {};
|
||||
const result = {};
|
||||
return result;
|
||||
|
||||
const headersArr = headers.trim().split("\n");
|
||||
for (var i = 0; i < headersArr.length; i++) {
|
||||
var row = headersArr[i];
|
||||
|
@ -27,13 +28,7 @@ function parseHeaders(headers) {
|
|||
, key = row.slice(0, index).trim().toLowerCase()
|
||||
, value = row.slice(index + 1).trim();
|
||||
|
||||
if (result[key] === undefined) {
|
||||
result[key] = value;
|
||||
} else if (Array.isArray(result[key])) {
|
||||
result[key].push(value);
|
||||
} else {
|
||||
result[key] = [result[key], value];
|
||||
}
|
||||
result.append(key, value);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
// @license GPL-3.0
|
||||
// @match *://*.discord.com/*
|
||||
// @grant GM_xmlhttpRequest
|
||||
// @grant unsafeWindow
|
||||
// @run-at document-start
|
||||
// @compatible chrome Chrome + Tampermonkey or Violentmonkey
|
||||
// @compatible firefox Firefox Tampermonkey
|
||||
|
|
12
misc/install.sh
Normal file → Executable file
12
misc/install.sh
Normal file → Executable file
|
@ -62,8 +62,16 @@ check_for_updates() {
|
|||
local_modified=$(stat -c "%y" "$INSTALLER_PATH" | cut -d' ' -f1-2) || error "Failed to get local modified date"
|
||||
|
||||
if [ "$local_modified" != "$latest_modified" ]; then
|
||||
echo -e "${YELLOW}Installer is outdated. Updating...${NC}"
|
||||
download_installer
|
||||
echo -e "${YELLOW}Installer is outdated. Do you wish to update? [y/n]${NC}"
|
||||
read -p "" -n 1 -r retval
|
||||
|
||||
# Create a new line before printing our next notice, otherwise it will be printed on the same line
|
||||
# that the prompt was created on!
|
||||
echo ""
|
||||
case "$retval" in
|
||||
y|Y ) download_installer;;
|
||||
n|N ) echo -e "${YELLOW}Update cancelled. Running installer...${NC}" && return;;
|
||||
esac
|
||||
else
|
||||
echo -e "${GREEN}Installer is up-to-date.${NC}"
|
||||
fi
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "equicord",
|
||||
"private": "true",
|
||||
"version": "1.11.8",
|
||||
"version": "1.12.2",
|
||||
"description": "The other cutest Discord client mod",
|
||||
"homepage": "https://github.com/Equicord/Equicord#readme",
|
||||
"bugs": {
|
||||
|
|
|
@ -115,7 +115,7 @@ const buildConfigs = ([
|
|||
...nodeCommonOpts,
|
||||
entryPoints: [join(dirname(fileURLToPath(import.meta.url)), "../../src/main/index.ts")],
|
||||
outfile: "dist/desktop/patcher.js",
|
||||
footer: { js: "//# sourceURL=VencordPatcher\n" + sourceMapFooter("patcher") },
|
||||
footer: { js: "//# sourceURL=file:///VencordPatcher\n" + sourceMapFooter("patcher") },
|
||||
sourcemap,
|
||||
plugins: [
|
||||
// @ts-ignore this is never undefined
|
||||
|
@ -135,7 +135,7 @@ const buildConfigs = ([
|
|||
outfile: "dist/desktop/renderer.js",
|
||||
format: "iife",
|
||||
target: ["esnext"],
|
||||
footer: { js: "//# sourceURL=VencordRenderer\n" + sourceMapFooter("renderer") },
|
||||
footer: { js: "//# sourceURL=file:///VencordRenderer\n" + sourceMapFooter("renderer") },
|
||||
globalName: "Vencord",
|
||||
sourcemap,
|
||||
plugins: [
|
||||
|
@ -153,7 +153,7 @@ const buildConfigs = ([
|
|||
...nodeCommonOpts,
|
||||
entryPoints: [join(dirname(fileURLToPath(import.meta.url)), "../../src/preload.ts")],
|
||||
outfile: "dist/desktop/preload.js",
|
||||
footer: { js: "//# sourceURL=VencordPreload\n" + sourceMapFooter("preload") },
|
||||
footer: { js: "//# sourceURL=file:///VencordPreload\n" + sourceMapFooter("preload") },
|
||||
sourcemap,
|
||||
define: {
|
||||
...defines,
|
||||
|
@ -168,7 +168,7 @@ const buildConfigs = ([
|
|||
...nodeCommonOpts,
|
||||
entryPoints: [join(dirname(fileURLToPath(import.meta.url)), "../../src/main/index.ts")],
|
||||
outfile: "dist/equibop/main.js",
|
||||
footer: { js: "//# sourceURL=VencordMain\n" + sourceMapFooter("main") },
|
||||
footer: { js: "//# sourceURL=file:///VencordDesktopMain\n" + sourceMapFooter("main") },
|
||||
sourcemap,
|
||||
plugins: [
|
||||
...nodeCommonOpts.plugins,
|
||||
|
@ -187,7 +187,7 @@ const buildConfigs = ([
|
|||
outfile: "dist/equibop/renderer.js",
|
||||
format: "iife",
|
||||
target: ["esnext"],
|
||||
footer: { js: "//# sourceURL=VencordRenderer\n" + sourceMapFooter("renderer") },
|
||||
footer: { js: "//# sourceURL=file:///VencordDesktopRenderer\n" + sourceMapFooter("renderer") },
|
||||
globalName: "Vencord",
|
||||
sourcemap,
|
||||
plugins: [
|
||||
|
@ -205,7 +205,7 @@ const buildConfigs = ([
|
|||
...nodeCommonOpts,
|
||||
entryPoints: [join(dirname(fileURLToPath(import.meta.url)), "../../src/preload.ts")],
|
||||
outfile: "dist/equibop/preload.js",
|
||||
footer: { js: "//# sourceURL=VencordPreload\n" + sourceMapFooter("preload") },
|
||||
footer: { js: "//# sourceURL=file:///VencordPreload\n" + sourceMapFooter("preload") },
|
||||
sourcemap,
|
||||
define: {
|
||||
...defines,
|
||||
|
|
|
@ -92,7 +92,7 @@ const buildConfigs = [
|
|||
{
|
||||
...commonOptions,
|
||||
outfile: "dist/browser/browser.js",
|
||||
footer: { js: "//# sourceURL=VencordWeb" }
|
||||
footer: { js: "//# sourceURL=file:///VencordWeb" }
|
||||
},
|
||||
{
|
||||
...commonOptions,
|
||||
|
@ -101,7 +101,7 @@ const buildConfigs = [
|
|||
...commonOptions.define,
|
||||
IS_EXTENSION: "true"
|
||||
},
|
||||
footer: { js: "//# sourceURL=VencordWeb" }
|
||||
footer: { js: "//# sourceURL=file:///VencordWeb" }
|
||||
},
|
||||
{
|
||||
...commonOptions,
|
||||
|
|
|
@ -186,7 +186,7 @@ export const globPlugins = kind => ({
|
|||
const mod = `p${i}`;
|
||||
code += `import ${mod} from "./${dir}/${fileName.replace(/\.tsx?$/, "")}";\n`;
|
||||
pluginsCode += `[${mod}.name]:${mod},\n`;
|
||||
metaCode += `[${mod}.name]:${JSON.stringify({ folderName, userPlugin })},\n`; // TODO: add excluded plugins to display in the UI?
|
||||
metaCode += `[${mod}.name]:${JSON.stringify({ folderName, userPlugin })},\n`;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -261,7 +261,7 @@ page.on("console", async e => {
|
|||
const [, tag, message, otherMessage] = args as Array<string>;
|
||||
|
||||
switch (tag) {
|
||||
case "WebpackInterceptor:":
|
||||
case "WebpackPatcher:":
|
||||
const patchFailMatch = message.match(/Patch by (.+?) (had no effect|errored|found no module) \(Module id is (.+?)\): (.+)/);
|
||||
const patchSlowMatch = message.match(/Patch by (.+?) (took [\d.]+?ms) \(Module id is (.+?)\): (.+)/);
|
||||
const match = patchFailMatch ?? patchSlowMatch;
|
||||
|
@ -315,11 +315,9 @@ page.on("console", async e => {
|
|||
report.badWebpackFinds.push(otherMessage);
|
||||
break;
|
||||
case "Finished test":
|
||||
await browser.close();
|
||||
await printReport();
|
||||
setTimeout(async () => {
|
||||
await browser.close();
|
||||
process.exit();
|
||||
}, 10000);
|
||||
process.exit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -358,4 +356,4 @@ await page.evaluateOnNewDocument(`
|
|||
}
|
||||
`);
|
||||
|
||||
await page.goto(CANARY ? "https://canary.discord.com/login" : "https://discord.com/login");
|
||||
await page.goto(CANARY ? "https://canary.discord.com/login" : "https://discord.com/login", { timeout: 120000 });
|
||||
|
|
|
@ -33,7 +33,7 @@ import { openUpdaterModal } from "@components/VencordSettings/UpdaterTab";
|
|||
import { StartAt } from "@utils/types";
|
||||
|
||||
import { get as dsGet } from "./api/DataStore";
|
||||
import { showNotification } from "./api/Notifications";
|
||||
import { NotificationData, showNotification } from "./api/Notifications";
|
||||
import { PlainSettings, Settings } from "./api/Settings";
|
||||
import { patches, PMLogger, startAllPlugins } from "./plugins";
|
||||
import { localStorage } from "./utils/localStorage";
|
||||
|
@ -105,6 +105,46 @@ async function syncSettings() {
|
|||
}
|
||||
}
|
||||
|
||||
let notifiedForUpdatesThisSession = false;
|
||||
|
||||
async function runUpdateCheck() {
|
||||
const notify = (data: NotificationData) => {
|
||||
if (notifiedForUpdatesThisSession) return;
|
||||
notifiedForUpdatesThisSession = true;
|
||||
|
||||
setTimeout(() => showNotification({
|
||||
permanent: true,
|
||||
noPersist: true,
|
||||
...data
|
||||
}), 10_000);
|
||||
};
|
||||
|
||||
try {
|
||||
const isOutdated = await checkForUpdates();
|
||||
if (!isOutdated) return;
|
||||
|
||||
if (Settings.autoUpdate) {
|
||||
await update();
|
||||
if (Settings.autoUpdateNotification) {
|
||||
notify({
|
||||
title: "Equicord has been updated!",
|
||||
body: "Click here to restart",
|
||||
onClick: relaunch
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
notify({
|
||||
title: "A Equicord update is available!",
|
||||
body: "Click here to view the update",
|
||||
onClick: openUpdaterModal!
|
||||
});
|
||||
} catch (err) {
|
||||
UpdateLogger.error("Failed to check for updates", err);
|
||||
}
|
||||
}
|
||||
|
||||
async function init() {
|
||||
await onceReady;
|
||||
startAllPlugins(StartAt.WebpackReady);
|
||||
|
@ -112,34 +152,8 @@ async function init() {
|
|||
syncSettings();
|
||||
|
||||
if (!IS_WEB && !IS_UPDATER_DISABLED) {
|
||||
try {
|
||||
const isOutdated = await checkForUpdates();
|
||||
if (!isOutdated) return;
|
||||
|
||||
if (Settings.autoUpdate) {
|
||||
await update();
|
||||
if (Settings.updateRelaunch) return relaunch;
|
||||
if (Settings.autoUpdateNotification)
|
||||
setTimeout(() => showNotification({
|
||||
title: "Equicord has been updated!",
|
||||
body: "Click here to restart",
|
||||
permanent: true,
|
||||
noPersist: true,
|
||||
onClick: relaunch
|
||||
}), 10_000);
|
||||
return;
|
||||
}
|
||||
|
||||
setTimeout(() => showNotification({
|
||||
title: "A Equicord update is available!",
|
||||
body: "Click here to view the update",
|
||||
permanent: true,
|
||||
noPersist: true,
|
||||
onClick: openUpdaterModal!
|
||||
}), 10_000);
|
||||
} catch (err) {
|
||||
UpdateLogger.error("Failed to check for updates", err);
|
||||
}
|
||||
runUpdateCheck();
|
||||
setInterval(runUpdateCheck, 1000 * 60 * 30); // 30 minutes
|
||||
}
|
||||
|
||||
if (IS_DEV) {
|
||||
|
@ -149,7 +163,7 @@ async function init() {
|
|||
"Webpack has finished initialising, but some patches haven't been applied yet.",
|
||||
"This might be expected since some Modules are lazy loaded, but please verify",
|
||||
"that all plugins are working as intended.",
|
||||
"You are seeing this warning because this is a Development build of Vencord.",
|
||||
"You are seeing this warning because this is a Development build of Equicord.",
|
||||
"\nThe following patches have not been applied:",
|
||||
"\n\n" + pendingPatches.map(p => `${p.plugin}: ${p.find}`).join("\n")
|
||||
);
|
||||
|
|
|
@ -21,25 +21,14 @@ import { Channel, User } from "discord-types/general/index.js";
|
|||
import { JSX } from "react";
|
||||
|
||||
interface DecoratorProps {
|
||||
activities: any[];
|
||||
channel: Channel;
|
||||
/**
|
||||
* Only for DM members
|
||||
*/
|
||||
channelName?: string;
|
||||
/**
|
||||
* Only for server members
|
||||
*/
|
||||
currentUser?: User;
|
||||
guildId?: string;
|
||||
isMobile: boolean;
|
||||
isOwner?: boolean;
|
||||
isTyping: boolean;
|
||||
selected: boolean;
|
||||
status: string;
|
||||
type: "guild" | "dm";
|
||||
user: User;
|
||||
[key: string]: any;
|
||||
/** only present when this is a DM list item */
|
||||
channel: Channel;
|
||||
/** only present when this is a guild list item */
|
||||
isOwner: boolean;
|
||||
}
|
||||
|
||||
export type MemberListDecoratorFactory = (props: DecoratorProps) => JSX.Element | null;
|
||||
type OnlyIn = "guilds" | "dms";
|
||||
|
||||
|
@ -53,18 +42,16 @@ export function removeMemberListDecorator(identifier: string) {
|
|||
decoratorsFactories.delete(identifier);
|
||||
}
|
||||
|
||||
export function __getDecorators(props: DecoratorProps): JSX.Element {
|
||||
const isInGuild = !!(props.guildId);
|
||||
|
||||
export function __getDecorators(props: DecoratorProps, type: "guild" | "dm"): JSX.Element {
|
||||
const decorators = Array.from(
|
||||
decoratorsFactories.entries(),
|
||||
([key, { render: Decorator, onlyIn }]) => {
|
||||
if ((onlyIn === "guilds" && !isInGuild) || (onlyIn === "dms" && isInGuild))
|
||||
if ((onlyIn === "guilds" && type !== "guild") || (onlyIn === "dms" && type !== "dm"))
|
||||
return null;
|
||||
|
||||
return (
|
||||
<ErrorBoundary noop key={key} message={`Failed to render ${key} Member List Decorator`}>
|
||||
<Decorator {...props} />
|
||||
<Decorator {...props} type={type} />
|
||||
</ErrorBoundary>
|
||||
);
|
||||
}
|
||||
|
|
40
src/api/NicknameIcons.tsx
Normal file
40
src/api/NicknameIcons.tsx
Normal file
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2025 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import ErrorBoundary from "@components/ErrorBoundary";
|
||||
import { Logger } from "@utils/Logger";
|
||||
import { ReactNode } from "react";
|
||||
|
||||
export interface NicknameIconProps {
|
||||
userId: string;
|
||||
}
|
||||
|
||||
export type NicknameIconFactory = (props: NicknameIconProps) => ReactNode | Promise<ReactNode>;
|
||||
|
||||
export interface NicknameIcon {
|
||||
priority: number;
|
||||
factory: NicknameIconFactory;
|
||||
}
|
||||
|
||||
const nicknameIcons = new Map<string, NicknameIcon>();
|
||||
const logger = new Logger("NicknameIcons");
|
||||
|
||||
export function addNicknameIcon(id: string, factory: NicknameIconFactory, priority = 0) {
|
||||
return nicknameIcons.set(id, {
|
||||
priority,
|
||||
factory: ErrorBoundary.wrap(factory, { noop: true, onError: error => logger.error(`Failed to render ${id}`, error) })
|
||||
});
|
||||
}
|
||||
|
||||
export function removeNicknameIcon(id: string) {
|
||||
return nicknameIcons.delete(id);
|
||||
}
|
||||
|
||||
export function _renderIcons(props: NicknameIconProps) {
|
||||
return Array.from(nicknameIcons)
|
||||
.sort((a, b) => b[1].priority - a[1].priority)
|
||||
.map(([id, { factory: NicknameIcon }]) => <NicknameIcon key={id} {...props} />);
|
||||
}
|
|
@ -18,7 +18,7 @@
|
|||
|
||||
import { Settings } from "@api/Settings";
|
||||
import { Queue } from "@utils/Queue";
|
||||
import { ReactDOM } from "@webpack/common";
|
||||
import { createRoot } from "@webpack/common";
|
||||
import type { ReactNode } from "react";
|
||||
import type { Root } from "react-dom/client";
|
||||
|
||||
|
@ -35,7 +35,7 @@ function getRoot() {
|
|||
const container = document.createElement("div");
|
||||
container.id = "vc-notification-container";
|
||||
document.body.append(container);
|
||||
reactRoot = ReactDOM.createRoot(container);
|
||||
reactRoot = createRoot(container);
|
||||
}
|
||||
return reactRoot;
|
||||
}
|
||||
|
|
|
@ -11,10 +11,6 @@
|
|||
width: 100%;
|
||||
}
|
||||
|
||||
.visual-refresh .vc-notification-root {
|
||||
background-color: var(--bg-overlay-floating, var(--background-base-low));
|
||||
}
|
||||
|
||||
.vc-notification-root:not(.vc-notification-log-wrapper > .vc-notification-root) {
|
||||
position: absolute;
|
||||
z-index: 2147483647;
|
||||
|
|
|
@ -39,7 +39,6 @@ export interface Settings {
|
|||
themeLinks: string[];
|
||||
frameless: boolean;
|
||||
transparent: boolean;
|
||||
updateRelaunch: boolean;
|
||||
winCtrlQ: boolean;
|
||||
macosVibrancyStyle:
|
||||
| "content"
|
||||
|
@ -101,7 +100,6 @@ const DefaultSettings: Settings = {
|
|||
winCtrlQ: false,
|
||||
macosVibrancyStyle: undefined,
|
||||
disableMinSize: false,
|
||||
updateRelaunch: false,
|
||||
winNativeTitleBar: false,
|
||||
plugins: {},
|
||||
|
||||
|
|
|
@ -27,6 +27,7 @@ import * as $MessageDecorations from "./MessageDecorations";
|
|||
import * as $MessageEventsAPI from "./MessageEvents";
|
||||
import * as $MessagePopover from "./MessagePopover";
|
||||
import * as $MessageUpdater from "./MessageUpdater";
|
||||
import * as $NicknameIcons from "./NicknameIcons";
|
||||
import * as $Notices from "./Notices";
|
||||
import * as $Notifications from "./Notifications";
|
||||
import * as $ServerList from "./ServerList";
|
||||
|
@ -123,6 +124,11 @@ export const MessageUpdater = $MessageUpdater;
|
|||
*/
|
||||
export const UserSettings = $UserSettings;
|
||||
|
||||
/**
|
||||
* An API allowing you to add icons to the nickname, in profiles
|
||||
*/
|
||||
export const NicknameIcons = $NicknameIcons;
|
||||
|
||||
/**
|
||||
* Just used to identify if user is on Equicord as Vencord doesnt have this
|
||||
*/
|
||||
|
|
|
@ -22,7 +22,26 @@ import { ButtonProps } from "@webpack/types";
|
|||
|
||||
import { Heart } from "./Heart";
|
||||
|
||||
export default function DonateButton({
|
||||
export function VCDonateButton({
|
||||
look = Button.Looks.LINK,
|
||||
color = Button.Colors.TRANSPARENT,
|
||||
...props
|
||||
}: Partial<ButtonProps>) {
|
||||
return (
|
||||
<Button
|
||||
{...props}
|
||||
look={look}
|
||||
color={color}
|
||||
onClick={() => VencordNative.native.openExternal("https://github.com/sponsors/Vendicated")}
|
||||
innerClassName="vc-donate-button"
|
||||
>
|
||||
<Heart />
|
||||
Donate
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
export function DonateButton({
|
||||
look = Button.Looks.LINK,
|
||||
color = Button.Colors.TRANSPARENT,
|
||||
...props
|
||||
|
|
|
@ -16,10 +16,10 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { LazyComponent, LazyComponentWrapper } from "@utils/lazyReact";
|
||||
import { Logger } from "@utils/Logger";
|
||||
import { Margins } from "@utils/margins";
|
||||
import { LazyComponent } from "@utils/react";
|
||||
import { React } from "@webpack/common";
|
||||
import type { React } from "@webpack/common";
|
||||
|
||||
import { ErrorCard } from "./ErrorCard";
|
||||
|
||||
|
@ -46,7 +46,9 @@ const NO_ERROR = {};
|
|||
// We might want to import this in a place where React isn't ready yet.
|
||||
// Thus, wrap in a LazyComponent
|
||||
const ErrorBoundary = LazyComponent(() => {
|
||||
return class ErrorBoundary extends React.PureComponent<React.PropsWithChildren<Props>> {
|
||||
// This component is used in a lot of files which end up importing other Webpack commons and causing circular imports.
|
||||
// For this reason, use a non import access here.
|
||||
return class ErrorBoundary extends Vencord.Webpack.Common.React.PureComponent<React.PropsWithChildren<Props>> {
|
||||
state = {
|
||||
error: NO_ERROR as any,
|
||||
stack: "",
|
||||
|
@ -107,9 +109,9 @@ const ErrorBoundary = LazyComponent(() => {
|
|||
}
|
||||
};
|
||||
}) as
|
||||
React.ComponentType<React.PropsWithChildren<Props>> & {
|
||||
LazyComponentWrapper<React.ComponentType<React.PropsWithChildren<Props>> & {
|
||||
wrap<T extends object = any>(Component: React.ComponentType<T>, errorBoundaryProps?: Omit<Props<T>, "wrappedProps">): React.FunctionComponent<T>;
|
||||
};
|
||||
}>;
|
||||
|
||||
ErrorBoundary.wrap = (Component, errorBoundaryProps) => props => (
|
||||
<ErrorBoundary {...errorBoundaryProps} wrappedProps={props}>
|
||||
|
|
|
@ -179,20 +179,18 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti
|
|||
}
|
||||
}
|
||||
|
||||
function renderMoreUsers(_label: string, count: number) {
|
||||
const sliceCount = plugin.authors.length - count;
|
||||
const sliceStart = plugin.authors.length - sliceCount;
|
||||
const sliceEnd = sliceStart + plugin.authors.length - count;
|
||||
function renderMoreUsers(_label: string) {
|
||||
const remainingAuthors = plugin.authors.slice(6);
|
||||
|
||||
return (
|
||||
<Tooltip text={plugin.authors.slice(sliceStart, sliceEnd).map(u => u.name).join(", ")}>
|
||||
<Tooltip text={remainingAuthors.map(u => u.name).join(", ")}>
|
||||
{({ onMouseEnter, onMouseLeave }) => (
|
||||
<div
|
||||
className={AvatarStyles.moreUsers}
|
||||
onMouseEnter={onMouseEnter}
|
||||
onMouseLeave={onMouseLeave}
|
||||
>
|
||||
+{sliceCount}
|
||||
+{remainingAuthors.length}
|
||||
</div>
|
||||
)}
|
||||
</Tooltip>
|
||||
|
@ -250,7 +248,6 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti
|
|||
count={plugin.authors.length}
|
||||
guildId={undefined}
|
||||
renderIcon={false}
|
||||
max={6}
|
||||
showDefaultAvatarsForNullUsers
|
||||
showUserPopout
|
||||
renderMoreUsers={renderMoreUsers}
|
||||
|
|
|
@ -252,6 +252,24 @@
|
|||
}
|
||||
|
||||
.visual-refresh .button-danger-background:hover {
|
||||
background-color: var(--status-danger-background) !important;
|
||||
color: var(--status-danger-text) !important;
|
||||
background-color: var(--status-danger-background) !important;
|
||||
color: var(--status-danger-text) !important;
|
||||
}
|
||||
|
||||
.visual-refresh .vc-plugins-info-card {
|
||||
background-color: var(--card-primary-bg) !important;
|
||||
border: 1px solid var(--border-subtle) !important;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--card-primary-bg) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.visual-refresh .vc-plugin-stats {
|
||||
background-color: var(--card-primary-bg) !important;
|
||||
border: 1px solid var(--border-subtle) !important;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--card-primary-bg) !important;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,12 +18,13 @@
|
|||
|
||||
import { CodeBlock } from "@components/CodeBlock";
|
||||
import { debounce } from "@shared/debounce";
|
||||
import { copyToClipboard } from "@utils/clipboard";
|
||||
import { Margins } from "@utils/margins";
|
||||
import { canonicalizeMatch, canonicalizeReplace } from "@utils/patches";
|
||||
import { makeCodeblock } from "@utils/text";
|
||||
import { Patch, ReplaceFn } from "@utils/types";
|
||||
import { search } from "@webpack";
|
||||
import { Button, Clipboard, Forms, Parser, React, Switch, TextArea, TextInput } from "@webpack/common";
|
||||
import { Button, Forms, Parser, React, Switch, TextArea, TextInput } from "@webpack/common";
|
||||
|
||||
import { SettingsTab, wrapTab } from "./shared";
|
||||
|
||||
|
@ -378,8 +379,8 @@ function PatchHelper() {
|
|||
<>
|
||||
<Forms.FormTitle className={Margins.top20}>Code</Forms.FormTitle>
|
||||
<CodeBlock lang="js" content={code} />
|
||||
<Button onClick={() => Clipboard.copy(code)}>Copy to Clipboard</Button>
|
||||
<Button className={Margins.top8} onClick={() => Clipboard.copy("```ts\n" + code + "\n```")}>Copy as Codeblock</Button>
|
||||
<Button onClick={() => copyToClipboard(code)}>Copy to Clipboard</Button>
|
||||
<Button className={Margins.top8} onClick={() => copyToClipboard("```ts\n" + code + "\n```")}>Copy as Codeblock</Button>
|
||||
</>
|
||||
)}
|
||||
</SettingsTab>
|
||||
|
|
|
@ -106,8 +106,6 @@ function Updatable(props: CommonProps) {
|
|||
const [updates, setUpdates] = React.useState(changes);
|
||||
const [isChecking, setIsChecking] = React.useState(false);
|
||||
const [isUpdating, setIsUpdating] = React.useState(false);
|
||||
|
||||
const settings = useSettings(["updateRelaunch"]);
|
||||
const isOutdated = (updates?.length ?? 0) > 0;
|
||||
|
||||
return (
|
||||
|
@ -119,7 +117,6 @@ function Updatable(props: CommonProps) {
|
|||
onClick={withDispatcher(setIsUpdating, async () => {
|
||||
if (await update()) {
|
||||
setUpdates([]);
|
||||
if (settings.updateRelaunch) return relaunch();
|
||||
return await new Promise<void>(r => {
|
||||
Alerts.show({
|
||||
title: "Update Success!",
|
||||
|
@ -191,7 +188,7 @@ function Newer(props: CommonProps) {
|
|||
}
|
||||
|
||||
function Updater() {
|
||||
const settings = useSettings(["autoUpdate", "updateRelaunch", "autoUpdateNotification"]);
|
||||
const settings = useSettings(["autoUpdate", "autoUpdateNotification"]);
|
||||
|
||||
const [repo, err, repoPending] = useAwaiter(getRepo, { fallbackValue: "Loading..." });
|
||||
|
||||
|
@ -217,30 +214,12 @@ function Updater() {
|
|||
</Switch>
|
||||
<Switch
|
||||
value={settings.autoUpdateNotification}
|
||||
onChange={(v: boolean) => {
|
||||
settings.autoUpdateNotification = v;
|
||||
if (settings.updateRelaunch) {
|
||||
settings.updateRelaunch = !v;
|
||||
}
|
||||
}}
|
||||
onChange={(v: boolean) => settings.autoUpdateNotification = v}
|
||||
note="Shows a notification when Equicord automatically updates"
|
||||
disabled={!settings.autoUpdate}
|
||||
>
|
||||
Get notified when an automatic update completes
|
||||
</Switch>
|
||||
<Switch
|
||||
value={settings.updateRelaunch}
|
||||
onChange={(v: boolean) => {
|
||||
settings.updateRelaunch = v;
|
||||
if (settings.autoUpdateNotification) {
|
||||
settings.autoUpdateNotification = !v;
|
||||
}
|
||||
}}
|
||||
note="Relaunches the app after updating with no prompt"
|
||||
disabled={!settings.autoUpdate}
|
||||
>
|
||||
Automatically relaunch after updating
|
||||
</Switch>
|
||||
|
||||
<Forms.FormTitle tag="h5">Repo</Forms.FormTitle>
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ import "./VencordTab.css";
|
|||
import { openNotificationLogModal } from "@api/Notifications/notificationLog";
|
||||
import { useSettings } from "@api/Settings";
|
||||
import { classNameFactory } from "@api/Styles";
|
||||
import DonateButton, { InviteButton } from "@components/DonateButton";
|
||||
import { DonateButton, InviteButton } from "@components/DonateButton";
|
||||
import { openContributorModal } from "@components/PluginSettings/ContributorModal";
|
||||
import { openPluginModal } from "@components/PluginSettings/PluginModal";
|
||||
import { gitRemote } from "@shared/vencordUserAgent";
|
||||
|
@ -82,7 +82,7 @@ function EquicordSettings() {
|
|||
(!IS_DISCORD_DESKTOP || !isWindows
|
||||
? {
|
||||
key: "frameless",
|
||||
title: "Disable the window frame",
|
||||
title: "Disable the Window Frame",
|
||||
note: "Requires a full restart",
|
||||
warning: { enabled: false },
|
||||
}
|
||||
|
@ -95,7 +95,7 @@ function EquicordSettings() {
|
|||
}),
|
||||
!IS_WEB && {
|
||||
key: "transparent",
|
||||
title: "Enable window transparency.",
|
||||
title: "Enable Window Transparency",
|
||||
note: "You need a theme that supports transparency or this will do nothing. Requires a full restart!",
|
||||
warning: {
|
||||
enabled: isWindows,
|
||||
|
@ -112,7 +112,7 @@ function EquicordSettings() {
|
|||
},
|
||||
IS_DISCORD_DESKTOP && {
|
||||
key: "disableMinSize",
|
||||
title: "Disable minimum window size",
|
||||
title: "Disable Minimum Window Size",
|
||||
note: "Requires a full restart",
|
||||
warning: { enabled: false },
|
||||
},
|
||||
|
|
|
@ -48,7 +48,7 @@ async function runReporter() {
|
|||
|
||||
for (const patch of patches) {
|
||||
if (!patch.all) {
|
||||
new Logger("WebpackInterceptor").warn(`Patch by ${patch.plugin} found no module (Module id is -): ${patch.find}`);
|
||||
new Logger("WebpackPatcher").warn(`Patch by ${patch.plugin} found no module (Module id is -): ${patch.find}`);
|
||||
if (IS_COMPANION_TEST)
|
||||
reporterData.failedPatches.foundNoModule.push(patch);
|
||||
}
|
||||
|
@ -56,7 +56,7 @@ async function runReporter() {
|
|||
|
||||
for (const [plugin, moduleId, match, totalTime] of patchTimings) {
|
||||
if (totalTime > 5) {
|
||||
new Logger("WebpackInterceptor").warn(`Patch by ${plugin} took ${Math.round(totalTime * 100) / 100}ms (Module id is ${String(moduleId)}): ${match}`);
|
||||
new Logger("WebpackPatcher").warn(`Patch by ${plugin} took ${Math.round(totalTime * 100) / 100}ms (Module id is ${String(moduleId)}): ${match}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -92,7 +92,7 @@ async function runReporter() {
|
|||
result = Webpack[method](...args);
|
||||
}
|
||||
|
||||
if (result == null || (result.$$vencordInternal != null && result.$$vencordInternal() == null)) throw new Error("Webpack Find Fail");
|
||||
if (result == null || (result.$$vencordGetWrappedComponent != null && result.$$vencordGetWrappedComponent() == null)) throw new Error("Webpack Find Fail");
|
||||
} catch (e) {
|
||||
let logMessage = searchType;
|
||||
if (method === "find" || method === "proxyLazyWebpack" || method === "LazyComponentWebpack") {
|
||||
|
|
|
@ -18,10 +18,26 @@
|
|||
|
||||
import "@equicordplugins/_misc/styles.css";
|
||||
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import { disableStyle, enableStyle } from "@api/Styles";
|
||||
import { EquicordDevs } from "@utils/constants";
|
||||
import definePlugin from "@utils/types";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import { Forms } from "@webpack/common";
|
||||
|
||||
import clanBadges from "../_misc/clanBadges.css?managed";
|
||||
|
||||
const settings = definePluginSettings({
|
||||
hideClanBadges: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Hide clan badges",
|
||||
default: false,
|
||||
onChange: value => {
|
||||
if (value) enableStyle(clanBadges);
|
||||
else disableStyle(clanBadges);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export default definePlugin({
|
||||
name: "EquicordHelper",
|
||||
description: "Fixes some misc issues with discord",
|
||||
|
@ -31,6 +47,7 @@ export default definePlugin({
|
|||
This Plugin is used for fixing misc issues with discord such as some crashes
|
||||
</Forms.FormText>
|
||||
</>,
|
||||
settings,
|
||||
required: true,
|
||||
patches: [
|
||||
{
|
||||
|
@ -45,13 +62,12 @@ export default definePlugin({
|
|||
replace: "return $1;"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
find: '"Slate: Unable to find syntax characters"',
|
||||
replacement: {
|
||||
match: /((let )(\i)=\i\.indexOf\(\i,(\i)\)),/,
|
||||
replace: "$1;if ($3 === -1) {return $4;}$2"
|
||||
}
|
||||
}
|
||||
]
|
||||
],
|
||||
start() {
|
||||
if (settings.store.hideClanBadges) enableStyle(clanBadges);
|
||||
},
|
||||
stop() {
|
||||
if (settings.store.hideClanBadges) disableStyle(clanBadges);
|
||||
}
|
||||
});
|
||||
|
|
3
src/equicordplugins/_misc/clanBadges.css
Normal file
3
src/equicordplugins/_misc/clanBadges.css
Normal file
|
@ -0,0 +1,3 @@
|
|||
[class*="chipletContainerInner_"]:has([src *="/clan-badges/"]) {
|
||||
display: none;
|
||||
}
|
69
src/equicordplugins/autoJump/index.tsx
Normal file
69
src/equicordplugins/autoJump/index.tsx
Normal file
|
@ -0,0 +1,69 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2024 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { NavContextMenuPatchCallback } from "@api/ContextMenu";
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import { EquicordDevs } from "@utils/constants";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import { ChannelStore, Menu, MessageActions, NavigationRouter } from "@webpack/common";
|
||||
|
||||
interface ChannelSelectEvent {
|
||||
type: "CHANNEL_SELECT";
|
||||
channelId: string | null;
|
||||
guildId: string | null;
|
||||
}
|
||||
|
||||
let lastChannelId = "0";
|
||||
|
||||
function autoJump({ guild_id, id: channelId }) {
|
||||
const guildId = guild_id ?? "@me";
|
||||
|
||||
lastChannelId = channelId;
|
||||
NavigationRouter.transitionTo(`/channels/${guildId}/${channelId}`);
|
||||
MessageActions.jumpToPresent(channelId, { limit: null });
|
||||
}
|
||||
|
||||
const MenuPatch: NavContextMenuPatchCallback = (children, { channel }) => {
|
||||
children.push(
|
||||
<Menu.MenuItem
|
||||
id="auto-jump"
|
||||
label="Jump to Last Message"
|
||||
action={() => {
|
||||
autoJump(channel);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const settings = definePluginSettings({
|
||||
autoJumping: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Automatically jump to the last message in the channel when switching channels",
|
||||
default: false
|
||||
}
|
||||
});
|
||||
|
||||
export default definePlugin({
|
||||
name: "AutoJump",
|
||||
description: "Jumps to Last Message in Channel",
|
||||
authors: [EquicordDevs.omaw],
|
||||
settings,
|
||||
contextMenus: {
|
||||
"channel-context": MenuPatch,
|
||||
"user-context": MenuPatch,
|
||||
"thread-context": MenuPatch
|
||||
},
|
||||
flux: {
|
||||
async CHANNEL_SELECT({ guildId, channelId }: ChannelSelectEvent) {
|
||||
if (!settings.store.autoJumping || !channelId) return;
|
||||
|
||||
const channel = ChannelStore.getChannel(channelId);
|
||||
if (!channel || channel.id === lastChannelId) return;
|
||||
|
||||
autoJump({ guild_id: guildId, id: channelId });
|
||||
}
|
||||
}
|
||||
});
|
|
@ -58,8 +58,8 @@ export default definePlugin({
|
|||
replacement: [
|
||||
{
|
||||
// We add the banner as a property while we can still access the user id
|
||||
match: /(?<=nameplate:(\i).*?)verified:(\i).isVerifiedBot.*?name:null.*?(?=avatar:)/,
|
||||
replace: "$&banner:$self.memberListBannerHook($2, $1),",
|
||||
match: /user:(\i).{0,150}nameplate:(\i).*?name:null.*?(?=avatar:)/,
|
||||
replace: "$&banner:$self.memberListBannerHook($1, $2),",
|
||||
},
|
||||
{
|
||||
match: /(?<=\),nameplate:)(\i)/,
|
||||
|
@ -112,7 +112,7 @@ export default definePlugin({
|
|||
}
|
||||
|
||||
return (
|
||||
<img id={`vc-banners-everywhere-${user.id}`} src={url} className="vc-banners-everywhere-memberlist"></img>
|
||||
<img alt="" id={`vc-banners-everywhere-${user.id}`} src={url} className="vc-banners-everywhere-memberlist"></img>
|
||||
);
|
||||
},
|
||||
|
||||
|
|
|
@ -35,7 +35,7 @@ export default definePlugin({
|
|||
patches: [
|
||||
{
|
||||
// Patch activity icons
|
||||
find: '"activity-status-web"',
|
||||
find: "isBlockedOrIgnored(null",
|
||||
replacement: {
|
||||
match: /(?<=hideTooltip:.{0,4}}=(\i).*?{}\))\]/,
|
||||
replace: ",$self.patchActivityList($1)]"
|
||||
|
@ -44,9 +44,9 @@ export default definePlugin({
|
|||
},
|
||||
{
|
||||
// Show all activities in the user popout/sidebar
|
||||
find: '"UserProfilePopoutBody"',
|
||||
find: "hasAvatarForGuild(null",
|
||||
replacement: {
|
||||
match: /(?<=(\i)\.id\)\}\)\),(\i).*?)\(0,.{0,100}\i\.activity\}\)/,
|
||||
match: /(?<=(\i)\.id\)\}\)\),(\i).*?)\(0,.{0,100}\i\.id,onClose:\i\}\)/,
|
||||
replace: "$self.showAllActivitiesComponent({ activity: $2, user: $1 })"
|
||||
},
|
||||
predicate: () => settings.store.userPopout
|
||||
|
|
|
@ -66,7 +66,7 @@ async function addListeners(audioElement: HTMLAudioElement, url: string, parentB
|
|||
const madeURL = new URL(url);
|
||||
madeURL.searchParams.set("t", Date.now().toString());
|
||||
|
||||
const corsProxyUrl = "https://corsproxy.io?" + encodeURIComponent(madeURL.href);
|
||||
const corsProxyUrl = "https://corsproxy.io/?url=" + encodeURIComponent(madeURL.href);
|
||||
const response = await fetch(corsProxyUrl);
|
||||
const blob = await response.blob();
|
||||
const blobUrl = URL.createObjectURL(blob);
|
||||
|
|
|
@ -4,53 +4,55 @@
|
|||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import "./style.css";
|
||||
import "./styles.css";
|
||||
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import { classNameFactory } from "@api/Styles";
|
||||
import { DeleteIcon, PlusIcon } from "@components/Icons";
|
||||
import { Devs } from "@utils/constants";
|
||||
import { getIntlMessage } from "@utils/discord";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import { Button, Forms, TextInput } from "@webpack/common";
|
||||
|
||||
const cl = classNameFactory("vc-bbr-");
|
||||
|
||||
function ReasonsComponent() {
|
||||
const { reasons } = settings.use(["reasons"]);
|
||||
const { reasons } = settings.store;
|
||||
|
||||
return (
|
||||
<Forms.FormSection title="Reasons">
|
||||
{reasons.map((reason: string, index: number) => (
|
||||
{reasons.map((r, i) => (
|
||||
<div
|
||||
className="vc-bbr-reason-wrapper"
|
||||
key={index}
|
||||
key={i}
|
||||
className={cl("reason-wrapper")}
|
||||
>
|
||||
<TextInput
|
||||
type="text"
|
||||
key={index}
|
||||
value={reason}
|
||||
value={r}
|
||||
onChange={v => {
|
||||
reasons[index] = v;
|
||||
settings.store.reasons = [...reasons];
|
||||
reasons[i] = v;
|
||||
settings.store.reasons = reasons;
|
||||
}}
|
||||
placeholder="Reason"
|
||||
/>
|
||||
<Button
|
||||
color={Button.Colors.RED}
|
||||
className="vc-bbr-remove-button"
|
||||
className={cl("remove-button")}
|
||||
color={Button.Colors.TRANSPARENT}
|
||||
onClick={() => {
|
||||
reasons.splice(index, 1);
|
||||
settings.store.reasons = [...reasons];
|
||||
reasons.splice(i, 1);
|
||||
settings.store.reasons = reasons;
|
||||
}}
|
||||
look={Button.Looks.BLANK}
|
||||
size={Button.Sizes.MIN}
|
||||
>
|
||||
Remove
|
||||
<DeleteIcon />
|
||||
</Button>
|
||||
</div>
|
||||
))}
|
||||
<Button
|
||||
onClick={() => {
|
||||
settings.store.reasons = [...reasons, ""];
|
||||
}}
|
||||
>
|
||||
Add new
|
||||
</Button>
|
||||
<div className={cl("reason-wrapper")}>
|
||||
<Button onClick={() => settings.store.reasons.push("")} className={cl("add-button")} size={Button.Sizes.LARGE} color={Button.Colors.TRANSPARENT}>
|
||||
<PlusIcon /> Add another reason
|
||||
</Button>
|
||||
</div>
|
||||
</Forms.FormSection>
|
||||
);
|
||||
}
|
||||
|
@ -59,10 +61,10 @@ const settings = definePluginSettings({
|
|||
reasons: {
|
||||
description: "Your custom reasons",
|
||||
type: OptionType.COMPONENT,
|
||||
default: [""],
|
||||
default: [] as string[],
|
||||
component: ReasonsComponent,
|
||||
},
|
||||
textInputDefault: {
|
||||
isTextInputDefault: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: 'Shows a text input instead of a select menu by default. (Equivalent to clicking the "Other" option)'
|
||||
}
|
||||
|
@ -74,9 +76,9 @@ export default definePlugin({
|
|||
authors: [Devs.Inbestigator],
|
||||
patches: [
|
||||
{
|
||||
find: "#{intl::BAN_MULTIPLE_CONFIRM_TITLE}",
|
||||
find: "#{intl::BAN_REASON_OPTION_SPAM_ACCOUNT}",
|
||||
replacement: [{
|
||||
match: /\[\{name:\i\.\i\.string\(\i\.\i#{intl::BAN_REASON_OPTION_SPAM_ACCOUNT}\).+?\}\]/,
|
||||
match: /\[(\{((name|value):\i\.\i\.string\(\i\.\i\.\i\),?){2}\},?){3}\]/,
|
||||
replace: "$self.getReasons()"
|
||||
},
|
||||
{
|
||||
|
@ -86,17 +88,16 @@ export default definePlugin({
|
|||
}
|
||||
],
|
||||
getReasons() {
|
||||
const reasons = settings.store.reasons.length
|
||||
? settings.store.reasons
|
||||
const storedReasons = settings.store.reasons.filter((r: string) => r.trim());
|
||||
const reasons: string[] = storedReasons.length
|
||||
? storedReasons
|
||||
: [
|
||||
getIntlMessage("BAN_REASON_OPTION_SPAM_ACCOUNT"),
|
||||
getIntlMessage("BAN_REASON_OPTION_HACKED_ACCOUNT"),
|
||||
getIntlMessage("BAN_REASON_OPTION_BREAKING_RULES")
|
||||
getIntlMessage("BAN_REASON_OPTION_BREAKING_RULES"),
|
||||
];
|
||||
return reasons.map(s => ({ name: s, value: s }));
|
||||
},
|
||||
getDefaultState() {
|
||||
return settings.store.textInputDefault ? 1 : 0;
|
||||
},
|
||||
getDefaultState: () => settings.store.isTextInputDefault ? 1 : 0,
|
||||
settings,
|
||||
});
|
||||
|
|
|
@ -1,11 +0,0 @@
|
|||
.vc-bbr-reason-wrapper {
|
||||
display: grid;
|
||||
padding: 0;
|
||||
padding-bottom: 0.5rem;
|
||||
gap: 0.5rem;
|
||||
grid-template-columns: 6fr 1fr;
|
||||
}
|
||||
|
||||
.vc-bbr-remove-button {
|
||||
height: 100%;
|
||||
}
|
31
src/equicordplugins/betterBanReasons/styles.css
Normal file
31
src/equicordplugins/betterBanReasons/styles.css
Normal file
|
@ -0,0 +1,31 @@
|
|||
.vc-bbr-reason-wrapper {
|
||||
display: grid;
|
||||
padding-bottom: 12px;
|
||||
gap: 4px 12px;
|
||||
align-items: center;
|
||||
grid-template-columns: 1fr 24px;
|
||||
}
|
||||
|
||||
.vc-bbr-remove-button {
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.vc-bbr-remove-button:hover {
|
||||
color: var(--button-danger-background-hover);
|
||||
}
|
||||
|
||||
.vc-bbr-add-button {
|
||||
justify-content: start !important;
|
||||
border: 0;
|
||||
padding: 2px 12px;
|
||||
color: var(--text-muted) !important;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.vc-bbr-add-button div {
|
||||
display: flex;
|
||||
justify-content: start;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
margin: 0 !important;
|
||||
}
|
|
@ -4,58 +4,23 @@
|
|||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import { Devs, EquicordDevs } from "@utils/constants";
|
||||
import { openUserProfile } from "@utils/discord";
|
||||
import { openModal } from "@utils/modal";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import { findByCodeLazy, findByPropsLazy, findComponentByCodeLazy } from "@webpack";
|
||||
import { Button, FluxDispatcher, React, RelationshipStore, Text, TextInput, UserStore } from "@webpack/common";
|
||||
import { ButtonProps } from "@webpack/types";
|
||||
import { User } from "discord-types/general";
|
||||
import "./styles.css";
|
||||
|
||||
import { EquicordDevs } from "@utils/constants";
|
||||
import { getIntlMessage, openUserProfile } from "@utils/discord";
|
||||
import definePlugin from "@utils/types";
|
||||
import { findByPropsLazy } from "@webpack";
|
||||
import { Button, React, RelationshipStore, TextInput, UserStore } from "@webpack/common";
|
||||
|
||||
const ChannelActions = findByPropsLazy("openPrivateChannel");
|
||||
|
||||
let lastSearch = "";
|
||||
let updateFunc = (v: any) => { };
|
||||
|
||||
const ChannelActions = findByPropsLazy("openPrivateChannel");
|
||||
const ButtonComponent = findComponentByCodeLazy('submittingStartedLabel","submittingFinishedLabel"]);');
|
||||
const ConfirmationModal = findByCodeLazy('"ConfirmModal")', "useLayoutEffect");
|
||||
|
||||
const settings = definePluginSettings({
|
||||
addDmsButton: {
|
||||
default: true,
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Adds a 'View DMs' button to the users in the blocked/ignored list.",
|
||||
},
|
||||
hideBlockedWarning: {
|
||||
default: false,
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Skip the warning about blocked/ignored users when opening any profile anywhere on discord outside of the blocklist.",
|
||||
restartNeeded: true,
|
||||
},
|
||||
showUnblockConfirmation: {
|
||||
default: true,
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Show a warning before unblocking a user from the blocklist.",
|
||||
},
|
||||
showUnblockConfirmationEverywhere: {
|
||||
default: false,
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Show a warning before unblocking a user anywhere on discord.",
|
||||
restartNeeded: true,
|
||||
},
|
||||
unblockButtonDanger: {
|
||||
default: false,
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Color the unblock button in the blocklist red instead of gray.",
|
||||
},
|
||||
});
|
||||
|
||||
export default definePlugin({
|
||||
name: "BetterBlockedUsers",
|
||||
description: "Allows you to search in blocked users list and makes names clickable in settings.",
|
||||
authors: [EquicordDevs.TheArmagan, Devs.Elvyra],
|
||||
settings,
|
||||
description: "Allows you to search in blocked users list and makes names selectable in settings.",
|
||||
authors: [EquicordDevs.TheArmagan],
|
||||
patches: [
|
||||
{
|
||||
find: '"],{numberOfBlockedUsers:',
|
||||
|
@ -65,12 +30,8 @@ export default definePlugin({
|
|||
replace: ",$1.listType==='blocked'?$self.renderSearchInput():null"
|
||||
},
|
||||
{
|
||||
match: /(?<=className:\i.userInfo,)(?=children:.{0,20}user:(\i))/,
|
||||
replace: "style:{cursor:'pointer'},onClick:()=>$self.openUserProfile($1),"
|
||||
},
|
||||
{
|
||||
match: /(?<=children:null!=(\i).globalName\?.+?}\),).*?(\{color:.{0,65}?string\((\i).+?"8wXU9P"]\)})\)/,
|
||||
replace: "$self.generateButtons({user:$1, originalProps:$2, isBlocked:$3})",
|
||||
match: /(?<=userId:(\i).*?\}\)\]\}\),)(\(.*?\)\}\))/,
|
||||
replace: "$self.renderUser($1,$2),",
|
||||
},
|
||||
{
|
||||
match: /(?<=\}=(\i).{0,10}(\i).useState\(.{0,1}\);)/,
|
||||
|
@ -81,64 +42,6 @@ export default definePlugin({
|
|||
replace: "$1(searchResults.length?searchResults:$2)"
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
find: "UserProfileModalHeaderActionButtons",
|
||||
replacement: [
|
||||
{
|
||||
match: /(?<=return \i)\|\|(\i)===.*?.FRIEND/,
|
||||
replace: (_, type) => `?null:${type} === 1|| ${type} === 2`,
|
||||
},
|
||||
{
|
||||
match: /(?<=\i.bot.{0,50}children:.*?onClose:)(\i)/,
|
||||
replace: "() => {$1();$self.closeSettingsWindow()}",
|
||||
}
|
||||
],
|
||||
},
|
||||
{
|
||||
find: ',["user"])',
|
||||
replacement: {
|
||||
match: /(?<=isIgnored:.*?,\[\i,\i]=\i.useState\()\i\|\|\i\|\|\i.*?]\);/,
|
||||
replace: "false);"
|
||||
},
|
||||
},
|
||||
|
||||
// If the users wishes to, they can disable the warning in all other places as well.
|
||||
...[
|
||||
"UserProfilePanelWrapper: currentUser cannot be undefined",
|
||||
"UserProfilePopoutWrapper: currentUser cannot be undefined",
|
||||
].map(x => ({
|
||||
find: x,
|
||||
replacement: {
|
||||
match: /(?<=isIgnored:.*?,\[\i,\i]=\i.useState\()\i\|\|\i\|\|\i\)(?:;\i.useEffect.*?]\))?/,
|
||||
replace: "false)",
|
||||
},
|
||||
predicate: () => settings.store.hideBlockedWarning,
|
||||
})),
|
||||
|
||||
{
|
||||
find: ".BLOCKED:return",
|
||||
replacement: {
|
||||
match: /(?<=\i.BLOCKED:return.{0,65}onClick:)\(\)=>\{(\i.\i.unblockUser\((\i).+?}\))/,
|
||||
replace: "(event) => {$self.openConfirmationModal(event,()=>{$1}, $2)",
|
||||
},
|
||||
predicate: () => settings.store.showUnblockConfirmationEverywhere,
|
||||
},
|
||||
{
|
||||
find: "#{intl::UNBLOCK}),",
|
||||
replacement: {
|
||||
match: /(?<=#{intl::UNBLOCK}.+?Click=)\(\)=>(\{.+?(\i.getRecipientId\(\))\)})/,
|
||||
replace: "event => $self.openConfirmationModal(event, ()=>$1, $2)",
|
||||
},
|
||||
predicate: () => settings.store.showUnblockConfirmationEverywhere,
|
||||
},
|
||||
{
|
||||
find: "#{intl::BLOCK}),action",
|
||||
replacement: {
|
||||
match: /(?<=id:"block".{0,100}action:\i\?)\(\)=>(\{.{0,25}unblockUser\((\i).{0,60}:void 0\)})/,
|
||||
replace: "event => {$self.openConfirmationModal(event, ()=>$1,$2)}",
|
||||
},
|
||||
predicate: () => settings.store.showUnblockConfirmationEverywhere,
|
||||
}
|
||||
],
|
||||
renderSearchInput() {
|
||||
|
@ -161,6 +64,19 @@ export default definePlugin({
|
|||
}} value={value}
|
||||
></TextInput>;
|
||||
},
|
||||
renderUser(userId: string, rest: any) {
|
||||
return (
|
||||
<div style={{ display: "flex", gap: "8px" }}>
|
||||
<Button color={Button.Colors.PRIMARY} onClick={() => openUserProfile(userId)}>
|
||||
{getIntlMessage("SHOW_USER_PROFILE")}
|
||||
</Button>
|
||||
{rest}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
getSearchResults() {
|
||||
return !!lastSearch;
|
||||
},
|
||||
setUpdateFunc(e, setResults) {
|
||||
if (e.listType !== "blocked") return;
|
||||
updateFunc = setResults;
|
||||
|
@ -173,72 +89,5 @@ export default definePlugin({
|
|||
if (!user) return id === search;
|
||||
return id === search || user?.username?.toLowerCase()?.includes(search) || user?.globalName?.toLowerCase()?.includes(search);
|
||||
}) as string[];
|
||||
},
|
||||
closeSettingsWindow() {
|
||||
FluxDispatcher.dispatch({ type: "LAYER_POP" });
|
||||
},
|
||||
openUserProfile(user: User) {
|
||||
openUserProfile(user.id);
|
||||
},
|
||||
generateButtons(props: { user: User, originalProps: ButtonProps, isBlocked: boolean; }) {
|
||||
const { user, originalProps, isBlocked } = props;
|
||||
|
||||
if (settings.store.unblockButtonDanger) originalProps.color = Button.Colors.RED;
|
||||
|
||||
// TODO add extra unblock confirmation after the click + setting.
|
||||
|
||||
if (settings.store.showUnblockConfirmation || settings.store.showUnblockConfirmationEverywhere) {
|
||||
const originalOnClick = originalProps.onClick!;
|
||||
originalProps.onClick = e => {
|
||||
if (!isBlocked) return originalOnClick(e);
|
||||
this.openConfirmationModal(e as unknown as MouseEvent, () => originalOnClick(e), user, true);
|
||||
};
|
||||
}
|
||||
|
||||
const unblockButton = <ButtonComponent {...originalProps} />;
|
||||
|
||||
if (!settings.store.addDmsButton) return unblockButton;
|
||||
|
||||
const dmButton = <ButtonComponent color={Button.Colors.BRAND_NEW} onClick={() => this.openDMChannel(user)}>Show DMs</ButtonComponent>;
|
||||
|
||||
return <div style={{ display: "flex", gap: "8px" }} className="vc-bbc-button-container">
|
||||
{dmButton}
|
||||
{unblockButton}
|
||||
</div>;
|
||||
},
|
||||
|
||||
openDMChannel(user: User) {
|
||||
ChannelActions.openPrivateChannel(user.id);
|
||||
this.closeSettingsWindow();
|
||||
return null;
|
||||
},
|
||||
openConfirmationModal(event: MouseEvent, callback: () => any, user: User | string, isSettingsOrigin: boolean = false) {
|
||||
if (event.shiftKey) return callback();
|
||||
|
||||
if (typeof user === "string") {
|
||||
user = UserStore.getUser(user);
|
||||
}
|
||||
|
||||
return openModal(m => <ConfirmationModal
|
||||
{...m}
|
||||
className="vc-bbc-confirmation-modal"
|
||||
header={`Unblock ${user?.username ?? "?"}?`}
|
||||
cancelText="Cancel"
|
||||
confirmText="Unblock"
|
||||
onConfirm={() => {
|
||||
callback();
|
||||
}}>
|
||||
<div style={{ display: "flex", flexDirection: "column", gap: "16px" }} className="vc-bbc-confirmation-modal-text">
|
||||
<div style={{ display: "flex", flexDirection: "column", gap: "8px" }}>
|
||||
<Text variant="text-md/semibold">{`Are you sure you want to unblock ${user?.username ?? "this user"}?`}</Text>
|
||||
<Text variant="text-md/normal">{`This will allow ${user?.username ?? "them"} to see your profile and message you again.`}</Text>
|
||||
</div>
|
||||
<Text variant="text-md/normal">{"You can always block them again later."}</Text>
|
||||
{isSettingsOrigin ? <div style={{ display: "flex", flexDirection: "column", gap: "8px" }}>
|
||||
<Text variant="text-sm/medium" style={{ color: "var(--text-muted)" }}>{"If you just want to read the chat logs instead, you can just click on their profile."}</Text>
|
||||
<Text variant="text-sm/normal" style={{ color: "var(--text-muted)" }}>{"Alternatively, you can enable a button to jump to DMs in the blocklist through the plugin settings."}</Text>
|
||||
</div> : <Text variant="text-sm/medium" style={{ color: "var(--text-muted)" }}>{"If you just want to read the chat logs, you can do this without unblocking them."}</Text>}
|
||||
</div>
|
||||
</ConfirmationModal>);
|
||||
},
|
||||
}
|
||||
});
|
||||
|
|
3
src/equicordplugins/betterBlockedUsers/styles.css
Normal file
3
src/equicordplugins/betterBlockedUsers/styles.css
Normal file
|
@ -0,0 +1,3 @@
|
|||
[class*="usersList_"] [class*="text_"] {
|
||||
user-select: text !important;
|
||||
}
|
49
src/equicordplugins/bypassPinPrompt/index.ts
Normal file
49
src/equicordplugins/bypassPinPrompt/index.ts
Normal file
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2025 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { EquicordDevs } from "@utils/constants";
|
||||
import definePlugin from "@utils/types";
|
||||
|
||||
export default definePlugin({
|
||||
name: "BypassPinPrompt",
|
||||
description: "Bypass the pin prompt when pinning messages",
|
||||
authors: [EquicordDevs.thororen],
|
||||
patches: [
|
||||
{
|
||||
find: '"Channel Pins"',
|
||||
replacement: {
|
||||
match: /(?<=(\i\.\i\.unpinMessage\(\i,\i\.id\)):)\i\.\i\.confirmUnpin\(\i,\i\)/,
|
||||
replace: "$1"
|
||||
}
|
||||
},
|
||||
{
|
||||
find: 'source:"message-actions"',
|
||||
replacement: [
|
||||
{
|
||||
match: /(?<=(\i\.\i\.pinMessage\(\i,\i\.id\)):)\i\.\i\.confirmPin\(\i,\i\)/,
|
||||
replace: "$1"
|
||||
},
|
||||
{
|
||||
match: /(?<=(\i\.\i\.unpinMessage\(\i,\i\.id\)):)\i\.\i\.confirmUnpin\(\i,\i\)/,
|
||||
replace: "$1"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
find: 'id:"pin"',
|
||||
replacement: [
|
||||
{
|
||||
match: /(?<=(\i\.\i\.pinMessage\(\i,\i\.id\)):)\i\.\i\.confirmPin\(\i,\i\)/,
|
||||
replace: "$1"
|
||||
},
|
||||
{
|
||||
match: /(?<=(\i\.\i\.unpinMessage\(\i,\i\.id\)):)\i\.\i\.confirmUnpin\(\i,\i\)/,
|
||||
replace: "$1"
|
||||
}
|
||||
]
|
||||
},
|
||||
],
|
||||
});
|
|
@ -20,6 +20,8 @@ const PlusSmallIcon = findComponentByCodeLazy("0v-5h5a1");
|
|||
|
||||
const cl = classNameFactory("vc-channeltabs-");
|
||||
|
||||
const isMac = navigator.platform.toLowerCase().startsWith("mac");
|
||||
|
||||
export default function ChannelsTabsContainer(props: BasicChannelTabsProps) {
|
||||
const [userId, setUserId] = useState("");
|
||||
const { showBookmarkBar, widerTabsAndBookmarks } = settings.use(["showBookmarkBar", "widerTabsAndBookmarks"]);
|
||||
|
@ -66,6 +68,7 @@ export default function ChannelsTabsContainer(props: BasicChannelTabsProps) {
|
|||
className={cl("container")}
|
||||
ref={ref}
|
||||
onContextMenu={e => ContextMenuApi.openContextMenu(e, () => <BasicContextMenu />)}
|
||||
style={{ marginTop: isMac ? "28px" : "0" }}
|
||||
>
|
||||
<div className={cl("tab-container")}>
|
||||
{openedTabs.map((tab, i) =>
|
||||
|
|
|
@ -48,7 +48,7 @@ export default definePlugin({
|
|||
{
|
||||
find: ".COLLECTIBLES_SHOP_FULLSCREEN))",
|
||||
replacement: {
|
||||
match: /(\?void 0:(\i)\.channelId.{0,300}return)((.{0,15})"div",{.*?\])(\}\)\}\})/,
|
||||
match: /(\?void 0:(\i)\.channelId.{0,500}return)((.{0,15})"div",{.*?\])(\}\)\}\})/,
|
||||
replace: "$1$4$self.render,{currentChannel:$2,children:$3})$5"
|
||||
}
|
||||
},
|
||||
|
|
|
@ -152,8 +152,8 @@ export default definePlugin({
|
|||
{
|
||||
find: "._areActivitiesExperimentallyHidden=(",
|
||||
replacement: {
|
||||
match: /BOOST_GEM_ICON\}\}\)\)\};/,
|
||||
replace: "$&if($self.shouldHideUser(this.props.user.id, this.props.channel.id)) return null; "
|
||||
match: /(?<=user:(\i),guildId:\i,channel:(\i).*?)BOOST_GEM_ICON.{0,10}\);/,
|
||||
replace: "$&if($self.shouldHideUser($1.id, $2.id)) return null; "
|
||||
}
|
||||
},
|
||||
// stop the role header from displaying if all users with that role are hidden (wip sorta)
|
||||
|
|
|
@ -6,9 +6,10 @@
|
|||
|
||||
import { showNotification } from "@api/Notifications";
|
||||
import { Settings } from "@api/Settings";
|
||||
import { copyToClipboard } from "@utils/clipboard";
|
||||
import { relaunch, showItemInFolder } from "@utils/native";
|
||||
import { checkForUpdates, getRepo } from "@utils/updater";
|
||||
import { Clipboard, GuildStore, NavigationRouter, SettingsRouter, Toasts } from "@webpack/common";
|
||||
import { GuildStore, NavigationRouter, SettingsRouter, Toasts } from "@webpack/common";
|
||||
|
||||
import gitHash from "~git-hash";
|
||||
import gitRemote from "~git-remote";
|
||||
|
@ -89,7 +90,7 @@ export const actions: ButtonAction[] = [
|
|||
const newUrl = url.replace(/(https?:\/\/)?([a-zA-Z0-9-]+)\.([a-zA-Z0-9-]+)/, "https://$2.$3");
|
||||
const res = (await fetch(newUrl));
|
||||
const text = await res.text();
|
||||
Clipboard.copy(text);
|
||||
copyToClipboard(text);
|
||||
|
||||
Toasts.show({
|
||||
message: "Copied response to clipboard!",
|
||||
|
@ -115,7 +116,7 @@ export const actions: ButtonAction[] = [
|
|||
|
||||
{
|
||||
id: "copyGitInfo", label: "Copy Git Info", callback: async () => {
|
||||
Clipboard.copy(`gitHash: ${gitHash}\ngitRemote: ${gitRemote}`);
|
||||
copyToClipboard(`gitHash: ${gitHash}\ngitRemote: ${gitRemote}`);
|
||||
|
||||
Toasts.show({
|
||||
message: "Copied git info to clipboard!",
|
||||
|
|
107
src/equicordplugins/copyProfileColors/index.tsx
Normal file
107
src/equicordplugins/copyProfileColors/index.tsx
Normal file
|
@ -0,0 +1,107 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2025 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { addContextMenuPatch, NavContextMenuPatchCallback, removeContextMenuPatch } from "@api/ContextMenu";
|
||||
import { copyToClipboard } from "@utils/clipboard";
|
||||
import { EquicordDevs } from "@utils/constants";
|
||||
import definePlugin from "@utils/types";
|
||||
import { Menu, Toasts, UserProfileStore } from "@webpack/common";
|
||||
|
||||
function getProfileColors(userId) {
|
||||
try {
|
||||
const profile = UserProfileStore.getUserProfile(userId);
|
||||
|
||||
if (!profile || !profile.themeColors || profile.themeColors.length < 2) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const primaryColor = profile.themeColors[0].toString(16).padStart(6, "0");
|
||||
const secondaryColor = profile.themeColors[1].toString(16).padStart(6, "0");
|
||||
|
||||
return { primaryColor, secondaryColor };
|
||||
} catch (e) {
|
||||
console.error("Failed to get profile colors:", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function copyProfileColors(userId) {
|
||||
const colors = getProfileColors(userId);
|
||||
|
||||
if (!colors) {
|
||||
Toasts.show({
|
||||
type: Toasts.Type.FAILURE,
|
||||
message: "No profile colors found!",
|
||||
id: Toasts.genId()
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const { primaryColor, secondaryColor } = colors;
|
||||
|
||||
// Formatting
|
||||
const formattedColors = `Primary-color #${primaryColor}, Secondary-Color #${secondaryColor}`;
|
||||
|
||||
try {
|
||||
copyToClipboard(formattedColors);
|
||||
Toasts.show({
|
||||
type: Toasts.Type.SUCCESS,
|
||||
message: "Profile colors copied to clipboard!",
|
||||
id: Toasts.genId()
|
||||
});
|
||||
} catch (e) {
|
||||
console.error("Failed to copy to clipboard:", e);
|
||||
Toasts.show({
|
||||
type: Toasts.Type.FAILURE,
|
||||
message: "Error copying profile colors!",
|
||||
id: Toasts.genId()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function ColorIcon() {
|
||||
return (
|
||||
<svg
|
||||
viewBox="0 0 24 24"
|
||||
width="20"
|
||||
height="20"
|
||||
fill="#94b3e4"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path d="M17,4H15.82A3,3,0,0,0,13,2H11A3,3,0,0,0,8.18,4H7A3,3,0,0,0,4,7V19a3,3,0,0,0,3,3H17a3,3,0,0,0,3-3V7A3,3,0,0,0,17,4ZM10,5a1,1,0,0,1,1-1h2a1,1,0,0,1,1,1V6H10Zm8,14a1,1,0,0,1-1,1H7a1,1,0,0,1-1-1V7A1,1,0,0,1,7,6H8V7A1,1,0,0,0,9,8h6a1,1,0,0,0,1-1V6h1a1,1,0,0,1,1,1Z" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
// spawn in the context menu
|
||||
const userContextMenuPatch: NavContextMenuPatchCallback = (children, { user }) => {
|
||||
if (!user) return;
|
||||
children.push(
|
||||
<Menu.MenuItem
|
||||
id="CopyProfileColors"
|
||||
icon={ColorIcon}
|
||||
label={<span style={{ color: "rgb(148, 179, 228)" }}>Copy Profile Colors</span>}
|
||||
action={() => copyProfileColors(user.id)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default definePlugin({
|
||||
name: "CopyProfileColors",
|
||||
description: "A plugin to copy people's profile gradient colors to clipboard.",
|
||||
authors: [EquicordDevs.Crxa, EquicordDevs.Cortex], // Cortex is here because he showed me how to add icons <3
|
||||
|
||||
start() {
|
||||
addContextMenuPatch("user-context", userContextMenuPatch);
|
||||
addContextMenuPatch("user-profile-actions", userContextMenuPatch);
|
||||
},
|
||||
|
||||
stop() {
|
||||
// bye bye menu options
|
||||
removeContextMenuPatch("user-context", userContextMenuPatch);
|
||||
removeContextMenuPatch("user-profile-actions", userContextMenuPatch);
|
||||
}
|
||||
});
|
135
src/equicordplugins/copyStickerLinks/index.tsx
Normal file
135
src/equicordplugins/copyStickerLinks/index.tsx
Normal file
|
@ -0,0 +1,135 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2025 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/ContextMenu";
|
||||
import { Devs } from "@utils/constants";
|
||||
import { copyWithToast } from "@utils/misc";
|
||||
import definePlugin from "@utils/types";
|
||||
import { findStoreLazy } from "@webpack";
|
||||
import { Menu, React } from "@webpack/common";
|
||||
import { Promisable } from "type-fest";
|
||||
|
||||
const StickersStore = findStoreLazy("StickersStore");
|
||||
|
||||
interface Sticker {
|
||||
t: "Sticker";
|
||||
format_type: number;
|
||||
id: string;
|
||||
type: number;
|
||||
}
|
||||
|
||||
const StickerExt = ["png", "png", "json", "gif"] as const;
|
||||
|
||||
function getUrl(data: Sticker) {
|
||||
if (data.format_type === 4)
|
||||
return `https:${window.GLOBAL_ENV.MEDIA_PROXY_ENDPOINT}/stickers/${data.id}.gif?size=4096&lossless=true`;
|
||||
|
||||
return `https://${window.GLOBAL_ENV.CDN_HOST}/stickers/${data.id}.${StickerExt[data.format_type]}?size=4096&lossless=true`;
|
||||
}
|
||||
|
||||
function buildMenuItem(Sticker, fetchData: () => Promisable<Omit<Sticker, "t">>) {
|
||||
return (
|
||||
<>
|
||||
<Menu.MenuSeparator></Menu.MenuSeparator>
|
||||
|
||||
<Menu.MenuItem
|
||||
id="copystickerurl"
|
||||
key="copystickerurl"
|
||||
label={"Copy URL"}
|
||||
action={async () => {
|
||||
const res = await fetchData();
|
||||
const data = { t: Sticker, ...res } as Sticker;
|
||||
const url = getUrl(data[0]);
|
||||
copyWithToast(url, "Link copied!");
|
||||
}
|
||||
}
|
||||
/>
|
||||
|
||||
<Menu.MenuItem
|
||||
id="openstickerlink"
|
||||
key="openstickerlink"
|
||||
label={"Open URL"}
|
||||
action={async () => {
|
||||
const res = await fetchData();
|
||||
const data = { t: Sticker, ...res } as Sticker;
|
||||
const url = getUrl(data[0]);
|
||||
VencordNative.native.openExternal(url);
|
||||
}
|
||||
}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function buildMenuExpression(Sticker, fetchData: () => Promisable<Omit<Sticker, "t">>) {
|
||||
return (
|
||||
<>
|
||||
<Menu.MenuSeparator></Menu.MenuSeparator>
|
||||
<Menu.MenuItem
|
||||
id="copystickerurl"
|
||||
key="copystickerurl"
|
||||
label={"Copy URL"}
|
||||
action={async () => {
|
||||
const res = await fetchData();
|
||||
const data = { t: Sticker, ...res } as Sticker;
|
||||
const url = getUrl(data);
|
||||
copyWithToast(url, "Link copied!");
|
||||
}
|
||||
}
|
||||
/>
|
||||
<Menu.MenuItem
|
||||
id="openstickerlink"
|
||||
key="openstickerlink"
|
||||
label={"Open URL"}
|
||||
action={async () => {
|
||||
const res = await fetchData();
|
||||
const data = { t: Sticker, ...res } as Sticker;
|
||||
const url = getUrl(data);
|
||||
VencordNative.native.openExternal(url);
|
||||
}
|
||||
}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const messageContextMenuPatch: NavContextMenuPatchCallback = (children, props) => {
|
||||
const { favoriteableId, favoriteableType } = props ?? {};
|
||||
if (!favoriteableId) return;
|
||||
const menuItem = (() => {
|
||||
const sticker = props.message.stickerItems.find(s => s.id === favoriteableId);
|
||||
if (sticker?.format_type === 3) return;
|
||||
switch (favoriteableType) {
|
||||
case "sticker":
|
||||
return buildMenuItem("Sticker", () => props.message.stickerItems);
|
||||
}
|
||||
})();
|
||||
|
||||
if (menuItem)
|
||||
findGroupChildrenByChildId("devmode-copy-id", children, true)?.push(menuItem);
|
||||
};
|
||||
|
||||
const expressionPickerPatch: NavContextMenuPatchCallback = (children, props: { target: HTMLElement; }) => {
|
||||
const { id } = props?.target?.dataset ?? {};
|
||||
if (!id) return;
|
||||
|
||||
if (!props.target.className?.includes("lottieCanvas")) {
|
||||
const stickerCache = StickersStore.getStickerById(id);
|
||||
if (stickerCache) {
|
||||
children.push(buildMenuExpression("Sticker", () => stickerCache));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default definePlugin({
|
||||
name: "CopyStickerLinks",
|
||||
description: "Adds the ability to copy and open sticker links to your browser",
|
||||
authors: [Devs.Byeoon],
|
||||
contextMenus: {
|
||||
"message": messageContextMenuPatch,
|
||||
"expression-picker": expressionPickerPatch
|
||||
}
|
||||
});
|
|
@ -5,9 +5,10 @@
|
|||
*/
|
||||
|
||||
import { NavContextMenuPatchCallback } from "@api/ContextMenu";
|
||||
import { copyToClipboard } from "@utils/clipboard";
|
||||
import { Devs, EquicordDevs } from "@utils/constants";
|
||||
import definePlugin from "@utils/types";
|
||||
import { Clipboard, Menu } from "@webpack/common";
|
||||
import { Menu } from "@webpack/common";
|
||||
import type { Channel, User } from "discord-types/general";
|
||||
|
||||
const MentionIcon = () => (
|
||||
|
@ -37,7 +38,7 @@ const UserContextMenuPatch: NavContextMenuPatchCallback = (children, { user }: U
|
|||
<Menu.MenuItem
|
||||
id="vc-copy-user-mention"
|
||||
label="Copy User Mention"
|
||||
action={() => Clipboard.copy(`<@${user.id}>`)}
|
||||
action={() => copyToClipboard(`<@${user.id}>`)}
|
||||
icon={MentionIcon}
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -71,11 +71,11 @@ export default definePlugin({
|
|||
find: "#{intl::MESSAGE_EDITED_TIMESTAMP_A11Y_LABEL}",
|
||||
replacement: [
|
||||
{
|
||||
match: /(?<=\i=null!=\i\?).{0,25}\((\i),"LT"\):\(0,\i\.\i\)\(\i,!0\)/,
|
||||
match: /(?<=null!=\i\?).{0,25}\((\i),"LT"\):\(0,\i\.\i\)\(\i,!0\)/,
|
||||
replace: '$self.format($1,"compactFormat","[calendar]"):$self.format($1,"cozyFormat","LT")',
|
||||
},
|
||||
{
|
||||
match: /(?<=text:)\(0,\i.\i\)\((\i),"LLLL"\)(?=,)/,
|
||||
match: /(?<=text:)\(\)=>\(0,\i.\i\)\((\i),"LLLL"\)(?=,)/,
|
||||
replace: '$self.format($1,"tooltipFormat","LLLL")',
|
||||
},
|
||||
]
|
||||
|
|
|
@ -62,12 +62,8 @@ const userContextMenuPatch: NavContextMenuPatchCallback = (children, { user }: {
|
|||
};
|
||||
|
||||
export function getCustomColorString(userId: string, withHash?: boolean): string | undefined {
|
||||
if (!colors[userId] || !Settings.plugins.CustomUserColors.enabled)
|
||||
return;
|
||||
|
||||
if (withHash)
|
||||
return `#${colors[userId]}`;
|
||||
|
||||
if (!colors[userId] || !Settings.plugins.CustomUserColors.enabled) return;
|
||||
if (withHash) return `#${colors[userId]}`;
|
||||
return colors[userId];
|
||||
}
|
||||
|
||||
|
@ -98,21 +94,23 @@ export default definePlugin({
|
|||
// this also affects name headers in chats outside of servers
|
||||
find: '="SYSTEM_TAG"',
|
||||
replacement: {
|
||||
match: /\i.gradientClassName]\),style:/,
|
||||
replace: "$&{color:$self.colorIfServer(arguments[0])},_style:"
|
||||
// Override colorString with our custom color and disable gradients if applying the custom color.
|
||||
match: /&&null!=\i\.secondaryColor,(?<=colorString:(\i).+?(\i)=.+?)/,
|
||||
replace: (m, colorString, hasGradientColors) => `${m}` +
|
||||
`vcCustomUserColorsDummy=[${colorString},${hasGradientColors}]=$self.getMessageColorsVariables(arguments[0],${hasGradientColors}),`
|
||||
},
|
||||
predicate: () => !Settings.plugins.IrcColors.enabled
|
||||
},
|
||||
{
|
||||
find: "PrivateChannel.renderAvatar",
|
||||
replacement: {
|
||||
match: /(highlighted:\i,)/,
|
||||
match: /(subText:\i\(\),)/,
|
||||
replace: "$1style:{color:`${$self.colorDMList(arguments[0])}`},"
|
||||
},
|
||||
predicate: () => settings.store.dmList,
|
||||
},
|
||||
{
|
||||
find: "!1,wrapContent",
|
||||
find: '"AvatarWithText"',
|
||||
replacement: [
|
||||
{
|
||||
match: /(\}=\i)/,
|
||||
|
@ -125,22 +123,40 @@ export default definePlugin({
|
|||
],
|
||||
predicate: () => settings.store.dmList,
|
||||
},
|
||||
{
|
||||
find: '"Reply Chain Nudge")',
|
||||
replacement: {
|
||||
match: /(,color:)(\i),/,
|
||||
replace: "$1$self.colorInReplyingTo(arguments[0]) ?? $2,",
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
colorDMList(a: any): string | undefined {
|
||||
const userId = a?.user?.id;
|
||||
if (!userId) return;
|
||||
const colorString = getCustomColorString(userId, true);
|
||||
if (colorString) return colorString;
|
||||
return "inherit";
|
||||
getMessageColorsVariables(context: any, hasGradientColors: boolean) {
|
||||
const colorString = this.colorIfServer(context);
|
||||
const originalColorString = context?.author?.colorString;
|
||||
|
||||
return [colorString, hasGradientColors && colorString === originalColorString];
|
||||
},
|
||||
|
||||
colorIfServer(a: any): string | undefined {
|
||||
const roleColor = a.author?.colorString;
|
||||
colorDMList(context: any): string | undefined {
|
||||
const userId = context?.user?.id;
|
||||
const colorString = getCustomColorString(userId, true);
|
||||
return colorString ?? "inherit";
|
||||
},
|
||||
|
||||
if (a?.channel?.guild_id && !settings.store.colorInServers) return roleColor;
|
||||
colorIfServer(context: any): string | undefined {
|
||||
const userId = context?.message?.author?.id;
|
||||
const colorString = context?.author?.colorString;
|
||||
|
||||
const color = getCustomColorString(a.message.author.id, true);
|
||||
return color ?? roleColor ?? undefined;
|
||||
}
|
||||
if (context?.channel?.guild_id && !settings.store.colorInServers) return colorString;
|
||||
|
||||
const color = getCustomColorString(userId, true);
|
||||
return color ?? colorString ?? undefined;
|
||||
},
|
||||
|
||||
colorInReplyingTo(a: any) {
|
||||
const { id } = a.reply.message.author;
|
||||
return getCustomColorString(id, true);
|
||||
},
|
||||
});
|
||||
|
|
|
@ -1,62 +0,0 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2024 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/ContextMenu";
|
||||
import { Devs, EquicordDevs } from "@utils/constants";
|
||||
import definePlugin from "@utils/types";
|
||||
import { Menu } from "@webpack/common";
|
||||
import type { Guild } from "discord-types/general";
|
||||
import { zipSync } from "fflate";
|
||||
|
||||
const Patch: NavContextMenuPatchCallback = (children, { guild }: { guild: Guild; }) => {
|
||||
// Assuming "privacy" is the correct ID for the group you want to modify.
|
||||
const group = findGroupChildrenByChildId("privacy", children);
|
||||
|
||||
if (group) {
|
||||
group.push(
|
||||
<Menu.MenuItem id="emoji.download" label="Download Emojis" action={() => zipServerEmojis(guild)}></Menu.MenuItem>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default definePlugin({
|
||||
name: "EmojiDumper",
|
||||
description: "Context menu to dump and download a server's emojis.",
|
||||
authors: [EquicordDevs.Cortex, Devs.Samwich, EquicordDevs.Woosh],
|
||||
contextMenus: {
|
||||
"guild-context": Patch,
|
||||
"guild-header-popout": Patch
|
||||
}
|
||||
});
|
||||
|
||||
async function zipServerEmojis(guild: Guild) {
|
||||
|
||||
const emojis = Vencord.Webpack.Common.EmojiStore.getGuilds()[guild.id]?.emojis;
|
||||
if (!emojis) {
|
||||
return console.log("Server not found!");
|
||||
}
|
||||
|
||||
const fetchEmojis = async e => {
|
||||
const filename = e.id + (e.animated ? ".gif" : ".png");
|
||||
const emoji = await fetch("https://cdn.discordapp.com/emojis/" + filename + "?size=512&quality=lossless").then(res => res.blob());
|
||||
return { file: new Uint8Array(await emoji.arrayBuffer()), filename };
|
||||
};
|
||||
const emojiPromises = emojis.map(e => fetchEmojis(e));
|
||||
|
||||
Promise.all(emojiPromises)
|
||||
.then(results => {
|
||||
const emojis = zipSync(Object.fromEntries(results.map(({ file, filename }) => [filename, file])));
|
||||
const blob = new Blob([emojis], { type: "application/zip" });
|
||||
const link = document.createElement("a");
|
||||
link.href = URL.createObjectURL(blob);
|
||||
link.download = `${guild.name}-emojis.zip`;
|
||||
link.click();
|
||||
link.remove();
|
||||
})
|
||||
.catch(error => {
|
||||
console.error(error);
|
||||
});
|
||||
}
|
|
@ -12,7 +12,7 @@ import {
|
|||
} from "@api/Commands";
|
||||
import * as DataStore from "@api/DataStore";
|
||||
import { addMessagePreSendListener, MessageSendListener, removeMessagePreSendListener } from "@api/MessageEvents";
|
||||
import { Devs } from "@utils/constants";
|
||||
import { Devs, EquicordDevs } from "@utils/constants";
|
||||
import { sleep } from "@utils/misc";
|
||||
import definePlugin from "@utils/types";
|
||||
import {
|
||||
|
@ -140,7 +140,7 @@ const ChatBarIcon: ChatBarButtonFactory = ({ isMainChat }) => {
|
|||
export default definePlugin({
|
||||
name: "Encryptcord",
|
||||
description: "End-to-end encryption in Discord!",
|
||||
authors: [Devs.Inbestigator],
|
||||
authors: [Devs.Inbestigator, EquicordDevs.ItsAlex],
|
||||
patches: [
|
||||
{
|
||||
find: "INTERACTION_APPLICATION_COMMAND_INVALID_VERSION",
|
||||
|
@ -360,18 +360,28 @@ async function handleGroupData(groupData) {
|
|||
|
||||
// Handle joining group
|
||||
async function handleJoin(senderId: string, senderKey: string, encryptcordGroupMembers: object) {
|
||||
encryptcordGroupMembers[senderId] = { key: senderKey, parent: UserStore.getCurrentUser().id, child: null };
|
||||
encryptcordGroupMembers[UserStore.getCurrentUser().id].child = senderId;
|
||||
const currentUserId = UserStore.getCurrentUser().id;
|
||||
|
||||
if (!encryptcordGroupMembers[senderId]) {
|
||||
encryptcordGroupMembers[senderId] = { key: senderKey, parent: currentUserId, child: null };
|
||||
}
|
||||
|
||||
if (!encryptcordGroupMembers[currentUserId]) {
|
||||
encryptcordGroupMembers[currentUserId] = { key: "", parent: null, child: null };
|
||||
}
|
||||
encryptcordGroupMembers[currentUserId].child = senderId;
|
||||
|
||||
await DataStore.set("encryptcordGroupMembers", encryptcordGroupMembers);
|
||||
|
||||
const groupChannel = await DataStore.get("encryptcordChannelId");
|
||||
|
||||
const newMember = await UserUtils.getUser(senderId).catch(() => null);
|
||||
if (!newMember) return;
|
||||
|
||||
const membersData = {};
|
||||
Object.entries(encryptcordGroupMembers)
|
||||
.forEach(([memberId, value]) => {
|
||||
membersData[memberId] = value;
|
||||
});
|
||||
Object.entries(encryptcordGroupMembers).forEach(([memberId, value]) => {
|
||||
membersData[memberId] = value;
|
||||
});
|
||||
|
||||
const membersDataString = JSON.stringify({ members: membersData, channel: groupChannel });
|
||||
|
||||
|
@ -382,6 +392,7 @@ async function handleJoin(senderId: string, senderKey: string, encryptcordGroupM
|
|||
});
|
||||
|
||||
await Promise.all(dmPromises);
|
||||
|
||||
await MessageActions.receiveMessage(groupChannel, {
|
||||
...await createMessage("", senderId, groupChannel, 7), components: [{
|
||||
type: 1,
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
@import url("https://dablulite.github.io/css-snippets/BetterAuthApps/import.css");
|
|
@ -1 +0,0 @@
|
|||
@import url("https://dablulite.github.io/css-snippets/BetterStatusPicker/import.css");
|
|
@ -1 +0,0 @@
|
|||
@import url("https://raw.githubusercontent.com/gold-me/DiscordIcons/master/DiscordIcons.theme.css");
|
|
@ -1,128 +0,0 @@
|
|||
/* stylelint-disable selector-class-pattern */
|
||||
/* stylelint-disable color-function-notation */
|
||||
|
||||
:root {
|
||||
/* || Gradients */
|
||||
--gradient-special: 140deg, hsl(245deg, calc(var(--saturaton-factor, 1)*79%), 72%) 0%, hsl(287deg, calc(var(--saturaton-factor, 1)*80%), 70%) 100%;
|
||||
--gradient-blurple: 140deg, hsl(235deg, calc(var(--saturation-factor, 1)*85%), 72%) 0%, hsl(235deg, calc(var(--saturation-factor, 1)*85%), 60%) 100%;
|
||||
--gradient-green: 140deg, hsl(139deg, calc(var(--saturaton-factor, 1)*47%), 44%) 0%, hsl(139deg, calc(var(--saturaton-factor, 1)*66%), 24%) 100%;
|
||||
--gradient-yellow: 140deg, hsl(38deg, calc(var(--saturaton-factor, 1)*96%), 54%) 0%, hsl(38deg, calc(var(--saturaton-factor, 1)*82%), 41%) 100%;
|
||||
--gradient-red: 140deg, hsl(359deg, calc(var(--saturaton-factor, 1)*83%), 59%) 0%, hsl(359deg, calc(var(--saturaton-factor, 1)*54%), 37%) 100%;
|
||||
--gradient-grey: 140deg, hsl(214deg, calc(var(--saturaton-factor, 1)*10%), 50%) 0%, hsl(216deg, calc(var(--saturaton-factor, 1)*11%), 26%) 100%;
|
||||
|
||||
/* || Transitions */
|
||||
--button-transition: 0.1s linear;
|
||||
--font-default: 500;
|
||||
--font-hover: 525;
|
||||
--fontsize-hover: 15px;
|
||||
--transform-normal: scale(1);
|
||||
--transform-hover: scale(1.15);
|
||||
--button-transform-hover: scale(1.04);
|
||||
}
|
||||
|
||||
/* || Filled Buttons */
|
||||
.lookFilled-yCfaCM {
|
||||
transform: var(--transform-normal);
|
||||
transition: var(--button-transition);
|
||||
background: var(--gradient);
|
||||
}
|
||||
|
||||
.lookFilled-yCfaCM:hover {
|
||||
transform: var(--button-transform-hover);
|
||||
}
|
||||
|
||||
.lookFilled-yCfaCM[disabled] {
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.lookFilled-yCfaCM.colorBrand-I6CyqQ {
|
||||
--gradient: linear-gradient(var(--gradient-blurple));
|
||||
}
|
||||
|
||||
.lookFilled-yCfaCM.colorGreen-3y-Z79,
|
||||
.lookFilled-yCfaCM.button_adcaac.buttonActive_adcaac {
|
||||
--gradient: linear-gradient(var(--gradient-green));
|
||||
}
|
||||
|
||||
.lookFilled-yCfaCM.colorYellow-Pgtmch {
|
||||
--gradient: linear-gradient(var(--gradient-yellow));
|
||||
}
|
||||
|
||||
.lookFilled-yCfaCM.colorRed-rQXKgM {
|
||||
--gradient: linear-gradient(var(--gradient-red));
|
||||
}
|
||||
|
||||
.lookFilled-yCfaCM.colorPrimary-2AuQVo,
|
||||
.lookFilled-yCfaCM.colorGrey-2iAG-B,
|
||||
.lookFilled-yCfaCM.buttonColor_adcaac {
|
||||
--gradient: linear-gradient(var(--gradient-grey));
|
||||
}
|
||||
|
||||
/* || Context Menus */
|
||||
.menu_d90b3d .item-1OdjEX:not(.hideInteraction-2jPGL_) {
|
||||
font-weight: var(--font-default);
|
||||
transition: var(--button-transition);
|
||||
}
|
||||
|
||||
.menu_d90b3d .item-1OdjEX:not(.hideInteraction-2jPGL_).focused-3qFvc8,
|
||||
.menu_d90b3d .item-1OdjEX:not(.hideInteraction-2jPGL_):active {
|
||||
font-size: var(--fontsize-hover);
|
||||
font-weight: var(--font-hover);
|
||||
background: var(--gradient);
|
||||
}
|
||||
|
||||
.menu_d90b3d .colorDefault-CDqZdO.focused-3qFvc8,
|
||||
.menu_d90b3d .colorDefault-CDqZdO:active {
|
||||
--gradient: linear-gradient(var(--gradient-blurple));
|
||||
}
|
||||
|
||||
.menu_d90b3d .colorDanger-3n-KnP.focused-3qFvc8,
|
||||
.menu_d90b3d .colorDanger-3n-KnP:active,
|
||||
.menu_d90b3d #status-picker-dnd.focused-3qFvc8,
|
||||
.menu_d90b3d #status-picker-dnd:active {
|
||||
--gradient: linear-gradient(var(--gradient-red));
|
||||
}
|
||||
|
||||
.menu_d90b3d .colorPremium-vwmYZQ.focused-3qFvc8,
|
||||
.menu_d90b3d .colorPremium-vwmYZQ:active {
|
||||
--gradient: linear-gradient(var(--gradient-special));
|
||||
}
|
||||
|
||||
.menu_d90b3d #status-picker-online.focused-3qFvc8,
|
||||
.menu_d90b3d #status-picker-online:active {
|
||||
--gradient: linear-gradient(var(--gradient-green));
|
||||
}
|
||||
|
||||
.menu_d90b3d #status-picker-idle.focused-3qFvc8,
|
||||
.menu_d90b3d #status-picker-idle:active {
|
||||
--gradient: linear-gradient(var(--gradient-yellow));
|
||||
}
|
||||
|
||||
.menu_d90b3d #status-picker-invisible.focused-3qFvc8,
|
||||
.menu_d90b3d #status-picker-invisible:active {
|
||||
--gradient: linear-gradient(var(--gradient-grey));
|
||||
}
|
||||
|
||||
/* || Message Actions */
|
||||
.wrapper_f7e168 .button_f7e168 {
|
||||
background: var(--gradient);
|
||||
}
|
||||
|
||||
.wrapper_f7e168 .button_f7e168 img,
|
||||
.wrapper_f7e168 .button_f7e168 svg {
|
||||
transition: var(--button-transition);
|
||||
transform: var(--transform-normal);
|
||||
}
|
||||
|
||||
.wrapper_f7e168 .button_f7e168:hover {
|
||||
--gradient: linear-gradient(var(--gradient-blurple));
|
||||
}
|
||||
|
||||
.wrapper_f7e168 .button_f7e168:hover svg {
|
||||
transform: var(--transform-hover);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.wrapper_f7e168 .button_f7e168.dangerous_f7e168:hover {
|
||||
--gradient: linear-gradient(var(--gradient-red));
|
||||
}
|
487
src/equicordplugins/equicordCSS/css/main.min.css
vendored
487
src/equicordplugins/equicordCSS/css/main.min.css
vendored
|
@ -1,487 +0,0 @@
|
|||
/* stylelint-disable property-no-vendor-prefix */
|
||||
/* stylelint-disable selector-class-pattern */
|
||||
|
||||
:root {
|
||||
--settingsicons: 1;
|
||||
--si-size: 18px;
|
||||
--si-gap: 14px;
|
||||
--use-si: calc(var(--settingsicons, 1) / (var(--settingsicons, 1)));
|
||||
--si-myaccount: url("https://minidiscordthemes.github.io/SettingsIcons/svg/myaccount.svg");
|
||||
--si-profilecustomization: url("https://minidiscordthemes.github.io/SettingsIcons/svg/profilecustomization.svg");
|
||||
--si-privacysafety: url("https://minidiscordthemes.github.io/SettingsIcons/svg/privacysafety.svg");
|
||||
--si-familycenter: url("https://minidiscordthemes.github.io/SettingsIcons/svg/familycenter.svg");
|
||||
--si-authorizedapps: url("https://minidiscordthemes.github.io/SettingsIcons/svg/authorizedapps.svg");
|
||||
--si-sessions: url("https://minidiscordthemes.github.io/SettingsIcons/svg/sessions.svg");
|
||||
--si-connections: url("https://minidiscordthemes.github.io/SettingsIcons/svg/connections.svg");
|
||||
--si-settingsclips: url("https://minidiscordthemes.github.io/SettingsIcons/svg/settingsclips.svg");
|
||||
--si-friendrequests: url("https://minidiscordthemes.github.io/SettingsIcons/svg/friendrequests.svg");
|
||||
--si-discordnitro: url("https://minidiscordthemes.github.io/SettingsIcons/svg/discordnitro.svg");
|
||||
--si-nitroserverboost: url("https://minidiscordthemes.github.io/SettingsIcons/svg/nitroserverboost.svg");
|
||||
--si-subscriptions: url("https://minidiscordthemes.github.io/SettingsIcons/svg/subscriptions.svg");
|
||||
--si-libraryinventory: url("https://minidiscordthemes.github.io/SettingsIcons/svg/libraryinventory.svg");
|
||||
--si-billing: url("https://minidiscordthemes.github.io/SettingsIcons/svg/billing.svg");
|
||||
--si-appearance: url("https://minidiscordthemes.github.io/SettingsIcons/svg/appearance.svg");
|
||||
--si-accessibility: url("https://minidiscordthemes.github.io/SettingsIcons/svg/accessibility.svg");
|
||||
--si-voicevideo: url("https://minidiscordthemes.github.io/SettingsIcons/svg/voicevideo.svg");
|
||||
--si-textimages: url("https://minidiscordthemes.github.io/SettingsIcons/svg/textimages.svg");
|
||||
--si-notifications: url("https://minidiscordthemes.github.io/SettingsIcons/svg/notifications.svg");
|
||||
--si-keybinds: url("https://minidiscordthemes.github.io/SettingsIcons/svg/keybinds.svg");
|
||||
--si-language: url("https://minidiscordthemes.github.io/SettingsIcons/svg/language.svg");
|
||||
--si-windows: url("https://minidiscordthemes.github.io/SettingsIcons/svg/windows.svg");
|
||||
--si-streamermode: url("https://minidiscordthemes.github.io/SettingsIcons/svg/streamermode.svg");
|
||||
--si-rtcspeedtest: url("https://minidiscordthemes.github.io/SettingsIcons/svg/rtcspeedtest.svg");
|
||||
--si-advanced: url("https://minidiscordthemes.github.io/SettingsIcons/svg/advanced.svg");
|
||||
--si-activityprivacy: url("https://minidiscordthemes.github.io/SettingsIcons/svg/activityprivacy.svg");
|
||||
--si-gameactivity: url("https://minidiscordthemes.github.io/SettingsIcons/svg/gameactivity.svg");
|
||||
--si-overlay: url("https://minidiscordthemes.github.io/SettingsIcons/svg/overlay.svg");
|
||||
--si-changelog: url("https://minidiscordthemes.github.io/SettingsIcons/svg/changelog.svg");
|
||||
--si-merchandise: url("https://minidiscordthemes.github.io/SettingsIcons/svg/merchandise.svg");
|
||||
--si-hypesquadonline: url("https://minidiscordthemes.github.io/SettingsIcons/svg/hypesquadonline.svg");
|
||||
--si-powermodesettings: url("https://minidiscordthemes.github.io/SettingsIcons/svg/powermodesettings.svg");
|
||||
--si-experiments: url("https://minidiscordthemes.github.io/SettingsIcons/svg/experiments.svg");
|
||||
--si-developeroptions: url("https://minidiscordthemes.github.io/SettingsIcons/svg/developeroptions.svg");
|
||||
--si-hotspotoptions: url("https://minidiscordthemes.github.io/SettingsIcons/svg/hotspotoptions.svg");
|
||||
--si-dismissiblecontentoptions: url("https://minidiscordthemes.github.io/SettingsIcons/svg/dismissiblecontentoptions.svg");
|
||||
--si-startuptimings: url("https://minidiscordthemes.github.io/SettingsIcons/svg/startuptimings.svg");
|
||||
--si-paymentflowmodals: url("https://minidiscordthemes.github.io/SettingsIcons/svg/paymentflowmodals.svg");
|
||||
--si-textplayground: url("https://minidiscordthemes.github.io/SettingsIcons/svg/textplayground.svg");
|
||||
--si-textcomponent: url("https://minidiscordthemes.github.io/SettingsIcons/svg/textcomponent.svg");
|
||||
--si-logout: url("https://minidiscordthemes.github.io/SettingsIcons/svg/logout.svg");
|
||||
--si-equicordsettings: url("https://minidiscordthemes.github.io/SettingsIcons/svg/vencordsettings.svg");
|
||||
--si-equicordplugins: url("https://minidiscordthemes.github.io/SettingsIcons/svg/vencordplugins.svg");
|
||||
--si-equicordthemes: url("https://minidiscordthemes.github.io/SettingsIcons/svg/vencordthemes.svg");
|
||||
--si-equicordupdater: url("https://minidiscordthemes.github.io/SettingsIcons/svg/vencordupdater.svg");
|
||||
--si-equicordcloud: url("https://minidiscordthemes.github.io/SettingsIcons/svg/vencordcloud.svg");
|
||||
--si-equicordsettingssync: url("https://minidiscordthemes.github.io/SettingsIcons/svg/vencordsettingssync.svg");
|
||||
--si-equicordpatchhelper: url("https://minidiscordthemes.github.io/SettingsIcons/svg/vencordpatchhelper.svg");
|
||||
--si-equibop: url("https://minidiscordthemes.github.io/SettingsIcons/svg/vesktop.svg");
|
||||
--si-vesktop: url("https://minidiscordthemes.github.io/SettingsIcons/svg/vesktop.svg");
|
||||
--si-overview: url("https://minidiscordthemes.github.io/SettingsIcons/svg/overview.svg");
|
||||
--si-roles: url("https://minidiscordthemes.github.io/SettingsIcons/svg/roles.svg");
|
||||
--si-emoji: url("https://minidiscordthemes.github.io/SettingsIcons/svg/emoji.svg");
|
||||
--si-stickers: url("https://minidiscordthemes.github.io/SettingsIcons/svg/stickers.svg");
|
||||
--si-soundboard: url("https://minidiscordthemes.github.io/SettingsIcons/svg/soundboard.svg");
|
||||
--si-widget: url("https://minidiscordthemes.github.io/SettingsIcons/svg/widget.svg");
|
||||
--si-guildtemplates: url("https://minidiscordthemes.github.io/SettingsIcons/svg/guildtemplates.svg");
|
||||
--si-vanityurl: url("https://minidiscordthemes.github.io/SettingsIcons/svg/vanityurl.svg");
|
||||
--si-integrations: url("https://minidiscordthemes.github.io/SettingsIcons/svg/integrations.svg");
|
||||
--si-appdirectory: url("https://minidiscordthemes.github.io/SettingsIcons/svg/appdirectory.svg");
|
||||
--si-safety: url("https://minidiscordthemes.github.io/SettingsIcons/svg/safety.svg");
|
||||
--si-auditlog: url("https://minidiscordthemes.github.io/SettingsIcons/svg/auditlog.svg");
|
||||
--si-bans: url("https://minidiscordthemes.github.io/SettingsIcons/svg/bans.svg");
|
||||
--si-community: url("https://minidiscordthemes.github.io/SettingsIcons/svg/community.svg");
|
||||
--si-onboarding: url("https://minidiscordthemes.github.io/SettingsIcons/svg/onboarding.svg");
|
||||
--si-analytics: url("https://minidiscordthemes.github.io/SettingsIcons/svg/analytics.svg");
|
||||
--si-partner: url("https://minidiscordthemes.github.io/SettingsIcons/svg/partner.svg");
|
||||
--si-discovery: url("https://minidiscordthemes.github.io/SettingsIcons/svg/discovery.svg");
|
||||
--si-rolesubscriptions: url("https://minidiscordthemes.github.io/SettingsIcons/svg/rolesubscriptions.svg");
|
||||
--si-guildpremium: url("https://minidiscordthemes.github.io/SettingsIcons/svg/guildpremium.svg");
|
||||
--si-members: url("https://minidiscordthemes.github.io/SettingsIcons/svg/members.svg");
|
||||
--si-instantinvites: url("https://minidiscordthemes.github.io/SettingsIcons/svg/instantinvites.svg");
|
||||
--si-delete: url("https://minidiscordthemes.github.io/SettingsIcons/svg/delete.svg");
|
||||
--si-permissions: url("https://minidiscordthemes.github.io/SettingsIcons/svg/permissions.svg");
|
||||
--si-default: url("https://minidiscordthemes.github.io/SettingsIcons/svg/default.svg");
|
||||
}
|
||||
|
||||
.sidebarRegion_c25c6d {
|
||||
flex-basis: calc(218px + var(--use-si)*(var(--si-size) + var(--si-gap))) !important
|
||||
}
|
||||
|
||||
.sidebar_c25c6d {
|
||||
width: calc(218px + var(--use-si)*(var(--si-size) + var(--si-gap))) !important
|
||||
}
|
||||
|
||||
.sidebar_c25c6d :is(.item_a0 .icon_f7189e, .premiumLabel_ae3c77>svg, .premiumLabel_ae3c77 img, .tabBarItemContainer_e7c031>svg, .tabBarItemContainer_e7c031 img) {
|
||||
transform: scaleX(calc(1 - var(--use-si)))
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0 {
|
||||
display: flex;
|
||||
align-items: center
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0::before {
|
||||
content: "";
|
||||
flex: 0 0 auto;
|
||||
width: calc(var(--use-si)*var(--si-size));
|
||||
height: calc(var(--use-si)*var(--si-size));
|
||||
margin-right: calc(var(--use-si)*var(--si-size)/2);
|
||||
background: currentcolor;
|
||||
z-index: 2;
|
||||
-webkit-mask: var(--si-default) center/contain no-repeat;
|
||||
mask: var(--si-default) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="My Account"]::before {
|
||||
-webkit-mask: var(--si-myaccount) center/contain no-repeat;
|
||||
mask: var(--si-myaccount) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="Profile Customization"]::before {
|
||||
-webkit-mask: var(--si-profilecustomization) center/contain no-repeat;
|
||||
mask: var(--si-profilecustomization) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="Privacy & Safety"]::before {
|
||||
-webkit-mask: var(--si-privacysafety) center/contain no-repeat;
|
||||
mask: var(--si-privacysafety) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="Family Center"]::before {
|
||||
-webkit-mask: var(--si-familycenter) center/contain no-repeat;
|
||||
mask: var(--si-familycenter) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="Authorized Apps"]::before {
|
||||
-webkit-mask: var(--si-authorizedapps) center/contain no-repeat;
|
||||
mask: var(--si-authorizedapps) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="Sessions"]::before {
|
||||
-webkit-mask: var(--si-sessions) center/contain no-repeat;
|
||||
mask: var(--si-sessions) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="Connections"]::before {
|
||||
-webkit-mask: var(--si-connections) center/contain no-repeat;
|
||||
mask: var(--si-connections) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="Settings Clips"]::before {
|
||||
-webkit-mask: var(--si-settingsclips) center/contain no-repeat;
|
||||
mask: var(--si-settingsclips) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="Friend Requests"]::before {
|
||||
-webkit-mask: var(--si-friendrequests) center/contain no-repeat;
|
||||
mask: var(--si-friendrequests) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="Discord Nitro"]::before {
|
||||
-webkit-mask: var(--si-discordnitro) center/contain no-repeat;
|
||||
mask: var(--si-discordnitro) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="Nitro Server Boost"]::before {
|
||||
-webkit-mask: var(--si-nitroserverboost) center/contain no-repeat;
|
||||
mask: var(--si-nitroserverboost) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="Subscriptions"]::before {
|
||||
-webkit-mask: var(--si-subscriptions) center/contain no-repeat;
|
||||
mask: var(--si-subscriptions) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="Library Inventory"]::before {
|
||||
-webkit-mask: var(--si-libraryinventory) center/contain no-repeat;
|
||||
mask: var(--si-libraryinventory) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="Billing"]::before {
|
||||
-webkit-mask: var(--si-billing) center/contain no-repeat;
|
||||
mask: var(--si-billing) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="Appearance"]::before {
|
||||
-webkit-mask: var(--si-appearance) center/contain no-repeat;
|
||||
mask: var(--si-appearance) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="Accessibility"]::before {
|
||||
-webkit-mask: var(--si-accessibility) center/contain no-repeat;
|
||||
mask: var(--si-accessibility) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="Voice & Video"]::before {
|
||||
-webkit-mask: var(--si-voicevideo) center/contain no-repeat;
|
||||
mask: var(--si-voicevideo) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="Text & Images"]::before {
|
||||
-webkit-mask: var(--si-textimages) center/contain no-repeat;
|
||||
mask: var(--si-textimages) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="Notifications"]::before {
|
||||
-webkit-mask: var(--si-notifications) center/contain no-repeat;
|
||||
mask: var(--si-notifications) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="Keybinds"]::before {
|
||||
-webkit-mask: var(--si-keybinds) center/contain no-repeat;
|
||||
mask: var(--si-keybinds) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="Language"]::before {
|
||||
-webkit-mask: var(--si-language) center/contain no-repeat;
|
||||
mask: var(--si-language) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="Windows"]::before {
|
||||
-webkit-mask: var(--si-windows) center/contain no-repeat;
|
||||
mask: var(--si-windows) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="Streamer Mode"]::before {
|
||||
-webkit-mask: var(--si-streamermode) center/contain no-repeat;
|
||||
mask: var(--si-streamermode) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="RTC Speed Test"]::before {
|
||||
-webkit-mask: var(--si-rtcspeedtest) center/contain no-repeat;
|
||||
mask: var(--si-rtcspeedtest) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="Advanced"]::before {
|
||||
-webkit-mask: var(--si-advanced) center/contain no-repeat;
|
||||
mask: var(--si-advanced) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="Activity Privacy"]::before {
|
||||
-webkit-mask: var(--si-activityprivacy) center/contain no-repeat;
|
||||
mask: var(--si-activityprivacy) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="Game Activity"]::before {
|
||||
-webkit-mask: var(--si-gameactivity) center/contain no-repeat;
|
||||
mask: var(--si-gameactivity) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="Overlay"]::before {
|
||||
-webkit-mask: var(--si-overlay) center/contain no-repeat;
|
||||
mask: var(--si-overlay) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="changelog"]::before {
|
||||
-webkit-mask: var(--si-changelog) center/contain no-repeat;
|
||||
mask: var(--si-changelog) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="merchandise"]::before {
|
||||
-webkit-mask: var(--si-merchandise) center/contain no-repeat;
|
||||
mask: var(--si-merchandise) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="Hypesquad Online"]::before {
|
||||
-webkit-mask: var(--si-hypesquadonline) center/contain no-repeat;
|
||||
mask: var(--si-hypesquadonline) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="Powermode Settings"]::before {
|
||||
-webkit-mask: var(--si-powermodesettings) center/contain no-repeat;
|
||||
mask: var(--si-powermodesettings) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="Experiments"]::before {
|
||||
-webkit-mask: var(--si-experiments) center/contain no-repeat;
|
||||
mask: var(--si-experiments) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="Developer Options"]::before {
|
||||
-webkit-mask: var(--si-developeroptions) center/contain no-repeat;
|
||||
mask: var(--si-developeroptions) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="Hotspot Options"]::before {
|
||||
-webkit-mask: var(--si-hotspotoptions) center/contain no-repeat;
|
||||
mask: var(--si-hotspotoptions) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="Dismissible Content Options"]::before {
|
||||
-webkit-mask: var(--si-dismissiblecontentoptions) center/contain no-repeat;
|
||||
mask: var(--si-dismissiblecontentoptions) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="StartupTimings"]::before {
|
||||
-webkit-mask: var(--si-startuptimings) center/contain no-repeat;
|
||||
mask: var(--si-startuptimings) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="Payment Flow Modals"]::before {
|
||||
-webkit-mask: var(--si-paymentflowmodals) center/contain no-repeat;
|
||||
mask: var(--si-paymentflowmodals) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="Text Playground"]::before {
|
||||
-webkit-mask: var(--si-textplayground) center/contain no-repeat;
|
||||
mask: var(--si-textplayground) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="Text Component"]::before {
|
||||
-webkit-mask: var(--si-textcomponent) center/contain no-repeat;
|
||||
mask: var(--si-textcomponent) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="logout"]::before {
|
||||
-webkit-mask: var(--si-logout) center/contain no-repeat;
|
||||
mask: var(--si-logout) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="EquicordSettings"]::before {
|
||||
-webkit-mask: var(--si-equicordsettings) center/contain no-repeat;
|
||||
mask: var(--si-equicordsettings) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="EquicordPlugins"]::before {
|
||||
-webkit-mask: var(--si-equicordplugins) center/contain no-repeat;
|
||||
mask: var(--si-equicordplugins) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="EquicordThemes"]::before {
|
||||
-webkit-mask: var(--si-equicordthemes) center/contain no-repeat;
|
||||
mask: var(--si-equicordthemes) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="EquicordUpdater"]::before {
|
||||
-webkit-mask: var(--si-equicordupdater) center/contain no-repeat;
|
||||
mask: var(--si-equicordupdater) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="EquicordCloud"]::before {
|
||||
-webkit-mask: var(--si-equicordcloud) center/contain no-repeat;
|
||||
mask: var(--si-equicordcloud) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="EquicordSettingsSync"]::before {
|
||||
-webkit-mask: var(--si-equicordsettingssync) center/contain no-repeat;
|
||||
mask: var(--si-equicordsettingssync) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="EquicordPatchHelper"]::before {
|
||||
-webkit-mask: var(--si-equicordpatchhelper) center/contain no-repeat;
|
||||
mask: var(--si-equicordpatchhelper) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="Equibop"]::before {
|
||||
-webkit-mask: var(--si-equibop) center/contain no-repeat;
|
||||
mask: var(--si-equibop) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="Vesktop"]::before {
|
||||
-webkit-mask: var(--si-vesktop) center/contain no-repeat;
|
||||
mask: var(--si-vesktop) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="OVERVIEW"]::before {
|
||||
-webkit-mask: var(--si-overview) center/contain no-repeat;
|
||||
mask: var(--si-overview) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="ROLES"]::before {
|
||||
-webkit-mask: var(--si-roles) center/contain no-repeat;
|
||||
mask: var(--si-roles) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="EMOJI"]::before {
|
||||
-webkit-mask: var(--si-emoji) center/contain no-repeat;
|
||||
mask: var(--si-emoji) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="STICKERS"]::before {
|
||||
-webkit-mask: var(--si-stickers) center/contain no-repeat;
|
||||
mask: var(--si-stickers) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="SOUNDBOARD"]::before {
|
||||
-webkit-mask: var(--si-soundboard) center/contain no-repeat;
|
||||
mask: var(--si-soundboard) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="WIDGET"]::before {
|
||||
-webkit-mask: var(--si-widget) center/contain no-repeat;
|
||||
mask: var(--si-widget) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="GUILD_TEMPLATES"]::before {
|
||||
-webkit-mask: var(--si-guildtemplates) center/contain no-repeat;
|
||||
mask: var(--si-guildtemplates) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="VANITY_URL"]::before {
|
||||
-webkit-mask: var(--si-vanityurl) center/contain no-repeat;
|
||||
mask: var(--si-vanityurl) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="INTEGRATIONS"]::before {
|
||||
-webkit-mask: var(--si-integrations) center/contain no-repeat;
|
||||
mask: var(--si-integrations) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="APP_DIRECTORY"]::before {
|
||||
-webkit-mask: var(--si-appdirectory) center/contain no-repeat;
|
||||
mask: var(--si-appdirectory) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="SAFETY"]::before {
|
||||
-webkit-mask: var(--si-safety) center/contain no-repeat;
|
||||
mask: var(--si-safety) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="AUDIT_LOG"]::before {
|
||||
-webkit-mask: var(--si-auditlog) center/contain no-repeat;
|
||||
mask: var(--si-auditlog) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="BANS"]::before {
|
||||
-webkit-mask: var(--si-bans) center/contain no-repeat;
|
||||
mask: var(--si-bans) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="COMMUNITY"]::before {
|
||||
-webkit-mask: var(--si-community) center/contain no-repeat;
|
||||
mask: var(--si-community) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="ONBOARDING"]::before {
|
||||
-webkit-mask: var(--si-onboarding) center/contain no-repeat;
|
||||
mask: var(--si-onboarding) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="ANALYTICS"]::before {
|
||||
-webkit-mask: var(--si-analytics) center/contain no-repeat;
|
||||
mask: var(--si-analytics) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="PARTNER"]::before {
|
||||
-webkit-mask: var(--si-partner) center/contain no-repeat;
|
||||
mask: var(--si-partner) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="DISCOVERY"]::before {
|
||||
-webkit-mask: var(--si-discovery) center/contain no-repeat;
|
||||
mask: var(--si-discovery) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="ROLE_SUBSCRIPTIONS"]::before {
|
||||
-webkit-mask: var(--si-rolesubscriptions) center/contain no-repeat;
|
||||
mask: var(--si-rolesubscriptions) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="GUILD_PREMIUM"]::before {
|
||||
-webkit-mask: var(--si-guildpremium) center/contain no-repeat;
|
||||
mask: var(--si-guildpremium) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="MEMBERS"]::before {
|
||||
-webkit-mask: var(--si-members) center/contain no-repeat;
|
||||
mask: var(--si-members) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="INSTANT_INVITES"]::before {
|
||||
-webkit-mask: var(--si-instantinvites) center/contain no-repeat;
|
||||
mask: var(--si-instantinvites) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="DELETE"]::before {
|
||||
-webkit-mask: var(--si-delete) center/contain no-repeat;
|
||||
mask: var(--si-delete) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="PERMISSIONS"]::before {
|
||||
-webkit-mask: var(--si-permissions) center/contain no-repeat;
|
||||
mask: var(--si-permissions) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0>div {
|
||||
flex: 1 1 auto
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
@import url("https://raw.githubusercontent.com/coolesding/snippets/main/import/fixnitrothemes.css");
|
|
@ -1,9 +0,0 @@
|
|||
@import url("https://raw.githubusercontent.com/Equicord/Equicord/main/src/equicordplugins/equicordCSS/css/main.min.css");
|
||||
|
||||
/* https://github.com/MiniDiscordThemes/SettingsIcons#customisation */
|
||||
|
||||
:root {
|
||||
--settingsicons: 1;
|
||||
--si-size: 18px;
|
||||
--si-gap: 14px;
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
@import url("https://dablulite.github.io/css-snippets/UserReimagined/import.css");
|
|
@ -1,124 +0,0 @@
|
|||
/*
|
||||
* Vencord, a modification for Discord's desktop app
|
||||
* Copyright (c) 2023 Vendicated and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// Import required modules and components
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import { disableStyle, enableStyle } from "@api/Styles";
|
||||
import { EquicordDevs } from "@utils/constants";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
|
||||
// Importing the style managed fixes on and off switch
|
||||
import betterauthapps from "./css/betterauthapps.css?managed";
|
||||
import betterstatuspicker from "./css/betterstatuspicker.css?managed";
|
||||
import discordicons from "./css/discordicons.css?managed";
|
||||
import gradientbuttons from "./css/gradientbuttons.css?managed";
|
||||
import nitrothemesfix from "./css/nitrothemesfix.css?managed";
|
||||
import settingsicons from "./css/settingsicons.css?managed";
|
||||
import userreimagined from "./css/userreimagined.css?managed";
|
||||
|
||||
// Forcing restartNeeded: true to not overcomplicate the live update of the settings using FluxDispatcher and making it complex
|
||||
const settings = definePluginSettings({
|
||||
betterAuthApps: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Enable Better Auth Apps CSS",
|
||||
restartNeeded: true,
|
||||
default: false
|
||||
},
|
||||
betterStatusPicker: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Enable Better Status Picker CSS",
|
||||
restartNeeded: true,
|
||||
default: false
|
||||
},
|
||||
discordicons: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Enable Discord Icons CSS",
|
||||
restartNeeded: true,
|
||||
default: false
|
||||
},
|
||||
gradientButtons: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Enable Gradient Buttons CSS",
|
||||
restartNeeded: true,
|
||||
default: false
|
||||
},
|
||||
nitroThemesFix: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Enable Fix Nitro Themes CSS",
|
||||
restartNeeded: true,
|
||||
default: false
|
||||
},
|
||||
settingsIcons: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Enable Settings Icons CSS",
|
||||
restartNeeded: true,
|
||||
default: false
|
||||
},
|
||||
userReimagined: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Enable User Reimagined CSS",
|
||||
restartNeeded: true,
|
||||
default: false
|
||||
}
|
||||
});
|
||||
|
||||
let settingsArray: Array<any> = [];
|
||||
let cssArray: Array<any> = [];
|
||||
|
||||
export default definePlugin({
|
||||
name: "EquicordCSS",
|
||||
description: "CSS for Equicord users. You will need to look at the settings.",
|
||||
authors: [EquicordDevs.thororen, EquicordDevs.Panniku],
|
||||
dependencies: ["ThemeAttributes"],
|
||||
settings,
|
||||
start() {
|
||||
|
||||
// Push variables to array to iterate on start() and stop()
|
||||
settingsArray.push(
|
||||
settings.store.betterAuthApps,
|
||||
settings.store.betterStatusPicker,
|
||||
settings.store.discordicons,
|
||||
settings.store.gradientButtons,
|
||||
settings.store.nitroThemesFix,
|
||||
settings.store.settingsIcons,
|
||||
settings.store.userReimagined
|
||||
);
|
||||
cssArray.push(
|
||||
betterauthapps,
|
||||
betterstatuspicker,
|
||||
discordicons,
|
||||
gradientbuttons,
|
||||
nitrothemesfix,
|
||||
settingsicons,
|
||||
userreimagined
|
||||
);
|
||||
|
||||
settingsArray.forEach((s, i) => {
|
||||
if (s) enableStyle(cssArray[i]);
|
||||
});
|
||||
},
|
||||
stop() {
|
||||
|
||||
settingsArray.forEach((s, i) => {
|
||||
if (s) disableStyle(cssArray[i]);
|
||||
});
|
||||
|
||||
settingsArray = [];
|
||||
cssArray = [];
|
||||
}
|
||||
});
|
|
@ -24,7 +24,7 @@ import ErrorBoundary from "@components/ErrorBoundary";
|
|||
import { Devs } from "@utils/constants";
|
||||
import definePlugin from "@utils/types";
|
||||
import { findComponentByCodeLazy } from "@webpack";
|
||||
import { Menu, Popout, useState } from "@webpack/common";
|
||||
import { Menu, Popout, useRef, useState } from "@webpack/common";
|
||||
import type { ReactNode } from "react";
|
||||
|
||||
const HeaderBarIcon = findComponentByCodeLazy(".HEADER_BAR_BADGE_TOP:", '.iconBadge,"top"');
|
||||
|
@ -116,6 +116,7 @@ function VencordPopoutIcon() {
|
|||
}
|
||||
|
||||
function VencordPopoutButton() {
|
||||
const buttonRef = useRef(null);
|
||||
const [show, setShow] = useState(false);
|
||||
|
||||
return (
|
||||
|
@ -126,10 +127,12 @@ function VencordPopoutButton() {
|
|||
animation={Popout.Animation.NONE}
|
||||
shouldShow={show}
|
||||
onRequestClose={() => setShow(false)}
|
||||
targetElementRef={buttonRef}
|
||||
renderPopout={() => VencordPopout(() => setShow(false))}
|
||||
>
|
||||
{(_, { isShown }) => (
|
||||
<HeaderBarIcon
|
||||
ref={buttonRef}
|
||||
className="vc-toolbox-btn"
|
||||
onClick={() => setShow(v => !v)}
|
||||
tooltip={isShown ? null : "Equicord Toolbox"}
|
||||
|
|
|
@ -19,9 +19,10 @@
|
|||
import "./styles.css";
|
||||
|
||||
import ErrorBoundary from "@components/ErrorBoundary";
|
||||
import { copyToClipboard } from "@utils/clipboard";
|
||||
import { EquicordDevs } from "@utils/constants";
|
||||
import definePlugin from "@utils/types";
|
||||
import { Clipboard, Toasts } from "@webpack/common";
|
||||
import { Toasts } from "@webpack/common";
|
||||
|
||||
interface User {
|
||||
id: string;
|
||||
|
@ -97,7 +98,7 @@ export default definePlugin({
|
|||
|
||||
copyContactToClipboard() {
|
||||
if (this.contactList) {
|
||||
Clipboard.copy(JSON.stringify(this.contactList));
|
||||
copyToClipboard(JSON.stringify(this.contactList));
|
||||
Toasts.show({
|
||||
message: "Contacts copied to clipboard successfully.",
|
||||
type: Toasts.Type.SUCCESS,
|
||||
|
|
|
@ -5,16 +5,17 @@
|
|||
*/
|
||||
|
||||
import { Text, Tooltip } from "@webpack/common";
|
||||
import type { ComponentProps } from "react";
|
||||
import type { ComponentProps, RefObject } from "react";
|
||||
|
||||
export interface BuilderButtonProps {
|
||||
label?: string | undefined;
|
||||
tooltip?: string | undefined;
|
||||
selectedStyle?: ComponentProps<"div">["style"];
|
||||
buttonProps?: ComponentProps<"div"> | undefined;
|
||||
buttonRef?: RefObject<null>;
|
||||
}
|
||||
|
||||
export const BuilderButton = ({ label, tooltip, selectedStyle, buttonProps }: BuilderButtonProps) => (
|
||||
export const BuilderButton = ({ buttonRef, label, tooltip, selectedStyle, buttonProps }: BuilderButtonProps) => (
|
||||
<Tooltip text={tooltip} shouldShow={!!tooltip}>
|
||||
{tooltipProps => (
|
||||
<div style={{ width: "60px" }}>
|
||||
|
@ -32,6 +33,7 @@ export const BuilderButton = ({ label, tooltip, selectedStyle, buttonProps }: Bu
|
|||
borderRadius: "4px",
|
||||
cursor: "pointer"
|
||||
}}
|
||||
ref={buttonRef}
|
||||
>
|
||||
{!selectedStyle && (
|
||||
<svg
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { Popout } from "@webpack/common";
|
||||
import { Popout, useRef } from "@webpack/common";
|
||||
|
||||
import { BuilderButton, type BuilderButtonProps, CustomColorPicker, type CustomColorPickerProps } from ".";
|
||||
|
||||
|
@ -13,29 +13,34 @@ export interface BuilderColorButtonProps extends Pick<BuilderButtonProps, "label
|
|||
setColor: (color: number | null) => void;
|
||||
}
|
||||
|
||||
export const BuilderColorButton = ({ label, color, setColor, suggestedColors }: BuilderColorButtonProps) => (
|
||||
<Popout
|
||||
position="bottom"
|
||||
renderPopout={() => (
|
||||
<CustomColorPicker
|
||||
value={color}
|
||||
onChange={setColor}
|
||||
showEyeDropper={true}
|
||||
suggestedColors={suggestedColors}
|
||||
/>
|
||||
)}
|
||||
>
|
||||
{popoutProps => {
|
||||
const hexColor = color ? "#" + color.toString(16).padStart(6, "0") : undefined;
|
||||
|
||||
return (
|
||||
<BuilderButton
|
||||
label={label}
|
||||
tooltip={hexColor}
|
||||
selectedStyle={hexColor ? { background: hexColor } : undefined}
|
||||
buttonProps={popoutProps}
|
||||
export function BuilderColorButton({ label, color, setColor, suggestedColors }: BuilderColorButtonProps) {
|
||||
const buttonRef = useRef(null);
|
||||
return (
|
||||
<Popout
|
||||
position="bottom"
|
||||
targetElementRef={buttonRef}
|
||||
renderPopout={() => (
|
||||
<CustomColorPicker
|
||||
value={color}
|
||||
onChange={setColor}
|
||||
showEyeDropper={true}
|
||||
suggestedColors={suggestedColors}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
</Popout>
|
||||
);
|
||||
)}
|
||||
>
|
||||
{popoutProps => {
|
||||
const hexColor = color ? "#" + color.toString(16).padStart(6, "0") : undefined;
|
||||
|
||||
return (
|
||||
<BuilderButton
|
||||
label={label}
|
||||
tooltip={hexColor}
|
||||
selectedStyle={hexColor ? { background: hexColor } : undefined}
|
||||
buttonProps={popoutProps}
|
||||
buttonRef={buttonRef}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
</Popout>
|
||||
);
|
||||
}
|
||||
|
|
83
src/equicordplugins/fastDeleteChannels/index.tsx
Normal file
83
src/equicordplugins/fastDeleteChannels/index.tsx
Normal file
|
@ -0,0 +1,83 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2025 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { EquicordDevs } from "@utils/constants";
|
||||
import definePlugin from "@utils/types";
|
||||
import { Constants, PermissionsBits, PermissionStore, React, RestAPI, useCallback, useEffect, useState } from "@webpack/common";
|
||||
|
||||
const showIcon = () => {
|
||||
const [show, setShow] = useState(false);
|
||||
|
||||
const handleKeys = useCallback(e => {
|
||||
const keysHeld = e.ctrlKey && e.altKey;
|
||||
setShow(keysHeld);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
window.addEventListener("keydown", handleKeys);
|
||||
window.addEventListener("keyup", handleKeys);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener("keydown", handleKeys);
|
||||
window.removeEventListener("keyup", handleKeys);
|
||||
};
|
||||
}, [handleKeys]);
|
||||
|
||||
return show;
|
||||
};
|
||||
|
||||
export default definePlugin({
|
||||
name: "FastDeleteChannels",
|
||||
description: "Adds a trash icon to delete channels when holding ctrl + alt",
|
||||
authors: [EquicordDevs.thororen],
|
||||
patches: [
|
||||
// TY TypingIndicator
|
||||
{
|
||||
find: "UNREAD_IMPORTANT:",
|
||||
replacement: {
|
||||
match: /\.name,{.{0,140}\.children.+?:null(?<=,channel:(\i).+?)/,
|
||||
replace: "$&,$self.TrashIcon($1)"
|
||||
}
|
||||
},
|
||||
{
|
||||
find: "M11 9H4C2.89543 9 2 8.10457 2 7V1C2 0.447715 1.55228 0 1 0C0.447715 0 0 0.447715 0 1V7C0 9.20914 1.79086 11 4 11H11C11.5523 11 12 10.5523 12 10C12 9.44771 11.5523 9 11 9Z",
|
||||
replacement: {
|
||||
match: /mentionsCount:\i.+?null(?<=channel:(\i).+?)/,
|
||||
replace: "$&,$self.TrashIcon($1)"
|
||||
}
|
||||
}
|
||||
],
|
||||
TrashIcon: channel => {
|
||||
const show = showIcon();
|
||||
|
||||
if (!show || !PermissionStore.can(PermissionsBits.MANAGE_CHANNELS, channel)) return null;
|
||||
|
||||
return (
|
||||
<span
|
||||
onClick={() => RestAPI.del({ url: Constants.Endpoints.CHANNEL(channel.id) })}
|
||||
>
|
||||
<svg
|
||||
width="16"
|
||||
height="16"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
color="#ed4245"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M14.25 1c.41 0 .75.34.75.75V3h5.25c.41 0 .75.34.75.75v.5c0 .41-.34.75-.75.75H3.75A.75.75 0 0 1 3 4.25v-.5c0-.41.34-.75.75-.75H9V1.75c0-.41.34-.75.75-.75h4.5Z"
|
||||
/>
|
||||
<path
|
||||
fill="currentColor"
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M5.06 7a1 1 0 0 0-1 1.06l.76 12.13a3 3 0 0 0 3 2.81h8.36a3 3 0 0 0 3-2.81l.75-12.13a1 1 0 0 0-1-1.06H5.07ZM11 12a1 1 0 1 0-2 0v6a1 1 0 1 0 2 0v-6Zm3-1a1 1 0 1 1 1 1v6a1 1 0 1 1-2 0v-6a1 1 0 0 1 1-1Z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
});
|
|
@ -21,7 +21,7 @@ import { disableStyle, enableStyle } from "@api/Styles";
|
|||
import { Devs } from "@utils/constants";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import { findByPropsLazy } from "@webpack";
|
||||
import { ChannelStore, MessageStore, ReactDOM, Toasts } from "@webpack/common";
|
||||
import { ChannelStore, createRoot, MessageStore, Toasts } from "@webpack/common";
|
||||
import Message from "discord-types/general/Message";
|
||||
import { Root } from "react-dom/client";
|
||||
|
||||
|
@ -101,7 +101,7 @@ export default definePlugin({
|
|||
madeComponent = true;
|
||||
element = document.createElement("div");
|
||||
document.querySelector("[class^=base_]")!.appendChild(element);
|
||||
root = ReactDOM.createRoot(element);
|
||||
root = createRoot(element);
|
||||
}
|
||||
root!.render(<ReplyNavigator replies={replies} />);
|
||||
}
|
||||
|
|
|
@ -34,11 +34,10 @@ export default definePlugin({
|
|||
patches: [
|
||||
// Taken from AnonymiseFileNames
|
||||
{
|
||||
find: "instantBatchUpload:",
|
||||
find: 'type:"UPLOAD_START"',
|
||||
replacement: {
|
||||
match: /uploadFiles:(\i),/,
|
||||
replace:
|
||||
"uploadFiles:(...args)=>(args[0].uploads.forEach(f=>f.filename=$self.fixExt(f)),$1(...args)),",
|
||||
match: /await \i\.uploadFiles\((\i),/,
|
||||
replace: "$1.forEach($self.fixExt),$&"
|
||||
},
|
||||
predicate: () => !Settings.plugins.AnonymiseFileNames.enabled,
|
||||
},
|
||||
|
|
53
src/equicordplugins/forwardAnywhere/index.ts
Normal file
53
src/equicordplugins/forwardAnywhere/index.ts
Normal file
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2025 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import { EquicordDevs } from "@utils/constants";
|
||||
import { sendMessage } from "@utils/discord";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import { Message } from "discord-types/general";
|
||||
|
||||
// Taken From Signature :)
|
||||
const settings = definePluginSettings({
|
||||
forwardPreface: {
|
||||
description: "What should forwarded from be prefaced with",
|
||||
type: OptionType.SELECT,
|
||||
options: [
|
||||
{ label: ">", value: ">", default: true },
|
||||
{ label: "-#", value: "-#" }
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
export default definePlugin({
|
||||
name: "ForwardAnywhere",
|
||||
description: "If a forward fails send it as a normal message also allows nsfw forwards",
|
||||
authors: [EquicordDevs.thororen],
|
||||
settings,
|
||||
patches: [
|
||||
{
|
||||
find: "#{intl::MESSAGE_FORWARDING_NSFW_NOT_ALLOWED}",
|
||||
replacement: {
|
||||
match: /if\((\i)\.isNSFW\(\)&&.{0,25}\)\)\)/,
|
||||
replace: "if(false)",
|
||||
}
|
||||
},
|
||||
{
|
||||
find: "#{intl::MESSAGE_ACTION_FORWARD_TO}",
|
||||
replacement: {
|
||||
match: /(?<=let (\i)=.{0,25}rejected.{0,25}\);)(?=.{0,25}message:(\i))/,
|
||||
replace: "if ($1) return $self.sendForward($1,$2);",
|
||||
}
|
||||
},
|
||||
],
|
||||
sendForward(channels: any, message: Message) {
|
||||
for (const c of channels) {
|
||||
sendMessage(c.id, {
|
||||
content: `${message.content}\n${settings.store.forwardPreface} Forwarded from <#${message.channel_id}>`
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
|
@ -6,8 +6,9 @@
|
|||
|
||||
import "./styles.css";
|
||||
|
||||
import { copyToClipboard } from "@utils/clipboard";
|
||||
import { findByPropsLazy } from "@webpack";
|
||||
import { Button, Clipboard, Flex, Forms, Parser, Text, useEffect, useState } from "@webpack/common";
|
||||
import { Button, Flex, Forms, Parser, Text, useEffect, useState } from "@webpack/common";
|
||||
|
||||
import { FriendInvite } from "./types";
|
||||
|
||||
|
@ -51,7 +52,7 @@ function FriendInviteCard({ invite }: { invite: FriendInvite; }) {
|
|||
<CopyButton
|
||||
copyText="Copy"
|
||||
copiedText="Copied!"
|
||||
onClick={() => Clipboard.copy(`https://discord.gg/${invite.code}`)}
|
||||
onClick={() => copyToClipboard(`https://discord.gg/${invite.code}`)}
|
||||
/>
|
||||
</Flex>
|
||||
</Flex>
|
||||
|
|
|
@ -17,9 +17,10 @@ export default definePlugin({
|
|||
{
|
||||
find: "#{intl::ADD_FRIEND})}),(",
|
||||
replacement: {
|
||||
match: /\.Fragment[^]*?children:\[[^]*?}\)/,
|
||||
match: /header,children:\[.*?\{\}\)/,
|
||||
replace: "$&,$self.FriendCodesPanel"
|
||||
}
|
||||
},
|
||||
noWarn: true,
|
||||
}
|
||||
],
|
||||
|
||||
|
|
|
@ -10,10 +10,11 @@ import { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/Co
|
|||
import { DataStore } from "@api/index";
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import { Flex } from "@components/Flex";
|
||||
import { copyToClipboard } from "@utils/clipboard";
|
||||
import { Devs, EquicordDevs } from "@utils/constants";
|
||||
import { ModalContent, ModalFooter, ModalHeader, ModalRoot, ModalSize, openModal } from "@utils/modal";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import { Alerts, Button, Clipboard, ContextMenuApi, FluxDispatcher, Forms, Menu, React, showToast, TextInput, Toasts, useCallback, useState } from "@webpack/common";
|
||||
import { Alerts, Button, ContextMenuApi, FluxDispatcher, Forms, Menu, React, showToast, TextInput, Toasts, useCallback, useState } from "@webpack/common";
|
||||
|
||||
import { addToCollection, cache_collections, createCollection, DATA_COLLECTION_NAME, deleteCollection, fixPrefix, getCollections, getGifById, getItemCollectionNameFromId, moveGifToCollection, refreshCacheCollection, removeFromCollection, renameCollection } from "./utils/collectionManager";
|
||||
import { getFormat } from "./utils/getFormat";
|
||||
|
@ -526,7 +527,7 @@ const RemoveItemContextMenu = ({ type, nameOrId, instance }) => (
|
|||
action={() => {
|
||||
const gifInfo = getGifById(nameOrId);
|
||||
if (!gifInfo) return;
|
||||
Clipboard.copy(gifInfo.url);
|
||||
copyToClipboard(gifInfo.url);
|
||||
showToast("URL copied to clipboard", Toasts.Type.SUCCESS);
|
||||
}}
|
||||
/>
|
||||
|
|
|
@ -76,7 +76,7 @@ export default definePlugin({
|
|||
}
|
||||
},
|
||||
{
|
||||
find: "action:\"PRESS_APP_CONNECTION\"",
|
||||
find: "#{intl::CONNECTIONS}),scrollIntoView",
|
||||
replacement: {
|
||||
match: /(?<=user:(\i).{0,15}displayProfile:(\i).*?CONNECTIONS.{0,100}\}\)\}\))/,
|
||||
replace: ",$self.ProfilePopoutComponent({ user: $1, displayProfile: $2 })"
|
||||
|
|
|
@ -5,10 +5,11 @@
|
|||
*/
|
||||
|
||||
import { definePluginSettings, Settings } from "@api/Settings";
|
||||
import { copyToClipboard } from "@utils/clipboard";
|
||||
import { Devs } from "@utils/constants";
|
||||
import definePlugin, { OptionType, StartAt } from "@utils/types";
|
||||
import { findComponentByCodeLazy } from "@webpack";
|
||||
import { Button, Clipboard, Forms, TextInput, Toasts, useState } from "@webpack/common";
|
||||
import { Button, Forms, TextInput, Toasts, useState } from "@webpack/common";
|
||||
|
||||
import { darkenColorHex, generateRandomColorHex, saturateColorHex } from "./generateTheme";
|
||||
import { themes } from "./themeDefinitions";
|
||||
|
@ -58,10 +59,7 @@ function copyPreset(name: string) {
|
|||
name: "${name}"
|
||||
}
|
||||
`;
|
||||
if (Clipboard.SUPPORTS_COPY) {
|
||||
Clipboard.copy(template);
|
||||
}
|
||||
|
||||
copyToClipboard(template);
|
||||
}
|
||||
|
||||
function CopyPresetComponent() {
|
||||
|
@ -229,9 +227,7 @@ export function ColorPick({ propertyname }: { propertyname: string; }) {
|
|||
|
||||
|
||||
function copyCSS() {
|
||||
if (Clipboard.SUPPORTS_COPY) {
|
||||
Clipboard.copy(getCSS(parseFontContent()));
|
||||
}
|
||||
copyToClipboard(getCSS(parseFontContent()));
|
||||
}
|
||||
|
||||
function parseFontContent() {
|
||||
|
|
75
src/equicordplugins/guildPickerDumper/index.tsx
Normal file
75
src/equicordplugins/guildPickerDumper/index.tsx
Normal file
|
@ -0,0 +1,75 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2024 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/ContextMenu";
|
||||
import { migratePluginSettings } from "@api/Settings";
|
||||
import { Devs, EquicordDevs } from "@utils/constants";
|
||||
import definePlugin from "@utils/types";
|
||||
import { EmojiStore, Menu, StickersStore } from "@webpack/common";
|
||||
import type { Guild } from "discord-types/general";
|
||||
import { zipSync } from "fflate";
|
||||
|
||||
const Patch: NavContextMenuPatchCallback = (children, { guild }: { guild: Guild; }) => {
|
||||
// Assuming "privacy" is the correct ID for the group you want to modify.
|
||||
const group = findGroupChildrenByChildId("privacy", children);
|
||||
|
||||
if (group) {
|
||||
group.push(
|
||||
<>
|
||||
<Menu.MenuItem id="emoji.download" label="Download Emojis" action={() => zipGuildAssets(guild, "emojis")}></Menu.MenuItem>
|
||||
<Menu.MenuItem id="sticker.download" label="Download Stickers" action={() => zipGuildAssets(guild, "stickers")}></Menu.MenuItem>
|
||||
</>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
async function zipGuildAssets(guild: Guild, type: "emojis" | "stickers") {
|
||||
const isEmojis = type === "emojis";
|
||||
const items = isEmojis
|
||||
? EmojiStore.getGuilds()[guild.id]?.emojis
|
||||
: StickersStore.getStickersByGuildId(guild.id);
|
||||
|
||||
if (!items) {
|
||||
return console.log("Server not found!");
|
||||
}
|
||||
|
||||
const fetchAsset = async e => {
|
||||
const ext = e.animated ? ".gif" : ".png";
|
||||
const filename = e.id + ext;
|
||||
const url = isEmojis
|
||||
? `https://${window.GLOBAL_ENV.MEDIA_PROXY_ENDPOINT}/emojis/${filename}?size=512&quality=lossless`
|
||||
: `https://${window.GLOBAL_ENV.MEDIA_PROXY_ENDPOINT}/stickers/${filename}?size=4096&lossless=true`;
|
||||
|
||||
const response = await fetch(url);
|
||||
const blob = await response.blob();
|
||||
return { file: new Uint8Array(await blob.arrayBuffer()), filename };
|
||||
};
|
||||
|
||||
const assetPromises = items.map(e => fetchAsset(e));
|
||||
|
||||
Promise.all(assetPromises)
|
||||
.then(results => {
|
||||
const zipped = zipSync(Object.fromEntries(results.map(({ file, filename }) => [filename, file])));
|
||||
const blob = new Blob([zipped], { type: "application/zip" });
|
||||
const link = document.createElement("a");
|
||||
link.href = URL.createObjectURL(blob);
|
||||
link.download = `${guild.name}-${type}.zip`;
|
||||
link.click();
|
||||
link.remove();
|
||||
})
|
||||
.catch(console.error);
|
||||
}
|
||||
|
||||
migratePluginSettings("GuildPickerDumper", "EmojiDumper");
|
||||
export default definePlugin({
|
||||
name: "GuildPickerDumper",
|
||||
description: "Context menu to dump and download a server's emojis and stickers.",
|
||||
authors: [EquicordDevs.Cortex, Devs.Samwich, EquicordDevs.Synth, EquicordDevs.thororen],
|
||||
contextMenus: {
|
||||
"guild-context": Patch,
|
||||
"guild-header-popout": Patch
|
||||
}
|
||||
});
|
|
@ -24,7 +24,7 @@ export const HiddenServersStore = proxyLazyWebpack(() => {
|
|||
// id try to use .initialize() but i dont know how it works
|
||||
public async load() {
|
||||
const data = await DataStore.get(DB_KEY);
|
||||
if (data) {
|
||||
if (data && data instanceof Set) {
|
||||
this._hiddenGuilds = data;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -77,8 +77,8 @@ export default definePlugin({
|
|||
find: '("guildsnav")',
|
||||
replacement: [
|
||||
{
|
||||
match: /(?<=#{intl::SERVERS}\),children:)(\i)(\)?\.map\(\i\))/g,
|
||||
replace: "$self.useFilteredGuilds($1)$2",
|
||||
match: /(?<=#{intl::SERVERS}\),gap:"xs",children:.{0,100}?)(\i)(\.map\(.{5,30}\}\))/,
|
||||
replace: "$self.useFilteredGuilds($1)$2"
|
||||
},
|
||||
// despite my best efforts, the above doesnt trigger a rerender
|
||||
{
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
import { classes } from "@utils/misc";
|
||||
import { findByCode } from "@webpack";
|
||||
import { Button, Clickable, Menu, Popout, React } from "@webpack/common";
|
||||
import { Button, Clickable, Menu, Popout, React, useRef } from "@webpack/common";
|
||||
|
||||
import { SvgOverFlowIcon } from "../icons/overFlowIcon";
|
||||
|
||||
|
@ -101,6 +101,8 @@ export function NoteBookTabs({ tabs, selectedTabId, onSelectTab }: { tabs: strin
|
|||
);
|
||||
}, [tabs, selectedTabId, onSelectTab, overflowedTabs]);
|
||||
|
||||
const buttonRef = useRef(null);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classes("vc-notebook-tabbar")}
|
||||
|
@ -137,9 +139,11 @@ export function NoteBookTabs({ tabs, selectedTabId, onSelectTab }: { tabs: strin
|
|||
position="bottom"
|
||||
align="right"
|
||||
spacing={0}
|
||||
targetElementRef={buttonRef}
|
||||
>
|
||||
{props => (
|
||||
<Button
|
||||
ref={buttonRef}
|
||||
{...props}
|
||||
className={"vc-notebook-overflow-chevron"}
|
||||
size={Button.Sizes.ICON}
|
||||
|
|
|
@ -4,10 +4,11 @@
|
|||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { copyToClipboard } from "@utils/clipboard";
|
||||
import { classes } from "@utils/misc";
|
||||
import { ModalProps } from "@utils/modal";
|
||||
import { findByCode, findByCodeLazy, findByProps, findComponentByCodeLazy } from "@webpack";
|
||||
import { Clipboard, ContextMenuApi, FluxDispatcher, Menu, NavigationRouter, React } from "@webpack/common";
|
||||
import { ContextMenuApi, FluxDispatcher, Menu, NavigationRouter, React } from "@webpack/common";
|
||||
|
||||
import noteHandler from "../../NoteHandler";
|
||||
import { HolyNotes } from "../../types";
|
||||
|
@ -139,13 +140,13 @@ const NoteContextMenu = (
|
|||
<Menu.MenuItem
|
||||
label="Copy Text"
|
||||
id="copy-text"
|
||||
action={() => Clipboard.copy(note.content)}
|
||||
action={() => copyToClipboard(note.content)}
|
||||
/>
|
||||
{note?.attachments.length ? (
|
||||
<Menu.MenuItem
|
||||
label="Copy Attachment URL"
|
||||
id="copy-url"
|
||||
action={() => Clipboard.copy(note.attachments[0].url)}
|
||||
action={() => copyToClipboard(note.attachments[0].url)}
|
||||
/>) : null}
|
||||
<Menu.MenuItem
|
||||
color="danger"
|
||||
|
@ -181,7 +182,7 @@ const NoteContextMenu = (
|
|||
<Menu.MenuItem
|
||||
label="Copy ID"
|
||||
id="copy-id"
|
||||
action={() => Clipboard.copy(note.id)}
|
||||
action={() => copyToClipboard(note.id)}
|
||||
/>
|
||||
</Menu.Menu>
|
||||
);
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
*/
|
||||
|
||||
import { CodeBlock } from "@components/CodeBlock";
|
||||
import { copyToClipboard } from "@utils/clipboard";
|
||||
import { Margins } from "@utils/margins";
|
||||
import { classes } from "@utils/misc";
|
||||
import {
|
||||
|
@ -41,9 +42,7 @@ function ModalComponent(props: { func: Function; iconName: string; color: number
|
|||
color={Button.Colors.PRIMARY}
|
||||
className={"vc-iv-raw-modal-copy-button"}
|
||||
onClick={() => {
|
||||
// silly typescript
|
||||
// @ts-ignore
|
||||
Clipboard.copy(String(func));
|
||||
copyToClipboard(String(func));
|
||||
Toasts.show({
|
||||
id: Toasts.genId(),
|
||||
message: `Copied raw \`${iconName}\` to clipboard`,
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { copyToClipboard } from "@utils/clipboard";
|
||||
import { getIntlMessage } from "@utils/discord";
|
||||
import { Margins } from "@utils/margins";
|
||||
import { classes } from "@utils/misc";
|
||||
|
@ -27,8 +28,7 @@ export type ClickableProps<T extends "a" | "div" | "span" | "li" = "div"> = Prop
|
|||
export function IconTooltip({ children, copy, className, ...props }: ClickableProps & { children: string; copy: string; }) {
|
||||
return <TooltipContainer text={"Click to copy"} className={className}>
|
||||
<Clickable onClick={() => {
|
||||
// @ts-ignore
|
||||
Clipboard.copy(copy);
|
||||
copyToClipboard(copy);
|
||||
}} {...props}>{children}</Clickable>
|
||||
</TooltipContainer>;
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
import { saveFile } from "@utils/web";
|
||||
import { filters, findAll, findByPropsLazy, waitFor } from "@webpack";
|
||||
import { React, ReactDOM } from "@webpack/common";
|
||||
import { createRoot, React, ReactDOM } from "@webpack/common";
|
||||
import * as t from "@webpack/types";
|
||||
export let _cssColors: string[] = [];
|
||||
export type IconsDef = { [k: string]: t.Icon; };
|
||||
|
@ -82,7 +82,7 @@ export function saveIcon(iconName: string, icon: EventTarget & SVGSVGElement | E
|
|||
|
||||
export function convertComponentToHtml(component?: React.ReactElement): string {
|
||||
const container = document.createElement("div");
|
||||
const root = ReactDOM.createRoot(container);
|
||||
const root = createRoot(container);
|
||||
|
||||
ReactDOM.flushSync(() => root.render(component));
|
||||
const content = container.innerHTML;
|
||||
|
|
|
@ -9,14 +9,11 @@ import "./styles.css";
|
|||
import { EquicordDevs } from "@utils/constants";
|
||||
import { Logger } from "@utils/Logger";
|
||||
import definePlugin from "@utils/types";
|
||||
import { findStoreLazy } from "@webpack";
|
||||
import { StickersStore } from "@webpack/common";
|
||||
|
||||
import { getMimeType, isLinkAnImage, settings, stripDiscordParams } from "./settings";
|
||||
|
||||
const logger = new Logger("ImagePreview", "#FFFFFF");
|
||||
const StickerStore = findStoreLazy("StickersStore") as {
|
||||
getStickerById(id: string): any;
|
||||
};
|
||||
|
||||
let currentPreview: HTMLDivElement | null = null;
|
||||
let currentPreviewFile: HTMLImageElement | HTMLVideoElement | null = null;
|
||||
|
@ -124,7 +121,7 @@ function loadImagePreview(url: string, sticker: boolean) {
|
|||
|
||||
if (sticker) {
|
||||
const stickerId = url.split("/").pop()?.split(".")[0] ?? null;
|
||||
const stickerData = stickerId ? StickerStore.getStickerById(stickerId) : null;
|
||||
const stickerData = stickerId ? StickersStore.getStickerById(stickerId) : null;
|
||||
|
||||
if (stickerData) {
|
||||
switch (stickerData.type) {
|
||||
|
|
107
src/equicordplugins/ingtoninator/index.tsx
Normal file
107
src/equicordplugins/ingtoninator/index.tsx
Normal file
|
@ -0,0 +1,107 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2025 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { addChatBarButton, ChatBarButton, ChatBarButtonFactory, removeChatBarButton } from "@api/ChatButtons";
|
||||
import { addMessagePreSendListener, removeMessagePreSendListener } from "@api/MessageEvents";
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import { EquicordDevs } from "@utils/constants";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import { React } from "@webpack/common";
|
||||
|
||||
const settings = definePluginSettings({
|
||||
showIcon: {
|
||||
type: OptionType.BOOLEAN,
|
||||
default: true,
|
||||
description: "Show a button to toggle the Ingtoninator plugin",
|
||||
restartNeeded: true
|
||||
},
|
||||
isEnabled: {
|
||||
type: OptionType.BOOLEAN,
|
||||
default: true,
|
||||
description: "Enable or disable the Ingtoninator"
|
||||
}
|
||||
});
|
||||
|
||||
const isLegal = (word: string) => {
|
||||
if (word.startsWith("<@")) return false;
|
||||
if (word.endsWith("ington")) return false;
|
||||
if (/^https?:\/\//i.test(word)) return false;
|
||||
if (/[aeouy]$/i.test(word)) return false;
|
||||
return true;
|
||||
};
|
||||
|
||||
const handleMessage = ((channelId, message) => {
|
||||
if (!settings.store.isEnabled) return;
|
||||
if (!message.content || !message.content.trim()) return;
|
||||
|
||||
const words = message.content.trim().split(/\s+/);
|
||||
if (words.length === 0) return;
|
||||
|
||||
let index = -1;
|
||||
let attempts = 0;
|
||||
do {
|
||||
index = Math.floor(Math.random() * words.length);
|
||||
attempts++;
|
||||
} while (!isLegal(words[index]) && attempts < words.length * 2);
|
||||
|
||||
if (isLegal(words[index])) {
|
||||
const word = words[index];
|
||||
if (word.endsWith("ing")) {
|
||||
words[index] = word === word.toUpperCase() ? word + "TON" : word + "ton";
|
||||
} else if (word.endsWith("i") || word.endsWith("I")) {
|
||||
words[index] = word === word.toUpperCase() ? word + "NGTON" : word + "ngton";
|
||||
} else if (word.endsWith("in") || word.endsWith("IN")) {
|
||||
words[index] = word === word.toUpperCase() ? word + "GTON" : word + "gton";
|
||||
} else if (word.endsWith("ing") || word.endsWith("ING")) {
|
||||
words[index] = word === word.toUpperCase() ? word + "TON" : word + "ton";
|
||||
} else if (word.endsWith("ingt") || word.endsWith("INGT")) {
|
||||
words[index] = word === word.toUpperCase() ? word + "ON" : word + "on";
|
||||
} else {
|
||||
words[index] = word === word.toUpperCase() ? word + "INGTON" : word + "ington";
|
||||
}
|
||||
}
|
||||
|
||||
message.content = words.join(" ");
|
||||
});
|
||||
|
||||
const IngtoninatorButton: ChatBarButtonFactory = ({ isMainChat }) => {
|
||||
const { isEnabled, showIcon } = settings.use(["isEnabled", "showIcon"]);
|
||||
const toggle = () => settings.store.isEnabled = !settings.store.isEnabled;
|
||||
|
||||
if (!isMainChat || !showIcon) return null;
|
||||
|
||||
return (
|
||||
<ChatBarButton
|
||||
tooltip={isEnabled ? "Ingtoninator Enabled" : "Ingtoninator Disabled"}
|
||||
onClick={toggle}
|
||||
>
|
||||
{isEnabled ? (
|
||||
<svg width="20" height="20" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256">
|
||||
<path transform="translate(351 -153)" fill="currentcolor" d="M-177.7,334.5c6.3-2.3,12.6-5.2,19.8-8.6c31.9-16.4,51.7-41.7,51.7-41.7s-32.5,0.6-64.4,17 c-4,1.7-7.5,4-10.9,5.7c5.7-7.5,12.1-16.4,18.7-25c25-37.1,31.3-77.3,31.3-77.3s-34.8,21-59.2,58.6c-5.2,7.5-9.8,14.9-13.8,22.7 c1.1-10.3,1.1-22.1,1.1-33.6c0-50-19.8-91.1-19.8-91.1s-19.8,40.5-19.8,91.1c0,12.1,0.6,23.3,1.1,33.6c-4-7.5-8.6-14.9-13.8-22.7 c-25-37.1-59.2-58.6-59.2-58.6s6.3,40,31.3,77.3c6.3,9.2,12.1,17.5,18.7,25c-3.4-2.3-7.5-4-10.9-5.7c-31.9-16.4-64.4-17-64.4-17 s19.8,25.6,51.7,41.7c6.9,3.4,13.2,6.3,19.8,8.6c-4,0.6-8,1.1-12.1,2.3c-30.5,6.4-53.2,23.9-53.2,23.9s27.3,7.5,58.6,1.1 c9.8-2.3,19.8-4.6,27.3-7.5c-1.1,1.1,15.8-8.6,21.6-14.4v60.4h8.6v-61.8c6.3,6.3,22.7,16.4,22.1,14.9c8,2.9,17.5,5.2,27.3,7.5 c30.8,6.3,58.6-1.1,58.6-1.1s-22.1-17.5-53.4-23.8C-169.6,335.7-173.7,335.1-177.7,334.5z" />
|
||||
</svg>
|
||||
) : (
|
||||
<svg width="20" height="20" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256">
|
||||
<path transform="translate(351 -153)" fill="var(--status-danger)" d="M-177.7,334.5c6.3-2.3,12.6-5.2,19.8-8.6c31.9-16.4,51.7-41.7,51.7-41.7s-32.5,0.6-64.4,17 c-4,1.7-7.5,4-10.9,5.7c5.7-7.5,12.1-16.4,18.7-25c25-37.1,31.3-77.3,31.3-77.3s-34.8,21-59.2,58.6c-5.2,7.5-9.8,14.9-13.8,22.7 c1.1-10.3,1.1-22.1,1.1-33.6c0-50-19.8-91.1-19.8-91.1s-19.8,40.5-19.8,91.1c0,12.1,0.6,23.3,1.1,33.6c-4-7.5-8.6-14.9-13.8-22.7 c-25-37.1-59.2-58.6-59.2-58.6s6.3,40,31.3,77.3c6.3,9.2,12.1,17.5,18.7,25c-3.4-2.3-7.5-4-10.9-5.7c-31.9-16.4-64.4-17-64.4-17 s19.8,25.6,51.7,41.7c6.9,3.4,13.2,6.3,19.8,8.6c-4,0.6-8,1.1-12.1,2.3c-30.5,6.4-53.2,23.9-53.2,23.9s27.3,7.5,58.6,1.1 c9.8-2.3,19.8-4.6,27.3-7.5c-1.1,1.1,15.8-8.6,21.6-14.4v60.4h8.6v-61.8c6.3,6.3,22.7,16.4,22.1,14.9c8,2.9,17.5,5.2,27.3,7.5 c30.8,6.3,58.6-1.1,58.6-1.1s-22.1-17.5-53.4-23.8C-169.6,335.7-173.7,335.1-177.7,334.5z" />
|
||||
</svg>
|
||||
)}
|
||||
</ChatBarButton>
|
||||
);
|
||||
};
|
||||
|
||||
export default definePlugin({
|
||||
name: "Ingtoninator",
|
||||
description: "Suffixes 'ington' to a random word in your message",
|
||||
authors: [EquicordDevs.zyqunix],
|
||||
settings,
|
||||
start() {
|
||||
addChatBarButton("Ingtoninator", IngtoninatorButton);
|
||||
addMessagePreSendListener(handleMessage);
|
||||
},
|
||||
stop() {
|
||||
removeChatBarButton("Ingtoninator");
|
||||
removeMessagePreSendListener(handleMessage);
|
||||
}
|
||||
});
|
|
@ -9,7 +9,7 @@ import "./styles.css";
|
|||
import { definePluginSettings } from "@api/Settings";
|
||||
import { Devs } from "@utils/constants";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import { FluxDispatcher, ReactDOM, useEffect, useState } from "@webpack/common";
|
||||
import { createRoot, FluxDispatcher, useEffect, useState } from "@webpack/common";
|
||||
import { Root } from "react-dom/client";
|
||||
|
||||
let jumpscareRoot: Root | undefined;
|
||||
|
@ -38,7 +38,7 @@ function getJumpscareRoot(): Root {
|
|||
element.id = "jumpscare-root";
|
||||
element.classList.add("jumpscare-root");
|
||||
document.body.append(element);
|
||||
jumpscareRoot = ReactDOM.createRoot(element);
|
||||
jumpscareRoot = createRoot(element);
|
||||
}
|
||||
|
||||
return jumpscareRoot;
|
||||
|
|
|
@ -33,7 +33,7 @@ const recentMentionsPopoutClass = findByPropsLazy("recentMentionsPopout");
|
|||
const tabClass = findByPropsLazy("inboxTitle", "tab");
|
||||
const buttonClass = findByPropsLazy("size36");
|
||||
const MenuHeader = findByCodeLazy(".getUnseenInviteCount())");
|
||||
const Popout = findByCodeLazy("#{intl::UNBLOCK_TO_JUMP_TITLE}", "canCloseAllMessages:");
|
||||
const Popout = findByCodeLazy("getProTip", "canCloseAllMessages:");
|
||||
const createMessageRecord = findByCodeLazy(".createFromServer(", ".isBlockedForMessage", "messageReference:");
|
||||
const KEYWORD_ENTRIES_KEY = "KeywordNotify_keywordEntries";
|
||||
const KEYWORD_LOG_KEY = "KeywordNotify_log";
|
||||
|
|
151
src/equicordplugins/lastActive/index.tsx
Normal file
151
src/equicordplugins/lastActive/index.tsx
Normal file
|
@ -0,0 +1,151 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2025 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { NavContextMenuPatchCallback } from "@api/ContextMenu";
|
||||
import { EquicordDevs } from "@utils/constants";
|
||||
import definePlugin from "@utils/types";
|
||||
import { Menu, MessageActions, MessageStore, NavigationRouter, Toasts, UserStore } from "@webpack/common";
|
||||
|
||||
async function findLastMessageFromUser(channelId: string, userId: string) {
|
||||
try {
|
||||
const messageCollection = MessageStore.getMessages(channelId);
|
||||
let messages = messageCollection?.toArray() || [];
|
||||
let userMessage = messages.filter(m => m?.author?.id === userId).pop();
|
||||
if (userMessage) return userMessage.id;
|
||||
try {
|
||||
await MessageActions.fetchMessages({
|
||||
channelId: channelId,
|
||||
limit: 50
|
||||
});
|
||||
|
||||
const updatedCollection = MessageStore.getMessages(channelId);
|
||||
messages = updatedCollection?.toArray() || [];
|
||||
userMessage = messages.filter(m => m?.author?.id === userId).pop();
|
||||
|
||||
if (userMessage) return userMessage.id;
|
||||
} catch (fetchError) {
|
||||
console.error("Error fetching messages:", fetchError);
|
||||
}
|
||||
|
||||
Toasts.show({
|
||||
type: Toasts.Type.FAILURE,
|
||||
message: "Couldn't find any recent messages from this user.",
|
||||
id: Toasts.genId()
|
||||
});
|
||||
return null;
|
||||
} catch (error) {
|
||||
console.error("Error finding last message:", error);
|
||||
Toasts.show({
|
||||
type: Toasts.Type.FAILURE,
|
||||
message: "Failed to find messages. Check console for details.",
|
||||
id: Toasts.genId()
|
||||
});
|
||||
return null;
|
||||
}
|
||||
}
|
||||
async function jumpToLastActive(channel: any, targetUserId?: string) {
|
||||
try {
|
||||
if (!channel) {
|
||||
Toasts.show({
|
||||
type: Toasts.Type.FAILURE,
|
||||
message: "Channel information not available.",
|
||||
id: Toasts.genId()
|
||||
});
|
||||
return;
|
||||
}
|
||||
const guildId = channel.guild_id !== null ? channel.guild_id : "@me";
|
||||
const channelId = channel.id;
|
||||
let userId: string;
|
||||
if (targetUserId) {
|
||||
|
||||
userId = targetUserId;
|
||||
} else {
|
||||
const currentUser = UserStore.getCurrentUser();
|
||||
userId = currentUser.id;
|
||||
}
|
||||
const messageId = await findLastMessageFromUser(channelId, userId);
|
||||
if (messageId) {
|
||||
const url = `/channels/${guildId}/${channelId}/${messageId}`;
|
||||
NavigationRouter.transitionTo(url);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error in jumpToLastActive:", error);
|
||||
Toasts.show({
|
||||
type: Toasts.Type.FAILURE,
|
||||
message: "Failed to jump to message. Check console for details.",
|
||||
id: Toasts.genId()
|
||||
});
|
||||
}
|
||||
}
|
||||
const ChannelContextMenuPatch: NavContextMenuPatchCallback = (children, { channel }) => {
|
||||
children.push(
|
||||
<Menu.MenuItem
|
||||
id="LastActive"
|
||||
label={<span style={{ color: "#aa6746" }}>Your Last Message</span>}
|
||||
icon={LastActiveIcon}
|
||||
action={() => {
|
||||
jumpToLastActive(channel);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
const UserContextMenuPatch: NavContextMenuPatchCallback = (children, { user, channel }) => {
|
||||
if (!channel || !user?.id) return;
|
||||
|
||||
children.push(
|
||||
<Menu.MenuItem
|
||||
id="LastActive"
|
||||
label={<span style={{ color: "#aa6746" }}>User's Last Message</span>}
|
||||
icon={UserLastActiveIcon}
|
||||
action={() => {
|
||||
jumpToLastActive(channel, user.id);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
export function UserLastActiveIcon() {
|
||||
return (
|
||||
<svg
|
||||
viewBox="0 0 52 52"
|
||||
width="20"
|
||||
height="20"
|
||||
fill="#aa6746"
|
||||
>
|
||||
<g>
|
||||
<path d="M11.4,21.6L24.9,7.9c0.6-0.6,1.6-0.6,2.2,0l13.5,13.7c0.6,0.6,0.6,1.6,0,2.2L38.4,26
|
||||
c-0.6,0.6-1.6,0.6-2.2,0l-9.1-9.4c-0.6-0.6-1.6-0.6-2.2,0l-9.1,9.3c-0.6,0.6-1.6,0.6-2.2,0l-2.2-2.2C10.9,23.1,10.9,22.2,11.4,21.6
|
||||
z"/>
|
||||
<path d="M11.4,39.7L24.9,26c0.6-0.6,1.6-0.6,2.2,0l13.5,13.7c0.6,0.6,0.6,1.6,0,2.2l-2.2,2.2
|
||||
c-0.6,0.6-1.6,0.6-2.2,0l-9.1-9.4c-0.6-0.6-1.6-0.6-2.2,0L15.8,44c-0.6,0.6-1.6,0.6-2.2,0l-2.2-2.2C10.9,41.2,10.9,40.2,11.4,39.7z
|
||||
"/>
|
||||
</g>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export function LastActiveIcon() {
|
||||
return (
|
||||
<svg
|
||||
viewBox="0 0 24 24"
|
||||
width="20"
|
||||
height="20"
|
||||
fill="#aa6746"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path fillRule="evenodd" d="M12,2 C17.5228475,2 22,6.4771525 22,12 C22,17.5228475 17.5228475,22 12,22 C6.4771525,22 2,17.5228475 2,12 C2,6.4771525 6.4771525,2 12,2 Z M12,4 C7.581722,4 4,7.581722 4,12 C4,16.418278 7.581722,20 12,20 C16.418278,20 20,16.418278 20,12 C20,7.581722 16.418278,4 12,4 Z M12,6 C12.5128358,6 12.9355072,6.38604019 12.9932723,6.88337887 L13,7 L13,11.5857864 L14.7071068,13.2928932 C15.0976311,13.6834175 15.0976311,14.3165825 14.7071068,14.7071068 C14.3466228,15.0675907 13.7793918,15.0953203 13.3871006,14.7902954 L13.2928932,14.7071068 L11.2928932,12.7071068 C11.1366129,12.5508265 11.0374017,12.3481451 11.0086724,12.131444 L11,12 L11,7 C11,6.44771525 11.4477153,6 12,6 Z" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
export default definePlugin({
|
||||
name: "LastActive",
|
||||
description: "A plugin to jump to last active message from yourself or another user in a channel/server.",
|
||||
authors: [EquicordDevs.Crxa],
|
||||
contextMenus: {
|
||||
"channel-context": ChannelContextMenuPatch,
|
||||
"user-context": UserContextMenuPatch,
|
||||
"thread-context": ChannelContextMenuPatch
|
||||
}
|
||||
});
|
|
@ -30,7 +30,7 @@ type Spinner = ComponentType<Omit<HTMLAttributes<HTMLDivElement>, "children"> &
|
|||
};
|
||||
|
||||
// https://github.com/Kyuuhachi/VencordPlugins/blob/main/MessageLinkTooltip/index.tsx#L11-L33
|
||||
export const Spinner = findComponentByCodeLazy('"pulsingEllipsis"') as Spinner;
|
||||
export const Spinner = findComponentByCodeLazy('"pulsingEllipsis"') as unknown as Spinner;
|
||||
|
||||
export const QrCodeIcon = findComponentByCodeLazy("0v3ZM20");
|
||||
|
||||
|
|
|
@ -14,7 +14,8 @@ async function handleButtonClick() {
|
|||
sendMessage(getCurrentChannel().id, { content: "meow" });
|
||||
}
|
||||
|
||||
const ChatBarIcon: ChatBarButtonFactory = () => {
|
||||
const ChatBarIcon: ChatBarButtonFactory = ({ isMainChat }) => {
|
||||
if (!isMainChat) return null;
|
||||
return (
|
||||
<ChatBarButton tooltip="Meow" onClick={handleButtonClick}>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20px" height="20px" viewBox="0 0 576 512"><path fill="currentColor" d="M320 192h17.1c22.1 38.3 63.5 64 110.9 64c11 0 21.8-1.4 32-4v228c0 17.7-14.3 32-32 32s-32-14.3-32-32V339.2L280 448h56c17.7 0 32 14.3 32 32s-14.3 32-32 32H192c-53 0-96-43-96-96V192.5c0-16.1-12-29.8-28-31.8l-7.9-1c-17.5-2.2-30-18.2-27.8-35.7S50.5 94 68 96.2l7.9 1c48 6 84.1 46.8 84.1 95.3v85.3c34.4-51.7 93.2-85.8 160-85.8m160 26.5c-10 3.5-20.8 5.5-32 5.5c-28.4 0-54-12.4-71.6-32c-3.7-4.1-7-8.5-9.9-13.2C357.3 164 352 146.6 352 128V10.7C352 4.8 356.7.1 362.6 0h.2c3.3 0 6.4 1.6 8.4 4.2v.1l12.8 17l27.2 36.3L416 64h64l4.8-6.4L512 21.3l12.8-17v-.1c2-2.6 5.1-4.2 8.4-4.2h.2c5.9.1 10.6 4.8 10.6 10.7V128c0 17.3-4.6 33.6-12.6 47.6c-11.3 19.8-29.6 35.2-51.4 42.9M432 128a16 16 0 1 0-32 0a16 16 0 1 0 32 0m48 16a16 16 0 1 0 0-32a16 16 0 1 0 0 32" /></svg>
|
||||
|
|
|
@ -89,7 +89,7 @@ export async function exportLogs() {
|
|||
const messages = await getAllMessagesIDB();
|
||||
const data = JSON.stringify({ messages }, null, 2);
|
||||
|
||||
if (IS_WEB || IS_VESKTOP) {
|
||||
if (IS_WEB || IS_VESKTOP || IS_EQUIBOP || !DiscordNative) {
|
||||
const file = new File([data], filename, { type: "application/json" });
|
||||
const a = document.createElement("a");
|
||||
a.href = URL.createObjectURL(file);
|
||||
|
|
|
@ -449,7 +449,7 @@ export const PickerHeader = ({ onQueryChange }: PickerHeaderProps) => {
|
|||
<div>
|
||||
<div className={clPicker("search-box")}>
|
||||
<TextInput
|
||||
style={{ height: "30px" }}
|
||||
style={{ height: "30px", border: "none" }}
|
||||
|
||||
placeholder="Search stickers"
|
||||
autoFocus={true}
|
||||
|
|
|
@ -35,54 +35,34 @@ export default definePlugin({
|
|||
find: "ChannelStickerPickerButton",
|
||||
replacement: [{
|
||||
match: /(children:\(0,\i\.jsx\)\()(.{0,10})({innerClassName.{10,30}\.stickerButton)/,
|
||||
replace: (_, head, button, tail) => {
|
||||
const isMoreStickers = "arguments[0]?.stickersType";
|
||||
return `${head}${isMoreStickers}?$self.stickerButton:${button}${tail}`;
|
||||
}
|
||||
replace: "$1arguments[0]?.stickersType?$self.stickerButton:$2$3"
|
||||
}, {
|
||||
match: /(\i=)(\i\.useCallback.{0,25}\.STICKER,.{0,10});/,
|
||||
replace: (_, decl, cb) => {
|
||||
const newCb = cb.replace(/(?<=\(\)=>\{\(.*?\)\().+?\.STICKER/, "\"stickers+\"");
|
||||
return `${decl}arguments[0]?.stickersType?${newCb}:${cb};`;
|
||||
}
|
||||
match: /(\i=)((\i\.useCallback\(\(\)=>\{\(.*?\)\().*?\.STICKER,(\i.{0,10}));/,
|
||||
replace: '$1arguments[0]?.stickersType?$3"stickers+",$4:$2;'
|
||||
}, {
|
||||
match: /(\i)=((\i)===\i\.\i\.STICKER)/,
|
||||
replace: (_, isActive, isStickerTab, currentTab) => {
|
||||
const c = "arguments[0].stickersType";
|
||||
return `${isActive}=${c}?(${currentTab}===${c}):(${isStickerTab})`;
|
||||
}
|
||||
replace: "$1=arguments[0].stickersType?($3===arguments[0].stickersType):($2)"
|
||||
}]
|
||||
},
|
||||
{
|
||||
find: ".gifts)",
|
||||
replacement: {
|
||||
match: /,.{0,5}\(null==\(\i=\i\.stickers\)\?void 0.*?(\i)\.push\((\(0,\w\.jsx\))\((.+?),{disabled:\i,type:(\i)},"sticker"\)\)\)/,
|
||||
replace: (m, _, jsx, compo, type) => {
|
||||
const c = "arguments[0].type";
|
||||
return `${m},${c}?.submit?.button&&${_}.push(${jsx}(${compo},{disabled:!${c}?.submit?.button,type:${type},stickersType:"stickers+"},"stickers+"))`;
|
||||
}
|
||||
match: /(?<=,.{0,5}\(null==\(\i=\i\.stickers\)\?void 0.*?(\i)\.push\((\(0,\i\.jsx\))\((.+?),{disabled:\i,type:(\i)},"sticker"\)\)\))/,
|
||||
replace: ",arguments[0].type?.submit?.button&&$1.push($2($3,{disabled:!arguments[0].type?.submit?.button,type:$4,stickersType:\"stickers+\"},\"stickers+\"))"
|
||||
}
|
||||
},
|
||||
{
|
||||
find: "#{intl::EXPRESSION_PICKER_CATEGORIES_A11Y_LABEL}",
|
||||
replacement: {
|
||||
match: /role:"tablist",.*?,?"aria-label":.+?\),children:(\[.*?\)\]}\)}\):null,)(.*?closePopout:\w.*?:null)/s,
|
||||
replace: m => {
|
||||
const stickerTabRegex = /(\w+?)\?(\([^()]+?\))\((.{1,2}),{.{0,128},isActive:(.{1,2})===.{1,6}\.STICKER.{1,140},children:(.{1,5}\.string\(.+?\)).*?:null/s;
|
||||
const res = m.replace(stickerTabRegex, (_m, canUseStickers, jsx, tabHeaderComp, currentTab, stickerText) => {
|
||||
const isActive = `${currentTab}==="stickers+"`;
|
||||
return (
|
||||
`${_m},${canUseStickers}?` +
|
||||
`${jsx}(${tabHeaderComp},{id:"stickers+-picker-tab","aria-controls":"more-stickers-picker-tab-panel","aria-selected":${isActive},isActive:${isActive},autoFocus:true,viewType:"stickers+",children:${jsx}("div",{children:${stickerText}+"+"})})` +
|
||||
":null"
|
||||
);
|
||||
});
|
||||
|
||||
return res.replace(/:null,((.{1,200})===.{1,30}\.STICKER&&\w+\?(\([^()]{1,10}\)).{1,15}?(\{.*?,onSelectSticker:.*?\})\):null)/s, (_, _m, currentTab, jsx, props) => {
|
||||
return `:null,${currentTab}==="stickers+"?${jsx}($self.moreStickersComponent,${props}):null,${_m}`;
|
||||
});
|
||||
replacement: [
|
||||
{
|
||||
match: /(?<=null,(\i)\?(\(.*?\))\((\i),{.{0,128},isActive:(\i)===.{0,200},children:(\i\.intl\.string\(.*?\))\}\)\}\):null,)/s,
|
||||
replace: '$1?$2($3,{id:"stickers+-picker-tab","aria-controls":"more-stickers-picker-tab-panel","aria-selected":$4==="stickers+",isActive:$4==="stickers+",autoFocus:true,viewType:"stickers+",children:$5+"+"}):null,'
|
||||
},
|
||||
{
|
||||
match: /:null,((.{1,200})===.{1,30}\.STICKER&&\w+\?(\([^()]{1,10}\)).{1,15}?(\{.*?,onSelectSticker:.*?\})\):null)/,
|
||||
replace: ':null,$2==="stickers+"?$3($self.moreStickersComponent,$4):null,$1'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
find: '==="remove_text"',
|
||||
|
@ -114,8 +94,8 @@ export default definePlugin({
|
|||
>
|
||||
<path d="M18.5 11c-4.136 0-7.5 3.364-7.5 7.5c0 .871.157 1.704.432 2.482l9.551-9.551A7.462 7.462 0 0 0 18.5 11z" />
|
||||
<path d="M12 2C6.486 2 2 6.486 2 12c0 4.583 3.158 8.585 7.563 9.69A9.431 9.431 0 0 1 9 18.5C9 13.262 13.262 9 18.5 9c1.12 0 2.191.205 3.19.563C20.585 5.158 16.583 2 12 2z" />
|
||||
</svg>
|
||||
</button>
|
||||
</svg >
|
||||
</button >
|
||||
);
|
||||
},
|
||||
moreStickersComponent({
|
||||
|
|
|
@ -116,7 +116,7 @@
|
|||
|
||||
.vc-more-stickers-category-scroller,
|
||||
.vc-more-stickers-picker-content-scroller {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-width: none;
|
||||
scrollbar-color: var(--scrollbar-thin-thumb) var(--scrollbar-thin-track);
|
||||
}
|
||||
|
||||
|
|
|
@ -11,8 +11,7 @@ import { ChannelStore, UploadHandler } from "@webpack/common";
|
|||
|
||||
import { FFmpegState, Sticker } from "./types";
|
||||
|
||||
|
||||
const MessageUpload = findByPropsLazy("instantBatchUpload");
|
||||
const MessageUpload = findByPropsLazy("uploadFiles");
|
||||
const CloudUpload = findLazy(m => m.prototype?.trackUploadFinished);
|
||||
const PendingReplyStore = findByPropsLazy("getPendingReply");
|
||||
const MessageUtils = findByPropsLazy("sendMessage");
|
||||
|
|
|
@ -14,7 +14,7 @@ import { FFmpegState } from "./types";
|
|||
export const cl = classNameFactory("vc-more-stickers-");
|
||||
export const clPicker = (className: string, ...args: any[]) => cl("picker-" + className, ...args);
|
||||
|
||||
const CORS_PROXY = "https://corsproxy.io?";
|
||||
const CORS_PROXY = "https://corsproxy.io/?url=";
|
||||
|
||||
function corsUrl(url: string | URL) {
|
||||
return CORS_PROXY + encodeURIComponent(url.toString());
|
||||
|
|
63
src/equicordplugins/moreUserTags/consts.ts
Normal file
63
src/equicordplugins/moreUserTags/consts.ts
Normal file
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2025 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { findByCodeLazy, findLazy } from "@webpack";
|
||||
import { GuildStore } from "@webpack/common";
|
||||
import { RC } from "@webpack/types";
|
||||
import { Channel, Guild, Message, User } from "discord-types/general";
|
||||
|
||||
import type { ITag } from "./types";
|
||||
|
||||
export const isWebhook = (message: Message, user: User) => !!message?.webhookId && user.isNonUserBot();
|
||||
export const tags = [
|
||||
{
|
||||
name: "WEBHOOK",
|
||||
displayName: "Webhook",
|
||||
description: "Messages sent by webhooks",
|
||||
condition: isWebhook
|
||||
}, {
|
||||
name: "OWNER",
|
||||
displayName: "Owner",
|
||||
description: "Owns the server",
|
||||
condition: (_, user, channel) => GuildStore.getGuild(channel?.guild_id)?.ownerId === user.id
|
||||
}, {
|
||||
name: "ADMINISTRATOR",
|
||||
displayName: "Admin",
|
||||
description: "Has the administrator permission",
|
||||
permissions: ["ADMINISTRATOR"]
|
||||
}, {
|
||||
name: "MODERATOR_STAFF",
|
||||
displayName: "Staff",
|
||||
description: "Can manage the server, channels or roles",
|
||||
permissions: ["MANAGE_GUILD", "MANAGE_CHANNELS", "MANAGE_ROLES"]
|
||||
}, {
|
||||
name: "MODERATOR",
|
||||
displayName: "Mod",
|
||||
description: "Can manage messages or kick/ban people",
|
||||
permissions: ["MANAGE_MESSAGES", "KICK_MEMBERS", "BAN_MEMBERS"]
|
||||
}, {
|
||||
name: "VOICE_MODERATOR",
|
||||
displayName: "VC Mod",
|
||||
description: "Can manage voice chats",
|
||||
permissions: ["MOVE_MEMBERS", "MUTE_MEMBERS", "DEAFEN_MEMBERS"]
|
||||
}, {
|
||||
name: "CHAT_MODERATOR",
|
||||
displayName: "Chat Mod",
|
||||
description: "Can timeout people",
|
||||
permissions: ["MODERATE_MEMBERS"]
|
||||
}
|
||||
] as const satisfies ITag[];
|
||||
|
||||
export const Tag = findLazy(m => m.Types?.[0] === "BOT") as RC<{ type?: number | null, className?: string, useRemSizes?: boolean; }> & { Types: Record<string, number>; };
|
||||
|
||||
// PermissionStore.computePermissions will not work here since it only gets permissions for the current user
|
||||
export const computePermissions: (options: {
|
||||
user?: { id: string; } | string | null;
|
||||
context?: Guild | Channel | null;
|
||||
overwrites?: Channel["permissionOverwrites"] | null;
|
||||
checkElevated?: boolean /* = true */;
|
||||
excludeGuildPermissions?: boolean /* = false */;
|
||||
}) => bigint = findByCodeLazy(".getCurrentUser()", ".computeLurkerPermissionsAllowList()");
|
185
src/equicordplugins/moreUserTags/index.tsx
Normal file
185
src/equicordplugins/moreUserTags/index.tsx
Normal file
|
@ -0,0 +1,185 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2025 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import "./styles.css";
|
||||
|
||||
import { classNameFactory } from "@api/Styles";
|
||||
import { Devs, EquicordDevs } from "@utils/constants";
|
||||
import { getCurrentChannel, getIntlMessage } from "@utils/discord";
|
||||
import definePlugin from "@utils/types";
|
||||
import { ChannelStore, GuildStore, PermissionsBits, SelectedChannelStore, UserStore } from "@webpack/common";
|
||||
import { Channel, Message, User } from "discord-types/general";
|
||||
|
||||
import { computePermissions, Tag, tags } from "./consts";
|
||||
import { settings } from "./settings";
|
||||
import { TagSettings } from "./types";
|
||||
|
||||
const cl = classNameFactory("vc-mut-");
|
||||
|
||||
const genTagTypes = () => {
|
||||
let i = 100;
|
||||
const obj = {};
|
||||
|
||||
for (const { name } of tags) {
|
||||
obj[name] = ++i;
|
||||
obj[i] = name;
|
||||
}
|
||||
|
||||
return obj;
|
||||
};
|
||||
|
||||
export default definePlugin({
|
||||
name: "MoreUserTags",
|
||||
description: "Adds tags for webhooks and moderative roles (owner, admin, etc.)",
|
||||
authors: [Devs.Cyn, Devs.TheSun, Devs.RyanCaoDev, Devs.LordElias, Devs.AutumnVN, EquicordDevs.Hen],
|
||||
dependencies: ["MemberListDecoratorsAPI", "NicknameIconsAPI", "MessageDecorationsAPI"],
|
||||
settings,
|
||||
patches: [
|
||||
// Make discord actually use our tags
|
||||
{
|
||||
find: ".STAFF_ONLY_DM:",
|
||||
replacement: [
|
||||
{
|
||||
match: /(?<=type:(\i).{10,1000}.REMIX.{10,100})default:(\i)=/,
|
||||
replace: "default:$2=$self.getTagText($self.localTags[$1]);",
|
||||
},
|
||||
{
|
||||
match: /(?<=type:(\i).{10,1000}.REMIX.{10,100})\.BOT:(?=default:)/,
|
||||
replace: "$&return null;",
|
||||
predicate: () => settings.store.dontShowBotTag
|
||||
},
|
||||
],
|
||||
}
|
||||
],
|
||||
start() {
|
||||
const tagSettings = settings.store.tagSettings || {} as TagSettings;
|
||||
for (const tag of Object.values(tags)) {
|
||||
tagSettings[tag.name] ??= {
|
||||
showInChat: true,
|
||||
showInNotChat: true,
|
||||
text: tag.displayName
|
||||
};
|
||||
}
|
||||
|
||||
settings.store.tagSettings = tagSettings;
|
||||
},
|
||||
localTags: genTagTypes(),
|
||||
getChannelId() {
|
||||
return SelectedChannelStore.getChannelId();
|
||||
},
|
||||
renderNicknameIcon(props) {
|
||||
const tagId = this.getTag({
|
||||
user: UserStore.getUser(props.userId),
|
||||
channel: ChannelStore.getChannel(this.getChannelId()),
|
||||
channelId: this.getChannelId(),
|
||||
isChat: false
|
||||
});
|
||||
|
||||
return tagId && <Tag
|
||||
type={tagId}
|
||||
verified={false}>
|
||||
</Tag>;
|
||||
|
||||
},
|
||||
renderMessageDecoration(props) {
|
||||
const tagId = this.getTag({
|
||||
message: props.message,
|
||||
user: UserStore.getUser(props.message.author.id),
|
||||
channelId: props.message.channel_id,
|
||||
isChat: false
|
||||
});
|
||||
|
||||
return tagId && <Tag
|
||||
useRemSizes={true}
|
||||
className={cl("message-tag", props.message.author.isVerifiedBot() && "message-verified")}
|
||||
type={tagId}
|
||||
verified={false}>
|
||||
</Tag>;
|
||||
},
|
||||
renderMemberListDecorator(props) {
|
||||
const tagId = this.getTag({
|
||||
user: props.user,
|
||||
channel: getCurrentChannel(),
|
||||
channelId: this.getChannelId(),
|
||||
isChat: false
|
||||
});
|
||||
|
||||
return tagId && <Tag
|
||||
type={tagId}
|
||||
verified={false}>
|
||||
</Tag>;
|
||||
},
|
||||
|
||||
getTagText(tagName: string) {
|
||||
if (!tagName) return getIntlMessage("APP_TAG");
|
||||
const tag = tags.find(({ name }) => tagName === name);
|
||||
if (!tag) return tagName || getIntlMessage("APP_TAG");
|
||||
|
||||
return settings.store.tagSettings?.[tag.name]?.text || tag.displayName;
|
||||
},
|
||||
|
||||
getTag({
|
||||
message, user, channelId, isChat, channel
|
||||
}: {
|
||||
message?: Message,
|
||||
user?: User & { isClyde(): boolean; },
|
||||
channel?: Channel & { isForumPost(): boolean; isMediaPost(): boolean; },
|
||||
channelId?: string;
|
||||
isChat?: boolean;
|
||||
}): number | null {
|
||||
const settings = this.settings.store;
|
||||
|
||||
if (!user) return null;
|
||||
if (isChat && user.id === "1") return null;
|
||||
if (user.isClyde()) return null;
|
||||
if (user.bot && settings.dontShowForBots) return null;
|
||||
|
||||
channel ??= ChannelStore.getChannel(channelId!) as any;
|
||||
if (!channel) return null;
|
||||
|
||||
const perms = this.getPermissions(user, channel);
|
||||
|
||||
for (const tag of tags) {
|
||||
if (isChat && !settings.tagSettings[tag.name].showInChat)
|
||||
continue;
|
||||
if (!isChat && !settings.tagSettings[tag.name].showInNotChat)
|
||||
continue;
|
||||
|
||||
// If the owner tag is disabled, and the user is the owner of the guild,
|
||||
// avoid adding other tags because the owner will always match the condition for them
|
||||
if (
|
||||
(tag.name !== "OWNER" &&
|
||||
GuildStore.getGuild(channel?.guild_id)?.ownerId ===
|
||||
user.id &&
|
||||
isChat &&
|
||||
!settings.tagSettings.OWNER.showInChat) ||
|
||||
(!isChat &&
|
||||
!settings.tagSettings.OWNER.showInNotChat)
|
||||
)
|
||||
continue;
|
||||
|
||||
if ("permissions" in tag ?
|
||||
tag.permissions.some(perm => perms.includes(perm)) :
|
||||
tag.condition(message!, user, channel)) {
|
||||
|
||||
return this.localTags[tag.name];
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
getPermissions(user: User, channel: Channel): string[] {
|
||||
const guild = GuildStore.getGuild(channel?.guild_id);
|
||||
if (!guild) return [];
|
||||
|
||||
const permissions = computePermissions({ user, context: guild, overwrites: channel.permissionOverwrites });
|
||||
return Object.entries(PermissionsBits)
|
||||
.map(([perm, permInt]) =>
|
||||
permissions & permInt ? perm : ""
|
||||
)
|
||||
.filter(Boolean);
|
||||
},
|
||||
});
|
104
src/equicordplugins/moreUserTags/settings.tsx
Normal file
104
src/equicordplugins/moreUserTags/settings.tsx
Normal file
|
@ -0,0 +1,104 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2025 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import { Margins } from "@utils/margins";
|
||||
import { OptionType } from "@utils/types";
|
||||
import { Card, Flex, Forms, Switch, TextInput, Tooltip } from "@webpack/common";
|
||||
|
||||
import { Tag, tags } from "./consts";
|
||||
import { TagSettings } from "./types";
|
||||
|
||||
function SettingsComponent() {
|
||||
const tagSettings = settings.store.tagSettings as TagSettings;
|
||||
const { localTags } = Vencord.Plugins.plugins.MoreUserTags as any;
|
||||
|
||||
return (
|
||||
<Flex flexDirection="column">
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
flexWrap: "wrap",
|
||||
gap: "16px",
|
||||
}}
|
||||
>
|
||||
{tags.map(t => (
|
||||
<Card
|
||||
key={t.name}
|
||||
style={{
|
||||
padding: "1em 1em 0",
|
||||
width: "calc(33.333% - 11px)",
|
||||
boxSizing: "border-box",
|
||||
}}
|
||||
>
|
||||
<Forms.FormTitle style={{ width: "fit-content" }}>
|
||||
<Tooltip text={t.description}>
|
||||
{({ onMouseEnter, onMouseLeave }) => (
|
||||
<div
|
||||
onMouseEnter={onMouseEnter}
|
||||
onMouseLeave={onMouseLeave}
|
||||
>
|
||||
{t.displayName} Tag
|
||||
</div>
|
||||
)}
|
||||
</Tooltip>
|
||||
</Forms.FormTitle>
|
||||
|
||||
<div style={{ marginBottom: "10px" }}>
|
||||
<Forms.FormText style={{ fontSize: "13px" }}>
|
||||
Example:
|
||||
</Forms.FormText>
|
||||
<Tag type={localTags[t.name]} />
|
||||
</div>
|
||||
|
||||
<TextInput
|
||||
type="text"
|
||||
value={tagSettings[t.name]?.text ?? t.displayName}
|
||||
placeholder={`Text on tag (default: ${t.displayName})`}
|
||||
onChange={v => tagSettings[t.name].text = v}
|
||||
className={Margins.bottom16}
|
||||
/>
|
||||
|
||||
<Switch
|
||||
value={tagSettings[t.name]?.showInChat ?? true}
|
||||
onChange={v => tagSettings[t.name].showInChat = v}
|
||||
hideBorder
|
||||
>
|
||||
Show in messages
|
||||
</Switch>
|
||||
|
||||
<Switch
|
||||
value={tagSettings[t.name]?.showInNotChat ?? true}
|
||||
onChange={v => tagSettings[t.name].showInNotChat = v}
|
||||
hideBorder
|
||||
>
|
||||
Show in member list and profiles
|
||||
</Switch>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
|
||||
export const settings = definePluginSettings({
|
||||
dontShowForBots: {
|
||||
description: "Don't show extra tags for bots (excluding webhooks)",
|
||||
type: OptionType.BOOLEAN,
|
||||
default: false
|
||||
},
|
||||
dontShowBotTag: {
|
||||
description: "Only show extra tags for bots / Hide [APP] text",
|
||||
type: OptionType.BOOLEAN,
|
||||
default: false,
|
||||
restartNeeded: true
|
||||
},
|
||||
tagSettings: {
|
||||
type: OptionType.COMPONENT,
|
||||
component: SettingsComponent,
|
||||
description: "fill me"
|
||||
}
|
||||
});
|
27
src/equicordplugins/moreUserTags/styles.css
Normal file
27
src/equicordplugins/moreUserTags/styles.css
Normal file
|
@ -0,0 +1,27 @@
|
|||
.vc-message-decorations-wrapper .vc-mut-message-tag {
|
||||
margin-bottom: 1px;
|
||||
}
|
||||
|
||||
/* stylelint-disable-next-line no-descending-specificity */
|
||||
.vc-mut-message-tag {
|
||||
/* Remove default margin from tags in messages */
|
||||
margin-top: unset !important;
|
||||
|
||||
/* Align with Discord default tags in messages */
|
||||
/* stylelint-disable-next-line length-zero-no-unit */
|
||||
bottom: 0px;
|
||||
top: -2px;
|
||||
margin-right: 3px;
|
||||
}
|
||||
|
||||
.vc-mut-message-verified {
|
||||
height: 1rem !important;
|
||||
}
|
||||
|
||||
span[class*="botTagCozy"][data-moreTags-darkFg="true"]>svg>path {
|
||||
fill: #000;
|
||||
}
|
||||
|
||||
span[class*="botTagCozy"][data-moreTags-darkFg="false"]>svg>path {
|
||||
fill: #fff;
|
||||
}
|
32
src/equicordplugins/moreUserTags/types.ts
Normal file
32
src/equicordplugins/moreUserTags/types.ts
Normal file
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2025 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import type { Permissions } from "@webpack/types";
|
||||
import type { Channel, Message, User } from "discord-types/general";
|
||||
|
||||
import { tags } from "./consts";
|
||||
|
||||
export type ITag = {
|
||||
// name used for identifying, must be alphanumeric + underscores
|
||||
name: string;
|
||||
// name shown on the tag itself, can be anything probably; automatically uppercase'd
|
||||
displayName: string;
|
||||
description: string;
|
||||
} & ({
|
||||
permissions: Permissions[];
|
||||
} | {
|
||||
condition?(message: Message | null, user: User, channel: Channel): boolean;
|
||||
});
|
||||
|
||||
export interface TagSetting {
|
||||
text: string;
|
||||
showInChat: boolean;
|
||||
showInNotChat: boolean;
|
||||
}
|
||||
|
||||
export type TagSettings = {
|
||||
[k in typeof tags[number]["name"]]: TagSetting;
|
||||
};
|
73
src/equicordplugins/morse/index.ts
Normal file
73
src/equicordplugins/morse/index.ts
Normal file
|
@ -0,0 +1,73 @@
|
|||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { ApplicationCommandInputType, ApplicationCommandOptionType } from "@api/Commands";
|
||||
import { EquicordDevs } from "@utils/constants";
|
||||
import definePlugin from "@utils/types";
|
||||
|
||||
const morseMap = {
|
||||
A: ".-", B: "-...", C: "-.-.", D: "-..", E: ".", F: "..-.",
|
||||
G: "--.", H: "....", I: "..", J: ".---", K: "-.-", L: ".-..",
|
||||
M: "--", N: "-.", O: "---", P: ".--.", Q: "--.-", R: ".-.",
|
||||
S: "...", T: "-", U: "..-", V: "...-", W: ".--", X: "-..-",
|
||||
Y: "-.--", Z: "--..",
|
||||
0: "-----", 1: ".----", 2: "..---", 3: "...--", 4: "....-",
|
||||
5: ".....", 6: "-....", 7: "--...", 8: "---..", 9: "----.",
|
||||
" ": "/"
|
||||
};
|
||||
|
||||
const toMorse = (text: string) => {
|
||||
return text.toUpperCase().split("").map(char => morseMap[char] ?? "").join(" ");
|
||||
};
|
||||
|
||||
const fromMorse = (text: string) => {
|
||||
const reversedMap = Object.fromEntries(Object.entries(morseMap).map(([k, v]) => [v, k]));
|
||||
const raw = text.split(" ").map(code => reversedMap[code] ?? "").join("").toLowerCase();
|
||||
return raw.charAt(0).toUpperCase() + raw.slice(1);
|
||||
};
|
||||
|
||||
// boo regex
|
||||
const isMorse = (text: string) => /^[.\-/ ]+$/.test(text);
|
||||
|
||||
export default definePlugin({
|
||||
name: "Morse",
|
||||
description: "A slash command to translate to/from morse code.",
|
||||
authors: [EquicordDevs.zyqunix],
|
||||
commands: [
|
||||
{
|
||||
inputType: ApplicationCommandInputType.BUILT_IN_TEXT,
|
||||
name: "morse",
|
||||
description: "Translate to or from Morse code",
|
||||
options: [
|
||||
{
|
||||
name: "text",
|
||||
description: "Text to convert",
|
||||
type: ApplicationCommandOptionType.STRING,
|
||||
required: true
|
||||
}
|
||||
],
|
||||
execute: opts => {
|
||||
const input = opts.find(o => o.name === "text")?.value as string;
|
||||
const output = isMorse(input) ? fromMorse(input) : toMorse(input);
|
||||
return {
|
||||
content: `${output}`
|
||||
};
|
||||
},
|
||||
}
|
||||
]
|
||||
});
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue