i made a working plugin

This commit is contained in:
Rose 2024-05-22 13:18:33 -04:00
parent 2cf6beb560
commit a83285a4c7
No known key found for this signature in database
2 changed files with 270 additions and 1 deletions

View file

@ -1 +1,15 @@
# Clicker
# 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

255
index.tsx Normal file
View file

@ -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 (
<TextInput
placeholder={placeholder}
value={value}
onChange={setValue}
spellCheck={false}
onBlur={() => 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 (
<>
<Forms.FormDivider />
<Forms.FormTitle tag="h4">Rules</Forms.FormTitle>
<Flex flexDirection="column" style={{ gap: "0.5em" }}>
{clickerRules.map((rule, ind) => {
return (
<React.Fragment key={`${rule.trigger}-${ind}`}>
<Flex flexDirection="row" style={{ gap: "0.5em" }}>
<div
style={{
flexGrow: 1,
gap: "0.5em",
display: "grid",
gridTemplateColumns: "1fr 1fr",
}}
>
<Input
placeholder="Trigger"
initalValue={rule.trigger}
onChange={e => modifyRule(e, ind, "trigger")}
/>
<TextInput
placeholder="Sound"
value={rule.sound}
onChange={e => modifyRule(e, ind, "sound")}
/>
</div>
<Button
size={Button.Sizes.ICON}
onClick={() => deleteRule(ind)}
style={{
background: "none",
color: "var(--status-danger)",
...(ind === clickerRules.length - 1
? {
visibility: "hidden",
pointerEvents: "none",
}
: {}),
}}
>
<DeleteIcon />
</Button>
</Flex>
</React.Fragment>
);
})}
</Flex>
</>
);
}
const settings = definePluginSettings({
rules: {
type: OptionType.COMPONENT,
description: "List of triggers to react to",
component: () => {
const update = useForceUpdater();
return <RuleInputter update={update} />;
},
},
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<void>(res => {
const audio = new Audio();
audio.src = sound;
audio.volume = settings.store.volume;
audio.addEventListener("ended", () => res());
audio.play();
});
}