From a83285a4c75fbb0d0521db1bf7134d98c31094de Mon Sep 17 00:00:00 2001 From: Rose Date: Wed, 22 May 2024 13:18:33 -0400 Subject: [PATCH] i made a working plugin --- README.md | 16 +++- index.tsx | 255 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 270 insertions(+), 1 deletion(-) create mode 100644 index.tsx diff --git a/README.md b/README.md index e6489b7..ba14970 100644 --- a/README.md +++ b/README.md @@ -1 +1,15 @@ -# Clicker \ No newline at end of file +# Sound Trigger + +### How it works? + +The plugin lets you configure trigger phrases (powered by regex) to play sounds. + +> _I have found https://www.myinstants.com/ to be the easiest place to source direct sound links from_ + +### Installation Guide + +0. [Install Vencord](https://github.com/Vendicated/Vencord/blob/main/docs/1_INSTALLING.md) +1. Clone the repo folder into `vencord/src/userplugins` +2. Build and inject Vencord. +3. Enable the plugin in Vencord settings and add triggers. +4. Enjoy :3 diff --git a/index.tsx b/index.tsx new file mode 100644 index 0000000..e4c2e10 --- /dev/null +++ b/index.tsx @@ -0,0 +1,255 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2024 Rose, Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { DataStore } from "@api/index"; +import { definePluginSettings } from "@api/Settings"; +import { Flex } from "@components/Flex"; +import { DeleteIcon } from "@components/Icons"; +import { makeRange } from "@components/PluginSettings/components/SettingSliderComponent"; +import { sleep } from "@utils/misc"; +import { useForceUpdater } from "@utils/react"; +import definePlugin, { OptionType } from "@utils/types"; +import { + Button, + Forms, + React, + RelationshipStore, + SelectedChannelStore, + TextInput, + useState, +} from "@webpack/common"; +import { Message } from "discord-types/general"; + +interface IMessageCreate { + type: "MESSAGE_CREATE"; + optimistic: boolean; + isPushNotification: boolean; + channelId: string; + message: Message; +} + +interface IRPCNotificationCreate { + type: "RPC_NOTIFICATION_CREATE"; + channelId: string; + message: Message; +} + +const CLICKER_RULES_KEY = "[Clicker]-rules"; + +interface ClickerRule { + trigger: string; + sound: string; +} +let clickerRules = [ + { + sound: "", + trigger: "", + }, +] as ClickerRule[]; + +interface InputProps { + placeholder: string; + initalValue: string; + onChange(newValue: string): void; +} + +function Input({ placeholder, initalValue, onChange }: InputProps) { + const [value, setValue] = useState(initalValue); + return ( + value !== initalValue && onChange(value)} + /> + ); +} + +function RuleInputter({ update }: { update: () => void; }) { + async function deleteRule(index: number) { + if (index === clickerRules.length - 1) return; + clickerRules.splice(index, 1); + + await DataStore.set(CLICKER_RULES_KEY, clickerRules); + update(); + } + + async function modifyRule( + newValue: string, + index: number, + key: keyof ClickerRule + ) { + if (index === clickerRules.length - 1) { + clickerRules.push({ + sound: "", + trigger: "", + }); + } + clickerRules[index][key] = newValue; + + if (clickerRules[index].trigger === "" && clickerRules[index].sound === "" && index !== clickerRules.length - 1) { + clickerRules.splice(index, 1); + } + + await DataStore.set(CLICKER_RULES_KEY, clickerRules); + update(); + } + return ( + <> + + Rules + + {clickerRules.map((rule, ind) => { + return ( + + +
+ modifyRule(e, ind, "trigger")} + /> + modifyRule(e, ind, "sound")} + /> +
+ +
+
+ ); + })} +
+ + ); +} + +const settings = definePluginSettings({ + rules: { + type: OptionType.COMPONENT, + description: "List of triggers to react to", + component: () => { + const update = useForceUpdater(); + return ; + }, + }, + delay: { + description: "Delay between triggers", + type: OptionType.SLIDER, + markers: makeRange(0, 500, 50), + default: 250, + stickToMarkers: true, + }, + volume: { + description: "Volume", + type: OptionType.SLIDER, + markers: makeRange(0, 1, 0.1), + default: 0.5, + stickToMarkers: false, + }, + ignoreBlocked: { + description: "Ignore blocked users", + type: OptionType.BOOLEAN, + default: true, + }, +}); + +export default definePlugin({ + name: "Sound Trigger", + description: "Configure trigger phrases (powered by regex) to play sounds.", + authors: [ + { + id: 172557961133162496n, + name: "Rose (rosasaur)", + }, + ], + settings, + async start() { + clickerRules = (await DataStore.get(CLICKER_RULES_KEY)) ?? clickerRules; + }, + flux: { + async RPC_NOTIFICATION_CREATE({ type, message }: IRPCNotificationCreate) { + if (type !== "RPC_NOTIFICATION_CREATE") return; + if (message.state === "SENDING") return; + if ( + settings.store.ignoreBlocked && + RelationshipStore.isBlocked(message.author?.id) + ) + return; + if (!message.content) return; + + await handleMessage(message); + }, + async MESSAGE_CREATE({ + optimistic, + type, + channelId, + message, + }: IMessageCreate) { + if (optimistic || type !== "MESSAGE_CREATE") return; + if (message.state === "SENDING") return; + if ( + settings.store.ignoreBlocked && + RelationshipStore.isBlocked(message.author?.id) + ) + return; + if (!message.content) return; + if (channelId !== SelectedChannelStore.getChannelId()) return; + + await handleMessage(message); + }, + }, +}); + +async function handleMessage(message: Message) { + let queue = [] as ClickerRule[]; + + for (const rule of clickerRules) { + if (!rule.trigger) continue; + const regex = new RegExp(rule.trigger, "g"); + const count = (message.content.match(regex) ?? []).length; + queue = [...queue, ...Array(count).fill(rule)]; + } + + for (const item of queue) { + await playSound(item.sound); + await sleep(settings.store.delay); + } +} + +function playSound(sound: string) { + return new Promise(res => { + const audio = new Audio(); + audio.src = sound; + audio.volume = settings.store.volume; + audio.addEventListener("ended", () => res()); + audio.play(); + }); +}