From 2985c2034d3e11d78e261e5462ca3eec16e829cc Mon Sep 17 00:00:00 2001 From: Rayanzay Date: Wed, 4 Jun 2025 21:01:43 +1000 Subject: [PATCH] feat: rename Equicord to Ryncord in package.json and add Duck plugin with quack functionality --- package.json | 12 +-- src/equicordplugins/duck/index.ts | 137 +++++++++++++++++++++++++++++ src/equicordplugins/moyai/index.ts | 48 +++++++--- 3 files changed, 180 insertions(+), 17 deletions(-) create mode 100644 src/equicordplugins/duck/index.ts diff --git a/package.json b/package.json index a02b7dcc..efaf839b 100644 --- a/package.json +++ b/package.json @@ -1,18 +1,18 @@ { - "name": "equicord", + "name": "ryncord", "private": "true", "version": "1.12.2", "description": "The other cutest Discord client mod", - "homepage": "https://github.com/Equicord/Equicord#readme", + "homepage": "https://github.com/Rayanzay/ryncord#readme", "bugs": { - "url": "https://github.com/Equicord/Equicord/issues" + "url": "https://github.com/Rayanzay/ryncord/issues" }, "repository": { "type": "git", - "url": "git+https://github.com/Equicord/Equicord.git" + "url": "git+https://github.com/Rayanzay/ryncord.git" }, "license": "GPL-3.0-or-later", - "author": "Equicord", + "author": "Rayanzay", "scripts": { "build": "node --require=./scripts/suppressExperimentalWarnings.js scripts/build/build.mjs", "buildStandalone": "pnpm build --standalone", @@ -119,4 +119,4 @@ "engines": { "node": ">=18" } -} +} \ No newline at end of file diff --git a/src/equicordplugins/duck/index.ts b/src/equicordplugins/duck/index.ts new file mode 100644 index 00000000..bb3aca11 --- /dev/null +++ b/src/equicordplugins/duck/index.ts @@ -0,0 +1,137 @@ +/* + * Vencord, a modification for Discord's desktop app + * Copyright (c) 2022 Vendicated and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . +*/ + +import { Devs } from "@utils/constants"; +import { sleep } from "@utils/misc"; +import definePlugin from "@utils/types"; +import { RelationshipStore, SelectedChannelStore, UserStore } from "@webpack/common"; +import { Message, ReactionEmoji } from "discord-types/general"; + +interface IMessageCreate { + type: "MESSAGE_CREATE"; + optimistic: boolean; + isPushNotification: boolean; + channelId: string; + message: Message; +} + +interface IReactionAdd { + type: "MESSAGE_REACTION_ADD"; + optimistic: boolean; + channelId: string; + messageId: string; + messageAuthorId: string; + userId: string; + emoji: ReactionEmoji; +} + +interface IVoiceChannelEffectSendEvent { + type: string; + emoji?: ReactionEmoji; + channelId: string; + userId: string; + animationType: number; + animationId: number; +} + +const DUCK = "🦆"; +const DUCK_VARIANTS = [":duck:", "🦆", ":🦆:", "duck"] as const; +const DUCK_URL = "https://pub-e77fd37d275f481896833bda931f1d70.r2.dev/moyai.WAV"; + +export default definePlugin({ + name: "Duckcord", + authors: [Devs.rayanzay], + description: "ryncord feature 👀", + required: true, + + flux: { + async MESSAGE_CREATE({ optimistic, type, message, channelId }: IMessageCreate) { + if (optimistic || type !== "MESSAGE_CREATE") return; + if (message.state === "SENDING") return; + if (message.author?.bot) return; + if (RelationshipStore.isBlocked(message.author?.id)) return; + if (!message.content) return; + if (channelId !== SelectedChannelStore.getChannelId()) return; + + const duckCount = getDuckCount(message.content); + + for (let i = 0; i < duckCount; i++) { + quack(); + await sleep(300); + } + }, + + MESSAGE_REACTION_ADD({ optimistic, type, channelId, userId, messageAuthorId, emoji }: IReactionAdd) { + if (optimistic || type !== "MESSAGE_REACTION_ADD") return; + if (UserStore.getUser(userId)?.bot) return; + if (RelationshipStore.isBlocked(messageAuthorId)) return; + if (channelId !== SelectedChannelStore.getChannelId()) return; + + const name = emoji.name.toLowerCase(); + const isDuckEmoji = name.includes("duck") || name === DUCK.toLowerCase(); + if (!isDuckEmoji) return; + + quack(); + }, + + VOICE_CHANNEL_EFFECT_SEND({ emoji }: IVoiceChannelEffectSendEvent) { + if (!emoji?.name) return; + const name = emoji.name.toLowerCase(); + const isDuckEmoji = name.includes("duck") || name === DUCK.toLowerCase(); + if (!isDuckEmoji) return; + + quack(); + } + } +}); + +function countOccurrences(sourceString: string, subString: string) { + let i = 0; + let lastIdx = 0; + while ((lastIdx = sourceString.indexOf(subString, lastIdx) + 1) !== 0) + i++; + + return i; +} + +function countMatches(sourceString: string, pattern: RegExp) { + if (!pattern.global) + throw new Error("pattern must be global"); + + let i = 0; + while (pattern.test(sourceString)) + i++; + + return i; +} + +const customDuckRe = //gi; + +function getDuckCount(message: string) { + const count = countOccurrences(message, DUCK) + + countMatches(message, customDuckRe); + + return Math.min(count, 10); +} + +function quack() { + const audioElement = document.createElement("audio"); + audioElement.src = DUCK_URL; + audioElement.volume = 0.5; // Fixed volume at 50% + audioElement.play(); +} diff --git a/src/equicordplugins/moyai/index.ts b/src/equicordplugins/moyai/index.ts index cbb94bad..8ec4469e 100644 --- a/src/equicordplugins/moyai/index.ts +++ b/src/equicordplugins/moyai/index.ts @@ -52,11 +52,16 @@ interface IVoiceChannelEffectSendEvent { } const MOYAI = "🗿"; -const TIE = "👔"; +const DUCK = "🦆"; +const DUCK_VARIANTS = [":duck:", "🦆", ":🦆:", "duck"] as const; const MOYAI_URL = "https://github.com/Equicord/Equibored/raw/main/sounds/moyai/moyai.mp3"; const MOYAI_URL_HD = "https://github.com/Equicord/Equibored/raw/main/sounds/moyai/moyai.wav"; const MOYAI_URL_ULTRA = "https://pub-e77fd37d275f481896833bda931f1d70.r2.dev/moyai.WAV"; const MOYAI_URL_ULTRA_HD = "https://pub-e77fd37d275f481896833bda931f1d70.r2.dev/moyai.WAV"; +const DUCK_URL = "https://pub-e77fd37d275f481896833bda931f1d70.r2.dev/moyai.WAV"; +const DUCK_URL_HD = "https://pub-e77fd37d275f481896833bda931f1d70.r2.dev/moyai.WAV"; +const DUCK_URL_ULTRA = "https://pub-e77fd37d275f481896833bda931f1d70.r2.dev/moyai.WAV"; +const DUCK_URL_ULTRA_HD = "https://pub-e77fd37d275f481896833bda931f1d70.r2.dev/moyai.WAV"; const settings = definePluginSettings({ volume: { @@ -112,10 +117,10 @@ export default definePlugin({ if (channelId !== SelectedChannelStore.getChannelId()) return; const moyaiCount = getMoyaiCount(message.content); - const hasTie = message.content.includes(TIE); + const hasDuck = hasDuckEmoji(message.content); for (let i = 0; i < moyaiCount; i++) { - boom(hasTie); + boom(hasDuck); await sleep(300); } }, @@ -127,17 +132,19 @@ export default definePlugin({ if (channelId !== SelectedChannelStore.getChannelId()) return; const name = emoji.name.toLowerCase(); - if (name !== MOYAI && !name.includes("moyai") && !name.includes("moai") && name !== TIE.toLowerCase()) return; + const isDuckEmoji = name.includes("duck") || name === DUCK.toLowerCase(); + if (name !== MOYAI && !name.includes("moyai") && !name.includes("moai") && !isDuckEmoji) return; - boom(name === TIE.toLowerCase()); + boom(isDuckEmoji); }, VOICE_CHANNEL_EFFECT_SEND({ emoji }: IVoiceChannelEffectSendEvent) { if (!emoji?.name) return; const name = emoji.name.toLowerCase(); - if (name !== MOYAI && !name.includes("moyai") && !name.includes("moai") && name !== TIE.toLowerCase()) return; + const isDuckEmoji = name.includes("duck") || name === DUCK.toLowerCase(); + if (name !== MOYAI && !name.includes("moyai") && !name.includes("moai") && !isDuckEmoji) return; - boom(name === TIE.toLowerCase()); + boom(isDuckEmoji); } } }); @@ -163,6 +170,7 @@ function countMatches(sourceString: string, pattern: RegExp) { } const customMoyaiRe = //gi; +const customDuckRe = //gi; function getMoyaiCount(message: string) { const count = countOccurrences(message, MOYAI) @@ -171,13 +179,31 @@ function getMoyaiCount(message: string) { return Math.min(count, 10); } -function boom(forcedUltra = false) { +function hasDuckEmoji(message: string) { + // Reset regex state since it's global + customDuckRe.lastIndex = 0; + + // Check for regular duck emoji and :duck: text + const hasBasicDuck = DUCK_VARIANTS.some(variant => message.includes(variant)); + + // Check for custom server emoji + const hasCustomDuck = customDuckRe.test(message); + + return hasBasicDuck || hasCustomDuck; +} + +function boom(isDuck = false) { if (!settings.store.triggerWhenUnfocused && !document.hasFocus()) return; const audioElement = document.createElement("audio"); - audioElement.src = (forcedUltra || settings.store.ultraMode) - ? (settings.store.quality === "HD" ? MOYAI_URL_ULTRA_HD : MOYAI_URL_ULTRA) - : (settings.store.quality === "HD" ? MOYAI_URL_HD : MOYAI_URL); + // Use the same quality/ultra logic for both moyai and duck sounds + audioElement.src = settings.store.ultraMode + ? (settings.store.quality === "HD" + ? (isDuck ? DUCK_URL_ULTRA_HD : MOYAI_URL_ULTRA_HD) + : (isDuck ? DUCK_URL_ULTRA : MOYAI_URL_ULTRA)) + : (settings.store.quality === "HD" + ? (isDuck ? DUCK_URL_HD : MOYAI_URL_HD) + : (isDuck ? DUCK_URL : MOYAI_URL)); audioElement.volume = settings.store.volume; audioElement.play();