i made a working plugin
This commit is contained in:
parent
2cf6beb560
commit
a83285a4c7
2 changed files with 270 additions and 1 deletions
16
README.md
16
README.md
|
|
@ -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
255
index.tsx
Normal 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();
|
||||
});
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue