/* * 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(); }); }