Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CustomSounds #1765

Open
wants to merge 26 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
8108fbb
Initial CustomSounds plugin
TheKodeToad Sep 29, 2023
9efd0f0
Extract SoundOverrideComponent
TheKodeToad Sep 29, 2023
c918240
Cleanup
TheKodeToad Oct 2, 2023
d446396
Merge remote-tracking branch 'upstream/dev' into custom-sounds
TheKodeToad Oct 3, 2023
d554581
Merge branch 'Vendicated:main' into custom-sounds
SpikeHD Oct 21, 2023
f0cc5b3
feat: file input
SpikeHD Oct 21, 2023
6363f58
fix: extension filters
SpikeHD Oct 21, 2023
0e37b02
fix: cleanup
SpikeHD Oct 21, 2023
5d46f40
Fix patches and finds
TheKodeToad Nov 29, 2023
c662004
Thank you!!
TheKodeToad Nov 29, 2023
74e958e
Merge branch 'main' into custom-sounds
TheKodeToad Nov 29, 2023
05d00f0
Hack for soundpacks
TheKodeToad Nov 30, 2023
c9b8381
Disable upload button
TheKodeToad Nov 30, 2023
46b94ef
Better file upload! :rocket: :rocket:
TheKodeToad Jan 11, 2024
8fca96f
Merge branch 'main' into custom-sounds
TheKodeToad Jan 11, 2024
95ede96
Add README
TheKodeToad Jan 11, 2024
b129751
Fix plugin README
TheKodeToad Jan 11, 2024
c5ea117
Merge branch 'main' into custom-sounds
TheKodeToad Feb 20, 2024
24fba24
Merge branch 'main' into custom-sounds
TheKodeToad Mar 11, 2024
880c5f0
Merge branch 'dev' into custom-sounds
Vendicated Mar 13, 2024
e9a286b
Make requested changes
TheKodeToad Mar 13, 2024
e84e364
Only allow file upload in *UI*
TheKodeToad Mar 27, 2024
5301ec6
Merge branch 'dev' into custom-sounds
TheKodeToad Mar 27, 2024
5057b90
Add Message (Focused Channel)
TheKodeToad May 14, 2024
bba0dc9
Merge remote-tracking branch 'upstream/dev' into custom-sounds
TheKodeToad Jun 23, 2024
67d4b0c
Fix plugin
TheKodeToad Jun 23, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/plugins/customSounds/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# CustomSounds

Replace Discord's sounds with your own. All sounds toggleable in the Notifications page are also customizable here!

https://github.com/Vendicated/Vencord/assets/57493648/41d35b2f-0db0-479e-bbc7-81a6c2c97e20
119 changes: 119 additions & 0 deletions src/plugins/customSounds/components/SoundOverrideComponent.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2023 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/

import { classNameFactory } from "@api/Styles";
import { makeRange } from "@components/PluginSettings/components";
import { Margins } from "@utils/margins";
import { classes } from "@utils/misc";
import { useForceUpdater } from "@utils/react";
import { findByCodeLazy, findLazy } from "@webpack";
import { Button, Card, Forms, Slider, Switch, useRef } from "@webpack/common";
import { ComponentType, Ref, SyntheticEvent } from "react";

import { SoundOverride, SoundPlayer, SoundType } from "../types";

type FileInput = ComponentType<{
ref: Ref<HTMLInputElement>;
onChange: (e: SyntheticEvent<HTMLInputElement>) => void;
multiple?: boolean;
filters?: { name?: string; extensions: string[]; }[];
}>;

const playSound: (id: string) => SoundPlayer = findByCodeLazy(".playWithListener().then");
const FileInput: FileInput = findLazy(m => m.prototype?.activateUploadDialogue && m.prototype.setRef);
const cl = classNameFactory("vc-custom-sounds-");

export function SoundOverrideComponent({ type, override, onChange }: { type: SoundType; override: SoundOverride; onChange: () => Promise<void>; }) {
const fileInputRef = useRef<HTMLInputElement>(null);
const sound = useRef<SoundPlayer | null>(null);
const update = useForceUpdater();

return (
<Card className={cl("card")}>
<Switch
value={override.enabled}
onChange={value => {
override.enabled = value;
onChange();
update();
}}
className={Margins.bottom16}
hideBorder={true}
>
{type.name} <span className={cl("id")}>({type.id})</span>
</Switch>
<Button
color={Button.Colors.PRIMARY}
className={Margins.bottom16}
onClick={() => {
if (sound.current != null)
sound.current.stop();
sound.current = playSound(type.id);
}}
disabled={!override.enabled}
>
Preview
</Button>
<Forms.FormTitle>Replacement Sound</Forms.FormTitle>
<Button
color={Button.Colors.PRIMARY}
disabled={!override.enabled}
className={classes(Margins.right8, Margins.bottom16, cl("upload"))}
>
Upload
<FileInput
ref={fileInputRef}
onChange={event => {
event.stopPropagation();
event.preventDefault();

if (!event.currentTarget?.files?.length)
return;

const { files } = event.currentTarget;
const file = files[0];

// Set override URL to a data URI
const reader = new FileReader;
reader.onload = () => {
override.url = reader.result as string;
onChange();
update();
};
reader.readAsDataURL(file);
}}
// Sorry .caf lovers, https://en.wikipedia.org/wiki/HTML5_audio#Supported_audio_coding_formats
filters={[{ extensions: ["mp3", "wav", "ogg", "webm", "flac"] }]}
/>
</Button>
<Button
color={Button.Colors.RED}
onClick={() => {
override.url = "";
onChange();
update();
}}
disabled={!(override.enabled && override.url.length !== 0)}
style={{ display: "inline" }}
className={classes(Margins.right8, Margins.bottom16)}
>
Clear
</Button>
<Forms.FormTitle>Volume</Forms.FormTitle>
<Slider
markers={makeRange(0, 100, 10)}
initialValue={override.volume}
onValueChange={value => {
override.volume = value;
onChange();
update();
}}
className={Margins.bottom16}
disabled={!override.enabled}
/>
</Card>
);
}
90 changes: 90 additions & 0 deletions src/plugins/customSounds/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2023 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/

import "./styles.css";

import { DataStore } from "@api/index";
import { definePluginSettings } from "@api/Settings";
import { Devs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types";
import { React } from "@webpack/common";

import { SoundOverrideComponent } from "./components/SoundOverrideComponent";
import { makeEmptyOverride, SoundOverride, soundTypes } from "./types";

const OVERRIDES_KEY = "CustomSounds_overrides";
let overrides: Record<string, SoundOverride> = soundTypes.reduce((result, sound) => ({ ...result, [sound.id]: makeEmptyOverride() }), {});
Vendicated marked this conversation as resolved.
Show resolved Hide resolved

const settings = definePluginSettings({
overrides: {
type: OptionType.COMPONENT,
description: "",
component: () =>
<>
{soundTypes.map(type =>
<SoundOverrideComponent
key={type.id}
type={type}
override={overrides[type.id]}
onChange={() => DataStore.set(OVERRIDES_KEY, overrides)}
/>
)}
</>
}
});

export function isOverriden(id: string): boolean {
return overrides[id]?.enabled ?? false;
}

export function findOverride(id: string): SoundOverride | null {
const result = overrides[id];
if (!result?.enabled)
return null;

return result;
}

export default definePlugin({
name: "CustomSounds",
description: "Replace Discord's sounds with your own.",
authors: [Devs.TheKodeToad, Devs.SpikeHD],
patches: [
// sound class
{
find: 'Error("could not play audio")',
replacement: [
// override URL
{
match: /(?<=new Audio;\i\.src=)\i\([0-9]+\)\("\.\/"\.concat\(this\.name,"\.mp3"\)/,
replace: "$self.findOverride(this.name)?.url || $&"
},
// override volume
{
match: /Math.min\(\i\.\i\.getOutputVolume\(\)\/100\*this\._volume/,
replace: "$& * ($self.findOverride(this.name)?.volume ?? 100) / 100"
}
]
},
// force classic soundpack for overriden sounds
{
find: ".playWithListener().then",
replacement: {
match: /\i\.\i\.getSoundpack\(\)/,
replace: '$self.isOverriden(arguments[0]) ? "classic" : $&'
}
}
],
settings,
findOverride,
isOverriden,
async start() {
overrides = await DataStore.get(OVERRIDES_KEY) ?? {};
for (const type of soundTypes)
overrides[type.id] ??= makeEmptyOverride();
}
});

11 changes: 11 additions & 0 deletions src/plugins/customSounds/styles.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
.vc-custom-sounds-card {
padding: 1em 1em 0;
}

.vc-custom-sounds-id {
color: var(--text-muted);
}

.vc-custom-sounds-upload {
display: inline;
}
58 changes: 58 additions & 0 deletions src/plugins/customSounds/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2023 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/

export interface SoundType {
name: string;
id: string;
}

export interface SoundOverride {
enabled: boolean;
url: string;
volume: number;
}

export interface SoundPlayer {
loop(): void;
play(): void;
pause(): void;
stop(): void;
}

export const soundTypes: readonly SoundType[] = [
MeguminSama marked this conversation as resolved.
Show resolved Hide resolved
{ name: "Message", id: "message1" },
TheKodeToad marked this conversation as resolved.
Show resolved Hide resolved
{ name: "Message (Focused Channel)", id: "message3" },
{ name: "Defean", id: "deafen" },
{ name: "Undefean", id: "undeafen" },
{ name: "Mute", id: "mute" },
{ name: "Unmute", id: "unmute" },
{ name: "Voice Disconnected", id: "disconnect" },
{ name: "PTT Activate", id: "ptt_start" },
{ name: "PTT Deactive", id: "ptt_stop" },
{ name: "User Join", id: "user_join" },
{ name: "User Leave", id: "user_leave" },
{ name: "User Moved", id: "user_moved" },
{ name: "Outgoing Ring", id: "call_calling" },
{ name: "Incoming Ring", id: "call_ringing" },
{ name: "Stream Started", id: "stream_started" },
{ name: "Stream Ended", id: "stream_ended" },
{ name: "Viewer Join", id: "stream_user_joined" },
{ name: "Viewer Leave", id: "stream_user_left" },
{ name: "Activity Start", id: "activity_launch" },
{ name: "Activity End", id: "activity_end" },
{ name: "Activity User Join", id: "activity_user_join" },
{ name: "Activity User Leave", id: "activity_user_left" },
{ name: "Invited to Speak", id: "reconnect" }
] as const;

export function makeEmptyOverride(): SoundOverride {
return {
enabled: false,
useFile: false,

Check failure on line 54 in src/plugins/customSounds/types.ts

View workflow job for this annotation

GitHub Actions / test

Object literal may only specify known properties, and 'useFile' does not exist in type 'SoundOverride'.
url: "",
volume: 100
};
}
4 changes: 4 additions & 0 deletions src/utils/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -529,6 +529,10 @@ export const Devs = /* #__PURE__*/ Object.freeze({
nekohaxx: {
name: "nekohaxx",
id: 1176270221628153886n
},
SpikeHD: {
name: "SpikeHD",
id: 221757857836564485n
}
} satisfies Record<string, Dev>);

Expand Down
Loading