Add Some Philhk Plugins

This commit is contained in:
thororen 2024-04-24 12:37:02 -04:00
parent 41021fe666
commit 6164e729e2
94 changed files with 5489 additions and 7 deletions

View file

@ -77,6 +77,7 @@
"stylelint-config-standard": "^33.0.0",
"tsx": "^3.12.7",
"type-fest": "^3.9.0",
"typed-emitter": "^2.1.0",
"typescript": "^5.0.4",
"zip-local": "^0.3.5",
"zustand": "^3.7.2"

30
pnpm-lock.yaml generated
View file

@ -1,9 +1,5 @@
lockfileVersion: '6.0'
settings:
autoInstallPeers: true
excludeLinksFromLockfile: false
patchedDependencies:
'@types/less@3.0.6':
hash: krcufrsfhsuxuoj7hocqugs6zi
@ -132,6 +128,9 @@ devDependencies:
type-fest:
specifier: ^3.9.0
version: 3.9.0
typed-emitter:
specifier: ^2.1.0
version: 2.1.0
typescript:
specifier: ^5.0.4
version: 5.0.4
@ -2594,6 +2593,14 @@ packages:
queue-microtask: 1.2.3
dev: true
/rxjs@7.8.1:
resolution: {integrity: sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==}
requiresBuild: true
dependencies:
tslib: 2.6.2
dev: true
optional: true
/safe-buffer@5.2.1:
resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
dev: true
@ -2889,6 +2896,11 @@ packages:
resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==}
dev: true
/tslib@2.6.2:
resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==}
dev: true
optional: true
/tsutils@3.21.0(typescript@5.0.4):
resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==}
engines: {node: '>= 6'}
@ -2942,6 +2954,12 @@ packages:
engines: {node: '>=14.16'}
dev: true
/typed-emitter@2.1.0:
resolution: {integrity: sha512-g/KzbYKbH5C2vPkaXGu8DJlHrGKHLsM25Zg9WuC9pMGfuvT+X25tZQWo5fK1BjBm8+UrVE9LDCvaY0CQk+fXDA==}
optionalDependencies:
rxjs: 7.8.1
dev: true
/typescript@5.0.4:
resolution: {integrity: sha512-cW9T5W9xY37cc+jfEnaUvX91foxtHkza3Nw3wkoF4sSlKn0MONdkdEndig/qPBWXNkmplh3NzayQzCiHM4/hqw==}
engines: {node: '>=12.20'}
@ -3123,3 +3141,7 @@ packages:
name: gifenc
version: 1.0.3
dev: false
settings:
autoInstallPeers: true
excludeLinksFromLockfile: false

View file

@ -0,0 +1,331 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2023 Vendicated and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Flex } from "@components/Flex";
import { Switch } from "@components/Switch";
import { ModalSize } from "@utils/modal";
import { Card, Forms, Select, Slider, TextInput, useEffect, useState } from "@webpack/common";
import { SelectOption } from "@webpack/types";
import {
ProfilableStore,
SettingsModal,
SettingsModalCard,
SettingsModalCardItem,
SettingsModalCardRow,
SettingsModalProfilesCard,
validateNumberInput,
validateTextInputNumber
} from "../../philsPluginLibrary";
import { Styles } from "../../philsPluginLibrary/styles";
import { MicrophoneProfile, MicrophoneStore } from "../stores";
const simpleVoiceBitrates: readonly SelectOption[] = [
{
label: "Normal",
value: 96
},
{
label: "Medium-High",
value: 160
},
{
label: "High",
value: 320
},
{
label: "Very-High",
value: 512
}
] as const;
export interface MicrophoneSettingsModalProps extends React.ComponentProps<typeof SettingsModal> {
microphoneStore: ProfilableStore<MicrophoneStore, MicrophoneProfile>;
showInfo?: boolean;
}
export const MicrophoneSettingsModal = (props: MicrophoneSettingsModalProps) => {
const { microphoneStore, showInfo } = props;
const {
currentProfile,
simpleMode,
setSimpleMode,
deleteProfile,
duplicateProfile,
getCurrentProfile,
getDefaultProfiles,
getProfile,
getProfiles,
isCurrentProfileADefaultProfile,
profiles,
saveProfile,
setChannels,
setChannelsEnabled,
setCurrentProfile,
setFreq,
setFreqEnabled,
setPacsize,
setPacsizeEnabled,
setRate,
setRateEnabled,
setVoiceBitrate,
setVoiceBitrateEnabled
} = microphoneStore.use();
const {
name,
channels,
channelsEnabled,
freq,
freqEnabled,
pacsize,
pacsizeEnabled,
rate,
rateEnabled,
voiceBitrate,
voiceBitrateEnabled
} = currentProfile;
const [isSaving, setIsSaving] = useState(false);
const [rateInput, setRateInput] = useState<string>(rate ? rate.toString() : "");
const [freqInput, setFreqInput] = useState<string>(freq ? freq.toString() : "");
const [pacsizeInput, setPacsizeInput] = useState<string>(pacsize ? pacsize.toString() : "");
const [channelsInput, setChannelsInput] = useState<string>(channels ? channels.toString() : "");
useEffect(() => {
setRateInput(rate ? rate.toString() : "");
setFreqInput(freq ? freq.toString() : "");
setPacsizeInput(pacsize ? pacsize.toString() : "");
setChannelsInput(channels ? channels.toString() : "");
}, [rate, freq, pacsize, channels]);
const simpleToggle =
<Flex style={{ justifyContent: "center", alignItems: "center", gap: "0.6em" }}>
<Forms.FormTitle style={{ margin: 0 }} tag="h5">Simple</Forms.FormTitle>
<Switch checked={simpleMode ?? false} disabled={isSaving} onChange={checked => setSimpleMode(checked)} />
</Flex>;
const settingsCardVoiceBitrateSimple =
<SettingsModalCard
title="Audio Bitrate"
switchEnabled
flex={0.8}
switchProps={{
checked: voiceBitrateEnabled ?? false,
disabled: isSaving,
onChange: status => setVoiceBitrateEnabled(status)
}}>
<SettingsModalCardItem>
<Select
isDisabled={!voiceBitrateEnabled || isSaving}
options={simpleVoiceBitrates}
select={(value: number) => setVoiceBitrate(value)}
isSelected={(value: number) => value === voiceBitrate}
serialize={() => ""} />
</SettingsModalCardItem>
</SettingsModalCard>;
const settingsCardChannelsSimple =
<SettingsModalCard
title="Stereo"
flex={0.2}
switchEnabled
switchProps={{
checked: (channelsEnabled && channels === 2) ?? false,
disabled: isSaving,
onChange: status => void setChannelsEnabled(status) ?? setChannels(2)
}}>
</SettingsModalCard>;
const settingsCardVoiceBitrate =
<SettingsModalCard
title="Audio Bitrate"
switchEnabled
flex={0.4}
switchProps={{
checked: voiceBitrateEnabled ?? false,
disabled: isSaving,
onChange: status => setVoiceBitrateEnabled(status)
}}>
<SettingsModalCardItem title="Kb/s">
<div style={{ paddingTop: "0.3em", paddingRight: "0.4em", paddingLeft: "0.4em", boxSizing: "border-box" }}>
<Slider
disabled={!voiceBitrateEnabled || isSaving}
onValueChange={value => setVoiceBitrate(value)}
initialValue={voiceBitrate || 8}
minValue={8}
maxValue={512}
markers={[8, 96, 320, 512]}
onValueRender={value => `${value.toFixed(0)}kb/s`} />
</div>
</SettingsModalCardItem>
</SettingsModalCard>;
const settingsCardRate =
<SettingsModalCard
title="Sample Rate"
switchEnabled
switchProps={{
checked: rateEnabled ?? false,
disabled: isSaving,
onChange: status => setRateEnabled(status)
}}>
<SettingsModalCardItem>
<TextInput
disabled={!rateEnabled || isSaving}
value={rateInput}
onChange={value => validateTextInputNumber(value) && setRateInput(value)}
onBlur={e => {
const result = validateNumberInput(e.target.value);
setRate(result);
setRateInput(result ? result.toString() : "");
}} />
</SettingsModalCardItem>
</SettingsModalCard>;
const settingsCardFreq =
<SettingsModalCard
title="Sample Frequency"
switchEnabled
switchProps={{
checked: freqEnabled ?? false,
disabled: isSaving,
onChange: status => setFreqEnabled(status)
}}>
<SettingsModalCardItem>
<TextInput
disabled={!freqEnabled || isSaving}
value={freqInput}
onChange={value => validateTextInputNumber(value) && setFreqInput(value)}
onBlur={e => {
const result = validateNumberInput(e.target.value);
setFreq(result);
setFreqInput(result ? result.toString() : "");
}} />
</SettingsModalCardItem>
</SettingsModalCard>;
const settingsCardPacsize =
<SettingsModalCard
title="Pac Size"
switchEnabled
switchProps={{
checked: pacsizeEnabled ?? false,
disabled: isSaving,
onChange: status => setPacsizeEnabled(status)
}}>
<SettingsModalCardItem>
<TextInput
disabled={!pacsizeEnabled || isSaving}
value={pacsizeInput}
onChange={value => validateTextInputNumber(value) && setPacsizeInput(value)}
onBlur={e => {
const result = validateNumberInput(e.target.value);
setPacsize(result);
setPacsizeInput(result ? result.toString() : "");
}} />
</SettingsModalCardItem>
</SettingsModalCard>;
const settingsCardChannels =
<SettingsModalCard
title="Channels"
switchEnabled
switchProps={{
checked: channelsEnabled ?? false,
disabled: isSaving,
onChange: status => setChannelsEnabled(status)
}}>
<SettingsModalCardItem>
<TextInput
disabled={!channelsEnabled || isSaving}
value={channelsInput}
onChange={value => validateTextInputNumber(value) && setChannelsInput(value)}
onBlur={e => {
const result = validateNumberInput(e.target.value);
setChannels(result);
setChannelsInput(result ? result.toString() : "");
}} />
</SettingsModalCardItem>
</SettingsModalCard>;
const settingsCardProfiles =
<SettingsModalProfilesCard
flex={0.6}
onSaveStateChanged={state => setIsSaving(state)}
profileableStore={microphoneStore} />;
const infoCard =
<Card style={{ ...Styles.infoCard }}>
<Forms.FormTitle tag="h5">Important</Forms.FormTitle>
<Forms.FormText>
To take full advantage of this plugin, please disable <span style={{ fontWeight: "bold" }}>Krisp</span> and <span style={{ fontWeight: "bold" }}>Echo Cancellation</span>, otherwise features like Stereo (Channels) will not work.
</Forms.FormText>
</Card>;
return (
<SettingsModal
size={simpleMode ? ModalSize.DYNAMIC : ModalSize.DYNAMIC}
title="Microphone Settings"
closeButtonName="Apply"
footerContent={
<Flex style={{ justifyContent: "center", alignItems: "center", marginLeft: "auto" }}>
{simpleToggle}
</Flex>
}
{...props}
onDone={() => {
props.onClose();
props.onDone && props.onDone();
}}
>
{simpleMode
? <div style={{ width: "30em", display: "flex", flexDirection: "column", gap: "1em" }}>
<SettingsModalCardRow>
{settingsCardVoiceBitrateSimple}
{settingsCardChannelsSimple}
</SettingsModalCardRow>
{showInfo &&
<SettingsModalCardRow>
{infoCard}
</SettingsModalCardRow>
}
</div>
: <div style={{ display: "flex", flexDirection: "column", width: "50em", gap: "1em", maxHeight: "30em" }}>
<SettingsModalCardRow>
{settingsCardFreq}
{settingsCardRate}
{settingsCardPacsize}
{settingsCardChannels}
</SettingsModalCardRow>
<SettingsModalCardRow>
{settingsCardVoiceBitrate}
{settingsCardProfiles}
</SettingsModalCardRow>
{showInfo &&
<SettingsModalCardRow>
{infoCard}
</SettingsModalCardRow>
}
</div>
}
</SettingsModal>
);
};

View file

@ -0,0 +1,19 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2023 Vendicated and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
export * from "./MicrophoneSettingsModal";

View file

@ -0,0 +1,31 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2023 Vendicated and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Devs } from "@utils/constants";
import { types } from "../../philsPluginLibrary";
export const PluginInfo = {
PLUGIN_NAME: "BetterMicrophone",
DESCRIPTION: "This plugin allows you to further customize your microphone.",
AUTHOR: {
...Devs.philhk,
github: "https://github.com/philhk"
},
CONTRIBUTORS: {}
} as const satisfies types.PluginInfo;

View file

@ -0,0 +1,19 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2023 Vendicated and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
export * from "./constants";

View file

@ -0,0 +1,57 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2023 Vendicated and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { PluginAuthor, PluginDef } from "@utils/types";
import { addSettingsPanelButton, Emitter, MicrophoneSettingsIcon, removeSettingsPanelButton } from "../philsPluginLibrary";
import { PluginInfo } from "./constants";
import { openMicrophoneSettingsModal } from "./modals";
import { MicrophonePatcher } from "./patchers";
import { initMicrophoneStore } from "./stores";
export default new class Plugin implements PluginDef {
readonly name: string;
readonly description: string;
readonly authors: PluginAuthor[];
readonly dependencies: string[];
public microphonePatcher?: MicrophonePatcher;
constructor() {
this.name = PluginInfo.PLUGIN_NAME;
this.description = PluginInfo.DESCRIPTION;
this.authors = [PluginInfo.AUTHOR, ...Object.values(PluginInfo.CONTRIBUTORS)] as PluginAuthor[];
this.dependencies = ["PhilsPluginLibrary"];
}
start(): void {
initMicrophoneStore();
this.microphonePatcher = new MicrophonePatcher().patch();
addSettingsPanelButton({ name: PluginInfo.PLUGIN_NAME, icon: MicrophoneSettingsIcon, tooltipText: "Microphone Settings", onClick: openMicrophoneSettingsModal });
}
stop(): void {
this.microphonePatcher?.unpatch();
Emitter.removeAllListeners(PluginInfo.PLUGIN_NAME);
removeSettingsPanelButton(PluginInfo.PLUGIN_NAME);
}
};

View file

@ -0,0 +1,23 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2023 Vendicated and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Logger } from "@utils/Logger";
import { PluginInfo } from "../constants";
export const logger = new Logger(PluginInfo.PLUGIN_NAME);

View file

@ -0,0 +1,43 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2023 Vendicated and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { openModalLazy } from "@utils/modal";
import { MicrophoneSettingsModal } from "../components";
import { PluginInfo } from "../constants";
import Plugin from "../index";
import { microphoneStore } from "../stores";
const onMicrophoneModalDone = () => {
const { microphonePatcher } = Plugin;
if (microphonePatcher)
microphonePatcher.forceUpdateTransportationOptions();
};
export const openMicrophoneSettingsModal =
() => openModalLazy(async () => {
return props =>
<MicrophoneSettingsModal
onDone={onMicrophoneModalDone}
showInfo
microphoneStore={microphoneStore}
author={PluginInfo.AUTHOR}
contributors={Object.values(PluginInfo.CONTRIBUTORS)}
{...props} />;
});

View file

@ -0,0 +1,19 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2023 Vendicated and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
export * from "./microphone";

View file

@ -0,0 +1,71 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2023 Vendicated and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Emitter, MediaEngineStore, Patcher, types } from "../../philsPluginLibrary";
import { patchConnectionAudioTransportOptions } from "../../philsPluginLibrary/patches/audio";
import { PluginInfo } from "../constants";
import { logger } from "../logger";
import { microphoneStore } from "../stores";
export class MicrophonePatcher extends Patcher {
private mediaEngineStore: types.MediaEngineStore;
private mediaEngine: types.MediaEngine;
public connection?: types.Connection;
public oldSetTransportOptions: (...args: any[]) => void;
public forceUpdateTransportationOptions: () => void;
constructor() {
super();
this.mediaEngineStore = MediaEngineStore;
this.mediaEngine = this.mediaEngineStore.getMediaEngine();
this.oldSetTransportOptions = () => void 0;
this.forceUpdateTransportationOptions = () => void 0;
}
public patch(): this {
this.unpatch();
const { get } = microphoneStore;
const connectionEventFunction =
(connection: types.Connection) => {
if (connection.context !== "default") return;
this.connection = connection;
const { oldSetTransportOptions, forceUpdateTransportationOptions } = patchConnectionAudioTransportOptions(connection, get, logger);
this.oldSetTransportOptions = oldSetTransportOptions;
this.forceUpdateTransportationOptions = forceUpdateTransportationOptions;
};
Emitter.addListener(
this.mediaEngine.emitter,
"on",
"connection",
connectionEventFunction,
PluginInfo.PLUGIN_NAME
);
return this;
}
public unpatch(): this {
return this._unpatch();
}
}

View file

@ -0,0 +1,19 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2023 Vendicated and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
export * from "./microphoneStore";

View file

@ -0,0 +1,94 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2023 Vendicated and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { createPluginStore, ProfilableInitializer, ProfilableStore, profileable, ProfileableProfile } from "../../philsPluginLibrary";
import { PluginInfo } from "../constants";
export interface MicrophoneProfile {
freq?: number,
pacsize?: number,
channels?: number,
rate?: number,
voiceBitrate?: number;
freqEnabled?: boolean,
pacsizeEnabled?: boolean;
channelsEnabled?: boolean;
rateEnabled?: boolean;
voiceBitrateEnabled?: boolean;
}
export interface MicrophoneStore {
simpleMode?: boolean;
setSimpleMode: (enabled?: boolean) => void;
setFreq: (freq?: number) => void;
setPacsize: (pacsize?: number) => void;
setChannels: (channels?: number) => void;
setRate: (rate?: number) => void;
setVoiceBitrate: (voiceBitrate?: number) => void;
setFreqEnabled: (enabled?: boolean) => void;
setPacsizeEnabled: (enabled?: boolean) => void;
setChannelsEnabled: (enabled?: boolean) => void;
setRateEnabled: (enabled?: boolean) => void;
setVoiceBitrateEnabled: (enabled?: boolean) => void;
}
export const defaultMicrophoneProfiles = {
normal: {
name: "Normal",
channels: 2,
channelsEnabled: true,
voiceBitrate: 96,
voiceBitrateEnabled: true
},
high: {
name: "High",
channels: 2,
channelsEnabled: true,
voiceBitrate: 320,
voiceBitrateEnabled: true
},
} as const satisfies Record<string, MicrophoneProfile & ProfileableProfile>;
export const microphoneStoreDefault: ProfilableInitializer<MicrophoneStore, MicrophoneProfile> = (set, get) => ({
simpleMode: true,
setSimpleMode: enabled => get().simpleMode = enabled,
setChannels: channels => get().currentProfile.channels = channels,
setRate: rate => get().currentProfile.rate = rate,
setVoiceBitrate: voiceBitrate => get().currentProfile.voiceBitrate = voiceBitrate,
setPacsize: pacsize => get().currentProfile.pacsize = pacsize,
setFreq: freq => get().currentProfile.freq = freq,
setChannelsEnabled: enabled => get().currentProfile.channelsEnabled = enabled,
setFreqEnabled: enabled => get().currentProfile.freqEnabled = enabled,
setPacsizeEnabled: enabled => get().currentProfile.pacsizeEnabled = enabled,
setRateEnabled: enabled => get().currentProfile.rateEnabled = enabled,
setVoiceBitrateEnabled: enabled => get().currentProfile.voiceBitrateEnabled = enabled,
});
export let microphoneStore: ProfilableStore<MicrophoneStore, MicrophoneProfile>;
export const initMicrophoneStore = () =>
microphoneStore = createPluginStore(
PluginInfo.PLUGIN_NAME,
"MicrophoneStore",
profileable(
microphoneStoreDefault,
{ name: "" },
Object.values(defaultMicrophoneProfiles)
)
);

View file

@ -0,0 +1,30 @@
# Better Screenshare Plugin
## How to use each Setting?
- **Audio Source** - Choose a window from the drop-down list and the audio will be shared whether you're streaming a window or a screen.
- **Resolution** - Here you can set the width and the height (both are required). The ideal resolution is the resolution of the monitor you are streaming. An example would be `1080p` (height: `1920` width: `1080`).
- **Framerate** - Here you can set the frame rate. The most common frame rate is `60`, but you can set it higher or lower if you like.
- **Keyframe Interval** - Here you can set the keyframe interval in milliseconds. If you don't know anything about it, just leave it disabled, but if you want to understand it better, read through this [article](https://filmora.wondershare.com/video-editing/keyframe-interval-obs.html).
- **Video Bitrate** - This is one of the most important settings as it affects the quality the most. Discord uses a low bitrate by default, so it's important to set it if you want good quality. If you want to find out your optimal bitrate, go to the video bitrate [section](https://github.com/Vendicated/Vencord/tree/main/src/plugins/betterScreenshare#video-bitrate).
- **Audio Bitrate** - This is almost the same as video bitrate, but it works a little differently for audio. If you want to learn more about it, you can read through this [article](https://www.adobe.com/creativecloud/video/discover/audio-bitrate.html). But I would recommend setting it between `96kb/s` and `320kb/s` (higher means better).
- **Video Codec** - Here you can set your video codec. Discord currently supports 4 codecs. Each codec offers different quality and performance. The most popular codec is **H264**, which I recommend. However, if you have a 40 series card, use the **AV1** codec.
- **AV1** - Only supported on 40-series cards.
- **VP8**
- **VP9**
- **H264**
- **HDR** - Allows streaming in HDR.
## Bitrate
To find your optimal bitrate, you must first find your upload speed. You can do this by running a speed test on this [website](https://www.speedtest.net/). When you're done, you'll see a number in the upload field that represents your maximum upload speed in (`Mbps`) `Mb/s`. To use the upload you need to convert it from `Mb/s` to `Kb/s` by multiplying it by `1000`. I'd recommend going a bit lower as you probably won't always hit your max upload speed and it will affect your ping. Heres is a example `5000 Kb/s` -> `4000 Kb/s`.
**IMPORTANT**: Discord added a cap of `10000 Kb/s`, if you went higher everyone would experience packet loss. It is not known if there is a bypass for this.
## Presets
If you don't want to do any of the above, you can just try the presets in the Profile Options tab and see what works best for you. And if you want to change a setting in the preset, you can simply copy it and save it as a new one.
## Known Issues
~~When sharing a window directly it can sometimes happen that no changes are applied, the reason for this is currently unknown, but to avoid this you can simply share your whole screen and set the audio source if required.~~

View file

@ -0,0 +1,62 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2023 Vendicated and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Select, useEffect, useState } from "@webpack/common";
import React from "react";
import { MediaEngineStore, types } from "../../philsPluginLibrary";
import { screenshareStore } from "../stores";
export const AudioSourceSelect = (props?: typeof Select["defaultProps"]) => {
const { use } = screenshareStore;
const { audioSource, setAudioSource } = use();
const [windowPreviews, setWindowPreviews] = useState<types.WindowPreview[]>([]);
useEffect(() => {
const intervalFn = async () => {
const newPreviews = await MediaEngineStore.getMediaEngine().getWindowPreviews(1, 1);
setWindowPreviews(oldPreviews => [...oldPreviews, ...newPreviews].filter((preview, index, array) => array.findIndex(t => t.id === preview.id) === index));
};
intervalFn();
const intervals = [
setInterval(async () => {
intervalFn();
}, 4000), setInterval(async () => {
setWindowPreviews(await MediaEngineStore.getMediaEngine().getWindowPreviews(1, 1));
}, 30000)
];
return () => intervals.forEach(interval => clearInterval(interval));
}, []);
return (
<Select
options={windowPreviews.map(({ name, id }) => ({
label: name,
value: id
}))}
isSelected={value => audioSource === value}
select={value => setAudioSource(value)}
serialize={() => ""}
{...props}
></Select>
);
};

View file

@ -0,0 +1,38 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2023 Vendicated and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Button } from "@webpack/common";
import React from "react";
import { openScreenshareModal } from "../modals";
export interface OpenScreenshareSettingsButtonProps {
title?: string;
}
export const OpenScreenshareSettingsButton = (props: OpenScreenshareSettingsButtonProps) => {
return (
<Button
size={Button.Sizes.SMALL}
color={Button.Colors.PRIMARY}
onClick={openScreenshareModal}
>
{props.title ? props.title : "Screenshare Settings"}
</Button>
);
};

View file

@ -0,0 +1,467 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2023 Vendicated and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Flex } from "@components/Flex";
import { Switch } from "@components/Switch";
import { ModalSize, openModalLazy } from "@utils/modal";
import { Button, Card, Forms, React, Select, Slider, TextInput, useEffect, useState } from "@webpack/common";
import { SelectOption } from "@webpack/types";
import { MicrophoneSettingsModal } from "../../betterMicrophone.desktop/components";
import {
MediaEngineStore,
openURL,
ProfilableStore,
SettingsModal,
SettingsModalCard,
SettingsModalCardItem,
SettingsModalCardRow,
SettingsModalProfilesCard,
types,
validateNumberInput,
validateTextInputNumber
} from "../../philsPluginLibrary";
import { Styles } from "../../philsPluginLibrary/styles";
import { PluginInfo } from "../constants";
import { ScreenshareAudioProfile, ScreenshareAudioStore, ScreenshareProfile, ScreenshareStore } from "../stores";
const simpleResolutions: readonly (SelectOption & { value: types.Resolution; })[] = [
{
label: "480p",
value: {
height: 480,
width: 720
}
},
{
label: "720p",
value: {
height: 720,
width: 1280
}
},
{
label: "1080p",
value: {
height: 1080,
width: 1920
}
},
{
label: "1440p",
value: {
height: 1440,
width: 2560
}
},
{
label: "2160p",
value: {
height: 2160,
width: 3840
}
}
] as const;
const simpleVideoBitrates: readonly SelectOption[] = [
{
label: "Low",
value: 2500
},
{
label: "Medium",
value: 5000
},
{
label: "Medium-High",
value: 7500
},
{
label: "High",
value: 10000
}
] as const;
export interface ScreenshareSettingsModalProps extends React.ComponentProps<typeof SettingsModal> {
screenshareStore: ProfilableStore<ScreenshareStore, ScreenshareProfile>;
screenshareAudioStore?: ProfilableStore<ScreenshareAudioStore, ScreenshareAudioProfile>;
onAudioDone?: () => void;
}
export const ScreenshareSettingsModal = (props: ScreenshareSettingsModalProps) => {
const { screenshareStore, screenshareAudioStore, onAudioDone } = props;
const {
currentProfile,
profiles,
simpleMode,
setVideoBitrateEnabled,
setVideoCodec,
setVideoCodecEnabled,
setFramerate,
setFramerateEnabled,
setHeight,
setKeyframeInterval,
setKeyframeIntervalEnabled,
setResolutionEnabled,
setVideoBitrate,
setWidth,
setCurrentProfile,
getProfile,
saveProfile,
setHdrEnabled,
setSimpleMode,
deleteProfile,
duplicateProfile,
getCurrentProfile,
getProfiles
} = screenshareStore.use();
const {
name,
framerate,
framerateEnabled,
height,
keyframeInterval,
keyframeIntervalEnabled,
resolutionEnabled,
videoBitrate,
videoBitrateEnabled,
videoCodec,
videoCodecEnabled,
width,
hdrEnabled
} = currentProfile;
const [videoCodecs, setVideoCodecs] = useState<types.CodecCapabilities[]>([]);
const [isSaving, setIsSaving] = useState(false);
const [textinputWidth, setTextinputWidth] = useState<string>(width ? width.toString() : "");
const [textinputHeight, setTextinputHeight] = useState<string>(height ? height.toString() : "");
const [textinputFramerate, setTextinputFramerate] = useState<string>(framerate ? framerate.toString() : "");
const [textinputKeyframeInterval, setTextinputKeyframeInterval] = useState<string>(keyframeInterval ? keyframeInterval.toString() : "");
useEffect(() => {
setTextinputWidth(width ? width.toString() : "");
setTextinputHeight(height ? height.toString() : "");
setTextinputFramerate(framerate ? framerate.toString() : "");
setTextinputKeyframeInterval(keyframeInterval ? keyframeInterval.toString() : "");
}, [width, height, framerate, keyframeInterval]);
useEffect(() => {
(async () => {
const mediaEngine = MediaEngineStore.getMediaEngine();
const stringifiedCodecs: types.CodecCapabilities[] = JSON.parse(
await new Promise(res => mediaEngine.getCodecCapabilities(res))
);
setVideoCodecs(stringifiedCodecs);
})();
}, []);
const settingsCardResolutionSimple =
<SettingsModalCard
title="Resolution"
switchEnabled
switchProps={{
checked: resolutionEnabled ?? false,
disabled: isSaving,
onChange: status => setResolutionEnabled(status)
}}>
<SettingsModalCardItem>
<Select
isDisabled={!resolutionEnabled || isSaving}
options={simpleResolutions}
select={(value: types.Resolution) => void setWidth(value.width) ?? setHeight(value.height)}
isSelected={(value: types.Resolution) => width === value.width && height === value.height}
serialize={() => ""} />
</SettingsModalCardItem>
</SettingsModalCard>;
const settingsCardVideoBitrateSimple =
<SettingsModalCard
title="Video Bitrate"
switchEnabled
switchProps={{
checked: videoBitrateEnabled ?? false,
disabled: isSaving,
onChange: status => setVideoBitrateEnabled(status)
}}>
<SettingsModalCardItem>
<Select
isDisabled={!videoBitrateEnabled || isSaving}
options={simpleVideoBitrates}
select={(value: number) => void setVideoBitrate(value)}
isSelected={(value: number) => videoBitrate === value}
serialize={() => ""} />
</SettingsModalCardItem>
</SettingsModalCard>;
const settingsCardResolution =
<SettingsModalCard
title="Resolution"
flex={0.5}
switchEnabled
switchProps={{
checked: resolutionEnabled ?? false,
onChange: status => setResolutionEnabled(status),
disabled: isSaving
}}>
<SettingsModalCardItem title="Width">
<TextInput
disabled={!resolutionEnabled || isSaving}
value={textinputWidth}
onChange={value => validateTextInputNumber(value) && setTextinputWidth(value)}
onBlur={e => {
const result = validateNumberInput(e.target.value);
setWidth(result);
setTextinputWidth(result ? result.toString() : "");
}} />
</SettingsModalCardItem>
<SettingsModalCardItem title="Height">
<TextInput
disabled={!resolutionEnabled || isSaving}
value={textinputHeight}
onChange={value => validateTextInputNumber(value) && setTextinputHeight(value)}
onBlur={e => {
const result = validateNumberInput(e.target.value);
setHeight(result);
setTextinputHeight(result ? result.toString() : "");
}} />
</SettingsModalCardItem>
</SettingsModalCard>;
const settingsCardItemFramerate =
<SettingsModalCardItem>
<TextInput
disabled={!framerateEnabled || isSaving}
value={textinputFramerate}
onChange={value => validateTextInputNumber(value) && setTextinputFramerate(value)}
onBlur={e => {
const result = validateNumberInput(e.target.value);
setFramerate(result);
setTextinputFramerate(result ? result.toString() : "");
}} />
</SettingsModalCardItem>;
const settingsCardFramerateProps: React.ComponentProps<typeof SettingsModalCard> = {
title: "Framerate",
switchEnabled: true,
switchProps: {
checked: framerateEnabled ?? false,
disabled: isSaving,
onChange: status => setFramerateEnabled(status)
}
};
const settingsCardFramerate =
<SettingsModalCard
{...settingsCardFramerateProps}
flex={0.25}
>
{settingsCardItemFramerate}
</SettingsModalCard>;
const settingsCardFramerateSimple =
<SettingsModalCard
{...settingsCardFramerateProps}
>
{settingsCardItemFramerate}
</SettingsModalCard>;
const settingsCardKeyframeInterval =
<SettingsModalCard
title="Keyframe Interval (ms)"
flex={0.25}
switchEnabled
switchProps={{
checked: keyframeIntervalEnabled ?? false,
disabled: isSaving,
onChange: status => setKeyframeIntervalEnabled(status)
}}>
<SettingsModalCardItem>
<TextInput
disabled={!keyframeIntervalEnabled || isSaving}
value={textinputKeyframeInterval}
onChange={value => validateTextInputNumber(value) && setTextinputKeyframeInterval(value)}
onBlur={e => {
const result = validateNumberInput(e.target.value);
setKeyframeInterval(result);
setTextinputKeyframeInterval(result ? result.toString() : "");
}} />
</SettingsModalCardItem>
</SettingsModalCard>;
const settingsCardVideoBitrate =
<SettingsModalCard
title="Video Bitrate"
flex={0.4}
switchEnabled
switchProps={{
checked: videoBitrateEnabled ?? false,
disabled: isSaving,
onChange: status => setVideoBitrateEnabled(status)
}}>
<SettingsModalCardItem title="Kb/s">
<div style={{ paddingTop: "0.3em", paddingRight: "0.4em", paddingLeft: "0.4em", boxSizing: "border-box" }}>
<Slider
disabled={!videoBitrateEnabled || isSaving}
onValueChange={value => setVideoBitrate(value)}
initialValue={videoBitrate || 500}
minValue={500}
maxValue={10000}
markers={[500, 10000]}
onValueRender={value => `${value.toFixed(0)}kb/s`} />
</div>
</SettingsModalCardItem>
</SettingsModalCard>;
const settingsCardAudioProps: React.ComponentProps<typeof SettingsModalCard> = {
title: "Audio Settings"
};
const settingsCardItemAudio =
<SettingsModalCardItem >
<Button
color={Button.Colors.PRIMARY}
size={Button.Sizes.SMALL}
onClick={() => {
if (screenshareAudioStore)
openModalLazy(async () => {
return props_ =>
<MicrophoneSettingsModal
author={props.author}
contributors={props.contributors}
title="Screenshare Audio Settings"
onDone={onAudioDone}
microphoneStore={screenshareAudioStore}
{...props_} />;
});
}}
children={"Open"} />
</SettingsModalCardItem>;
const settingsCardAudio =
<SettingsModalCard
{...settingsCardAudioProps}
flex={0.2}>
{settingsCardItemAudio}
</SettingsModalCard>;
const settingsCardAudioSimple =
<SettingsModalCard
{...settingsCardAudioProps}>
{settingsCardItemAudio}
</SettingsModalCard>;
const settingsCardVideoCodec =
<SettingsModalCard
title="Video Codec"
flex={0.4}
switchEnabled
switchProps={{
checked: videoCodecEnabled ?? false,
disabled: isSaving,
onChange: status => setVideoCodecEnabled(status)
}}>
<SettingsModalCardItem>
<Select
isDisabled={!videoCodecEnabled || isSaving}
isSelected={value => value === videoCodec}
options={videoCodecs.map(codecCapabilities => ({ label: codecCapabilities.codec, value: codecCapabilities.codec }))}
select={value => setVideoCodec(value)}
serialize={() => ""} />
</SettingsModalCardItem>
</SettingsModalCard>;
const settingsCardHdr =
<SettingsModalCard
title="Hdr"
flex={0.1}
switchEnabled
switchProps={{
checked: hdrEnabled ?? false,
disabled: isSaving,
onChange: status => setHdrEnabled(status)
}} />;
const guideCard =
<Card style={{ ...Styles.infoCard, flex: 0.4 }}>
<Forms.FormTitle tag="h5">How to use?</Forms.FormTitle>
<Forms.FormText>If you want to know more about the settings or possible issues, please read <a onClick={() => openURL(PluginInfo.README + "#better-screenshare-plugin")}>this</a>.</Forms.FormText>
</Card>;
const settingsCardProfiles =
<SettingsModalProfilesCard flex={0.5} onSaveStateChanged={state => setIsSaving(state)} profileableStore={screenshareStore} />;
const simpleToggle =
<Flex style={{ justifyContent: "center", alignItems: "center", gap: "0.6em" }}>
<Forms.FormTitle style={{ margin: 0 }} tag="h5">Simple</Forms.FormTitle>
<Switch checked={simpleMode ?? false} disabled={isSaving} onChange={checked => setSimpleMode(checked)} />
</Flex>;
return (
<SettingsModal
size={simpleMode ? ModalSize.DYNAMIC : ModalSize.LARGE}
title="Screenshare Settings"
closeButtonName="Apply"
footerContent={
<Flex style={{ justifyContent: "center", alignItems: "center", marginLeft: "auto" }}>
{simpleToggle}
</Flex>
}
{...props}
onDone={() => {
props.onClose();
props.onDone && props.onDone();
}}
>
{simpleMode
? <div style={{ width: "55em" }}>
<SettingsModalCardRow>
{settingsCardResolutionSimple}
{settingsCardFramerateSimple}
{settingsCardVideoBitrateSimple}
{screenshareAudioStore && settingsCardAudioSimple}
</SettingsModalCardRow>
</div>
: <>
<SettingsModalCardRow>
{settingsCardResolution}
{settingsCardFramerate}
{settingsCardKeyframeInterval}
</SettingsModalCardRow>
<SettingsModalCardRow>
{settingsCardVideoBitrate}
{settingsCardVideoCodec}
{screenshareAudioStore && settingsCardAudio}
</SettingsModalCardRow>
<SettingsModalCardRow>
{guideCard}
{settingsCardHdr}
{settingsCardProfiles}
</SettingsModalCardRow>
</>
}
</SettingsModal>
);
};

View file

@ -0,0 +1,21 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2023 Vendicated and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
export * from "./AudioSourceSelect";
export * from "./OpenScreenshareSettingsButton";
export * from "./ScreenshareSettingsModal";

View file

@ -0,0 +1,38 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2023 Vendicated and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Devs } from "@utils/constants";
import { types } from "../../philsPluginLibrary";
export const PluginInfo = {
PLUGIN_NAME: "BetterScreenshare",
DESCRIPTION: "This plugin allows you to further customize your screen sharing.",
AUTHOR: {
...Devs.philhk,
github: "https://github.com/philhk"
},
CONTRIBUTORS: {
walrus: {
github: "https://github.com/philhk",
id: 305317288775778306n,
name: "walrus"
},
},
README: "https://github.com/Vendicated/Vencord/tree/main/src/plugins/betterScreenshare"
} as const satisfies types.PluginInfo;

View file

@ -0,0 +1,19 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2023 Vendicated and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
export * from "./constants";

View file

@ -0,0 +1,86 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2023 Vendicated and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { definePluginSettings } from "@api/Settings";
import { DefinedSettings, OptionType, Patch, PluginAuthor, PluginDef, SettingsDefinition } from "@utils/types";
import { addSettingsPanelButton, Emitter, removeSettingsPanelButton, ScreenshareSettingsIcon } from "../philsPluginLibrary";
import { PluginInfo } from "./constants";
import { openScreenshareModal } from "./modals";
import { ScreenshareAudioPatcher, ScreensharePatcher } from "./patchers";
import { replacedScreenshareModalComponent } from "./patches";
import { initScreenshareAudioStore, initScreenshareStore } from "./stores";
export default new class Plugin implements PluginDef {
readonly name: string;
readonly description: string;
readonly authors: PluginAuthor[];
readonly patches: Omit<Patch, "plugin">[];
readonly settings: DefinedSettings<SettingsDefinition, {}>;
readonly dependencies: string[];
private readonly replacedScreenshareModalComponent: typeof replacedScreenshareModalComponent;
public screensharePatcher?: ScreensharePatcher;
public screenshareAudioPatcher?: ScreenshareAudioPatcher;
constructor() {
this.name = PluginInfo.PLUGIN_NAME;
this.description = PluginInfo.DESCRIPTION;
this.authors = [PluginInfo.AUTHOR, ...Object.values(PluginInfo.CONTRIBUTORS)] as PluginAuthor[];
this.patches = [
{
find: "Messages.SCREENSHARE_RELAUNCH",
replacement: {
match: /(function .{1,2}\(.{1,2}\){)(.{1,40}(?=selectGuild).+?(?:]}\)}\)))(})/,
replace: "$1return $self.replacedScreenshareModalComponent(function(){$2}, this, arguments)$3"
}
}
];
this.settings = definePluginSettings({
hideDefaultSettings: {
type: OptionType.BOOLEAN,
description: "Hide Discord screen sharing settings",
default: true,
}
});
this.dependencies = ["PhilsPluginLibrary"];
this.replacedScreenshareModalComponent = replacedScreenshareModalComponent;
}
start(): void {
initScreenshareStore();
initScreenshareAudioStore();
this.screensharePatcher = new ScreensharePatcher().patch();
this.screenshareAudioPatcher = new ScreenshareAudioPatcher().patch();
addSettingsPanelButton({
name: PluginInfo.PLUGIN_NAME,
icon: ScreenshareSettingsIcon,
tooltipText: "Screenshare Settings",
onClick: openScreenshareModal
});
}
stop(): void {
this.screensharePatcher?.unpatch();
this.screenshareAudioPatcher?.unpatch();
Emitter.removeAllListeners(PluginInfo.PLUGIN_NAME);
removeSettingsPanelButton(PluginInfo.PLUGIN_NAME);
}
};

View file

@ -0,0 +1,23 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2023 Vendicated and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Logger } from "@utils/Logger";
import { PluginInfo } from "../constants";
export const logger = new Logger(PluginInfo.PLUGIN_NAME);

View file

@ -0,0 +1,55 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2023 Vendicated and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { openModalLazy } from "@utils/modal";
import Plugin from "..";
import { ScreenshareSettingsModal } from "../components";
import { PluginInfo } from "../constants";
import { screenshareAudioStore, screenshareStore } from "../stores";
const onScreenshareModalDone = () => {
const { screenshareAudioPatcher, screensharePatcher } = Plugin;
if (screensharePatcher) {
screensharePatcher.forceUpdateTransportationOptions();
screensharePatcher.forceUpdateDesktopSourceOptions();
}
if (screenshareAudioPatcher)
screenshareAudioPatcher.forceUpdateTransportationOptions();
};
const onScreenshareAudioModalDone = () => {
const { screenshareAudioPatcher } = Plugin;
if (screenshareAudioPatcher)
screenshareAudioPatcher.forceUpdateTransportationOptions();
};
export const openScreenshareModal =
() => openModalLazy(async () => {
return props =>
<ScreenshareSettingsModal
onAudioDone={onScreenshareAudioModalDone}
onDone={onScreenshareModalDone}
screenshareStore={screenshareStore}
screenshareAudioStore={screenshareAudioStore}
author={PluginInfo.AUTHOR}
contributors={Object.values(PluginInfo.CONTRIBUTORS)}
{...props} />;
});

View file

@ -0,0 +1,20 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2023 Vendicated and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
export * from "./screenshare";
export * from "./screenshareAudio";

View file

@ -0,0 +1,99 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2023 Vendicated and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { UserStore } from "@webpack/common";
import { Emitter, MediaEngineStore, Patcher, types } from "../../philsPluginLibrary";
import { patchConnectionVideoSetDesktopSourceWithOptions, patchConnectionVideoTransportOptions } from "../../philsPluginLibrary/patches/video";
import { PluginInfo } from "../constants";
import { logger } from "../logger";
import { screenshareStore } from "../stores";
export class ScreensharePatcher extends Patcher {
private mediaEngineStore: types.MediaEngineStore;
private mediaEngine: types.MediaEngine;
public connection?: types.Connection;
public oldSetDesktopSourceWithOptions: (...args: any[]) => void;
public oldSetTransportOptions: (...args: any[]) => void;
public forceUpdateTransportationOptions: () => void;
public forceUpdateDesktopSourceOptions: () => void;
constructor() {
super();
this.mediaEngineStore = MediaEngineStore;
this.mediaEngine = this.mediaEngineStore.getMediaEngine();
this.forceUpdateTransportationOptions = () => void 0;
this.forceUpdateDesktopSourceOptions = () => void 0;
this.oldSetDesktopSourceWithOptions = () => void 0;
this.oldSetTransportOptions = () => void 0;
}
public patch(): this {
this.unpatch();
const { get } = screenshareStore;
const connectionEventFunction =
(connection: types.Connection) => {
if (!(connection.context === "stream" && connection.streamUserId === UserStore.getCurrentUser().id)) return;
this.connection = connection;
const {
oldSetDesktopSourceWithOptions,
oldSetTransportOptions,
forceUpdateDesktopSourceOptions,
forceUpdateTransportationOptions
} = {
...patchConnectionVideoTransportOptions(connection, get, logger),
...patchConnectionVideoSetDesktopSourceWithOptions(connection, get, logger)
};
this.oldSetDesktopSourceWithOptions = oldSetDesktopSourceWithOptions;
this.oldSetTransportOptions = oldSetTransportOptions;
this.forceUpdateDesktopSourceOptions = forceUpdateDesktopSourceOptions;
this.forceUpdateTransportationOptions = forceUpdateTransportationOptions;
Emitter.addListener(connection.emitter, "on", "connected", () => {
this.forceUpdateTransportationOptions();
this.forceUpdateDesktopSourceOptions();
});
Emitter.addListener(connection.emitter, "on", "destroy", () => {
this.forceUpdateTransportationOptions = () => void 0;
this.forceUpdateDesktopSourceOptions = () => void 0;
this.oldSetTransportOptions = () => void 0;
this.oldSetDesktopSourceWithOptions = () => void 0;
});
};
Emitter.addListener(
this.mediaEngine.emitter,
"on",
"connection",
connectionEventFunction,
PluginInfo.PLUGIN_NAME
);
return this;
}
public unpatch(): this {
return this._unpatch();
}
}

View file

@ -0,0 +1,85 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2023 Vendicated and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { UserStore } from "@webpack/common";
import { Emitter, MediaEngineStore, patchConnectionAudioTransportOptions, Patcher, types } from "../../philsPluginLibrary";
import { PluginInfo } from "../constants";
import { logger } from "../logger";
import { screenshareAudioStore } from "../stores/screenshareAudioStore";
export class ScreenshareAudioPatcher extends Patcher {
private mediaEngineStore: types.MediaEngineStore;
private mediaEngine: types.MediaEngine;
public connection?: types.Connection;
public oldSetTransportOptions: (...args: any[]) => void;
public forceUpdateTransportationOptions: () => void;
constructor() {
super();
this.mediaEngineStore = MediaEngineStore;
this.mediaEngine = this.mediaEngineStore.getMediaEngine();
this.forceUpdateTransportationOptions = () => void 0;
this.oldSetTransportOptions = () => void 0;
}
public patch(): this {
this.unpatch();
const { get } = screenshareAudioStore;
const connectionEventFunction =
(connection: types.Connection) => {
if (connection.context !== "stream" || connection.streamUserId !== UserStore.getCurrentUser().id) return;
this.connection = connection;
const {
forceUpdateTransportationOptions: forceUpdateTransportationOptionsAudio,
oldSetTransportOptions: oldSetTransportOptionsAudio
} = patchConnectionAudioTransportOptions(connection, get, logger);
this.forceUpdateTransportationOptions = forceUpdateTransportationOptionsAudio;
this.oldSetTransportOptions = oldSetTransportOptionsAudio;
Emitter.addListener(connection.emitter, "on", "connected", () => {
this.forceUpdateTransportationOptions();
});
Emitter.addListener(connection.emitter, "on", "destroy", () => {
this.forceUpdateTransportationOptions = () => void 0;
});
};
Emitter.addListener(
this.mediaEngine.emitter,
"on",
"connection",
connectionEventFunction,
PluginInfo.PLUGIN_NAME
);
return this;
}
public unpatch(): this {
return this._unpatch();
}
}

View file

@ -0,0 +1,19 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2023 Vendicated and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
export * from "./screenshareModal";

View file

@ -0,0 +1,98 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2023 Vendicated and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Flex } from "@components/Flex";
import { React } from "@webpack/common";
import { Settings } from "Vencord";
import { SettingsModalCard, SettingsModalCardItem } from "../../philsPluginLibrary";
import Plugin from "..";
import { AudioSourceSelect, OpenScreenshareSettingsButton } from "../components";
import { PluginInfo } from "../constants";
import { screenshareStore } from "../stores";
const ReplacedStreamSettings = () => {
const { use } = screenshareStore;
const { audioSourceEnabled, setAudioSourceEnabled } = use();
const cardProps = { style: { border: "1px solid var(--primary-800)" } };
return (
<div style={{ margin: "1em", display: "flex", flexDirection: "column", gap: "1em" }}>
<SettingsModalCard cardProps={cardProps} title="Stream Settings">
<SettingsModalCardItem>
<Flex flexDirection="column">
<OpenScreenshareSettingsButton title="Advanced Settings" />
</Flex>
</SettingsModalCardItem>
</SettingsModalCard>
<SettingsModalCard
cardProps={cardProps}
switchEnabled
switchProps={{
checked: audioSourceEnabled ?? false,
onChange: status => setAudioSourceEnabled(status)
}}
title="Audio Source">
<SettingsModalCardItem>
<AudioSourceSelect isDisabled={!audioSourceEnabled} />
</SettingsModalCardItem>
</SettingsModalCard>
</div>
);
};
export function replacedScreenshareModalSettingsContentType(oldType: (...args: any[]) => any, thisContext: any, functionArguments: any) {
const { hideDefaultSettings } = Settings.plugins[PluginInfo.PLUGIN_NAME];
const oldTypeResult = Reflect.apply(oldType, thisContext, functionArguments);
if (hideDefaultSettings)
oldTypeResult.props.children = oldTypeResult.props.children.filter(c => !c?.props?.selectedFPS);
oldTypeResult.props.children.push(<ReplacedStreamSettings />);
return oldTypeResult;
}
export function replacedScreenshareModalComponent(oldComponent: (...args: any[]) => any, thisContext: any, functionArguments: any) {
const oldComponentResult = Reflect.apply(oldComponent, thisContext, functionArguments);
const content = oldComponentResult.props.children.props.children[2].props.children[1].props.children[2].props.children.props.children;
const oldContentType = content.type;
content.type = function () {
return replacedScreenshareModalSettingsContentType(oldContentType, this, arguments);
};
const [submitBtn, cancelBtn] = oldComponentResult.props.children.props.children[2].props.children[2].props.children;
submitBtn.props.onClick = () => {
const { screensharePatcher, screenshareAudioPatcher } = Plugin;
if (screensharePatcher) {
screensharePatcher.forceUpdateTransportationOptions();
if (screensharePatcher.connection?.connectionState === "CONNECTED")
screensharePatcher.forceUpdateDesktopSourceOptions();
}
if (screenshareAudioPatcher)
screenshareAudioPatcher.forceUpdateTransportationOptions();
};
return oldComponentResult;
}

View file

@ -0,0 +1,20 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2023 Vendicated and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
export * from "./screenshareAudioStore";
export * from "./screenshareStore";

View file

@ -0,0 +1,41 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2023 Vendicated and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import {
defaultMicrophoneProfiles as defaultScreenshareAudioProfiles,
MicrophoneProfile as ScreenshareAudioProfile,
MicrophoneStore as ScreenshareAudioStore,
microphoneStoreDefault as screenshareAudioStoreDefault
} from "../../betterMicrophone.desktop/stores";
import { createPluginStore, ProfilableStore, profileable } from "../../philsPluginLibrary";
import { PluginInfo } from "../constants";
export let screenshareAudioStore: ProfilableStore<ScreenshareAudioStore, ScreenshareAudioProfile>;
export const initScreenshareAudioStore = () =>
screenshareAudioStore = createPluginStore(
PluginInfo.PLUGIN_NAME,
"ScreenshareAudioStore",
profileable(
screenshareAudioStoreDefault,
{ name: "" },
Object.values(defaultScreenshareAudioProfiles)
)
);
export { defaultScreenshareAudioProfiles, ScreenshareAudioProfile, ScreenshareAudioStore, screenshareAudioStoreDefault };

View file

@ -0,0 +1,122 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2023 Vendicated and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { createPluginStore, ProfilableInitializer, ProfilableStore, profileable, ProfileableProfile } from "../../philsPluginLibrary";
import { PluginInfo } from "../constants";
export interface ScreenshareProfile {
width?: number,
height?: number,
framerate?: number,
videoCodec?: string,
keyframeInterval?: number,
videoBitrate?: number;
videoBitrateEnabled?: boolean;
resolutionEnabled?: boolean,
framerateEnabled?: boolean,
videoCodecEnabled?: boolean;
keyframeIntervalEnabled?: boolean;
hdrEnabled?: boolean;
}
export interface ScreenshareStore {
audioSource?: string;
audioSourceEnabled?: boolean;
simpleMode?: boolean;
setWidth: (width?: number) => void;
setHeight: (height?: number) => void;
setFramerate: (framerate?: number) => void;
setVideoCodec: (codec?: string) => void;
setKeyframeInterval: (keyframeInterval?: number) => void;
setVideoBitrate: (bitrate?: number) => void;
setKeyframeIntervalEnabled: (enabled?: boolean) => void;
setResolutionEnabled: (enabled?: boolean) => void;
setFramerateEnabled: (enabled?: boolean) => void;
setVideoCodecEnabled: (enabled?: boolean) => void;
setVideoBitrateEnabled: (enabled?: boolean) => void;
setHdrEnabled: (enabled?: boolean) => void;
setAudioSource: (audioSource?: string) => void;
setAudioSourceEnabled: (enabled?: boolean) => void;
setSimpleMode: (enabled?: boolean) => void;
}
export const defaultScreenshareProfiles = {
low: {
name: "Low Quality",
width: 1280,
height: 720,
framerate: 60,
videoBitrate: 2500,
resolutionEnabled: true,
framerateEnabled: true,
videoBitrateEnabled: true,
},
medium: {
name: "Medium Quality",
width: 1920,
height: 1080,
framerate: 60,
videoBitrate: 5000,
resolutionEnabled: true,
framerateEnabled: true,
videoBitrateEnabled: true,
},
high: {
name: "High Quality",
width: 1920,
height: 1080,
framerate: 60,
videoBitrate: 10000,
resolutionEnabled: true,
framerateEnabled: true,
videoBitrateEnabled: true,
}
} as const satisfies Record<string, ScreenshareProfile & ProfileableProfile>;
export const screenshareStoreDefault: ProfilableInitializer<ScreenshareStore, ScreenshareProfile> = (set, get) => ({
setVideoBitrate: bitrate => get().currentProfile.videoBitrate = bitrate,
setVideoBitrateEnabled: enabled => get().currentProfile.videoBitrateEnabled = enabled,
setVideoCodec: codec => get().currentProfile.videoCodec = codec,
setVideoCodecEnabled: enabled => get().currentProfile.videoCodecEnabled = enabled,
setFramerate: framerate => get().currentProfile.framerate = framerate,
setFramerateEnabled: enabled => get().currentProfile.framerateEnabled = enabled,
setHeight: height => get().currentProfile.height = height,
setWidth: width => get().currentProfile.width = width,
setResolutionEnabled: enabled => get().currentProfile.resolutionEnabled = enabled,
setKeyframeInterval: keyframeInterval => get().currentProfile.keyframeInterval = keyframeInterval,
setKeyframeIntervalEnabled: enabled => get().currentProfile.keyframeIntervalEnabled = enabled,
setHdrEnabled: enabled => get().currentProfile.hdrEnabled = enabled,
setAudioSource: audioSource => get().audioSource = audioSource,
setAudioSourceEnabled: enabled => get().audioSourceEnabled = enabled,
setSimpleMode: enabled => get().simpleMode = enabled,
simpleMode: true
});
export let screenshareStore: ProfilableStore<ScreenshareStore, ScreenshareProfile>;
export const initScreenshareStore = () =>
screenshareStore = createPluginStore(
PluginInfo.PLUGIN_NAME,
"ScreenshareStore",
profileable(
screenshareStoreDefault,
{ name: "" },
Object.values(defaultScreenshareProfiles)
)
);

View file

@ -0,0 +1,57 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2023 Vendicated and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { PluginAuthor } from "@utils/types";
import { useEffect, UserUtils, useState } from "@webpack/common";
import { User } from "discord-types/general";
import React from "react";
import { createDummyUser, types, UserSummaryItem } from "../../philsPluginLibrary";
export interface AuthorUserSummaryItemProps extends Partial<React.ComponentProps<types.UserSummaryItem>> {
authors: PluginAuthor[];
}
export const AuthorUserSummaryItem = (props: AuthorUserSummaryItemProps) => {
const [users, setUsers] = useState<Partial<User>[]>([]);
useEffect(() => {
(async () => {
props.authors.forEach(author =>
UserUtils.getUser(`${author.id}`)
.then(user => setUsers(users => [...users, user]))
.catch(() => setUsers(users => [...users, createDummyUser({
username: author.name,
id: `${author.id}`,
bot: true,
})]))
);
})();
}, []);
return (
<UserSummaryItem
users={users as User[]}
guildId={undefined}
renderIcon={false}
showDefaultAvatarsForNullUsers
showUserPopout
{...props}
/>
);
};

View file

@ -0,0 +1,53 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2023 Vendicated and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Flex } from "@components/Flex";
import { Text } from "@webpack/common";
import React from "react";
import { Author, Contributor } from "../types";
import { openURL } from "../utils";
import { AuthorUserSummaryItem } from "./AuthorSummaryItem";
export interface ContributorAuthorSummaryProps {
author?: Author;
contributors?: Contributor[];
}
export const ContributorAuthorSummary = ({ author, contributors }: ContributorAuthorSummaryProps) => {
return (
<Flex style={{ gap: "0.7em" }}>
{author &&
<Flex style={{ justifyContent: "center", alignItems: "center", gap: "0.5em" }}>
<Text variant="text-sm/normal" style={{ color: "var(--text-muted)" }}>
Author: <a onClick={() => author.github && openURL(author.github)}>{`${author.name}`}</a>
</Text>
<AuthorUserSummaryItem authors={[author]} />
</Flex>
}
{(contributors && contributors.length > 0) &&
<Flex style={{ justifyContent: "center", alignItems: "center", gap: "0.5em" }}>
<Text variant="text-sm/normal" style={{ color: "var(--text-muted)" }}>
Contributors:
</Text>
<AuthorUserSummaryItem authors={contributors} />
</Flex>
}
</Flex>
);
};

View file

@ -0,0 +1,37 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2023 Vendicated and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Button } from "@webpack/common";
import React from "react";
import { IconTooltipButton } from ".";
export const CopyButton = (props: typeof Button["defaultProps"]) => {
return (
<IconTooltipButton
color={Button.Colors.PRIMARY}
tooltipText="Copy Profile"
icon={
<svg width="18" height="18" viewBox="0 0 24 24">
<path d="M7.024 3.75c0-.966.784-1.75 1.75-1.75H20.25c.966 0 1.75.784 1.75 1.75v11.498a1.75 1.75 0 0 1-1.75 1.75H8.774a1.75 1.75 0 0 1-1.75-1.75Zm1.75-.25a.25.25 0 0 0-.25.25v11.498c0 .139.112.25.25.25H20.25a.25.25 0 0 0 .25-.25V3.75a.25.25 0 0 0-.25-.25Z" fill="currentColor"></path>
<path d="M1.995 10.749a1.75 1.75 0 0 1 1.75-1.751H5.25a.75.75 0 1 1 0 1.5H3.745a.25.25 0 0 0-.25.25L3.5 20.25c0 .138.111.25.25.25h9.5a.25.25 0 0 0 .25-.25v-1.51a.75.75 0 1 1 1.5 0v1.51A1.75 1.75 0 0 1 13.25 22h-9.5A1.75 1.75 0 0 1 2 20.25l-.005-9.501Z" fill="currentColor"></path>
</svg>
}
{...props} />
);
};

View file

@ -0,0 +1,39 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2023 Vendicated and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Button } from "@webpack/common";
import * as types from "@webpack/types";
import React from "react";
import { IconTooltipButton } from ".";
export const DeleteButton = (props: types.Button["defaultProps"]) => {
return (
<IconTooltipButton
color={Button.Colors.RED}
tooltipText="Delete Profile"
icon={
<svg width="18" height="18" viewBox="0 0 24 24">
<path d="M16 1.75V3h5.25a.75.75 0 0 1 0 1.5H2.75a.75.75 0 0 1 0-1.5H8V1.75C8 .784 8.784 0 9.75 0h4.5C15.216 0 16 .784 16 1.75Zm-6.5 0V3h5V1.75a.25.25 0 0 0-.25-.25h-4.5a.25.25 0 0 0-.25.25ZM4.997 6.178a.75.75 0 1 0-1.493.144L4.916 20.92a1.75 1.75 0 0 0 1.742 1.58h10.684a1.75 1.75 0 0 0 1.742-1.581l1.413-14.597a.75.75 0 0 0-1.494-.144l-1.412 14.596a.25.25 0 0 1-.249.226H6.658a.25.25 0 0 1-.249-.226L4.997 6.178Z" fill="currentColor"></path>
<path d="M9.206 7.501a.75.75 0 0 1 .793.705l.5 8.5A.75.75 0 1 1 9 16.794l-.5-8.5a.75.75 0 0 1 .705-.793Zm6.293.793A.75.75 0 1 0 14 8.206l-.5 8.5a.75.75 0 0 0 1.498.088l.5-8.5Z" fill="currentColor"></path>
</svg>
}
{...props} />
);
};

View file

@ -0,0 +1,44 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2023 Vendicated and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Flex } from "@components/Flex";
import { Button, Tooltip } from "@webpack/common";
import React from "react";
export interface IconTooltipButtonProps {
tooltipText?: string;
icon?: JSX.Element;
}
export const IconTooltipButton = (props: typeof Button["defaultProps"] & IconTooltipButtonProps) => {
return (
<Tooltip text={props.tooltipText}>
{tooltipProps => <Button
size={Button.Sizes.ICON}
{...props as any}
style={{ aspectRatio: 1, maxHeight: "32px", boxSizing: "border-box", ...props.style }}
>
<Flex style={{ justifyContent: "center", alignItems: "center", width: 24, height: 24 }}>
{props.icon}
</Flex>
<span {...tooltipProps} style={{ position: "absolute", top: 0, right: 0, bottom: 0, left: 0 }} />
</Button>}
</Tooltip >
);
};

View file

@ -0,0 +1,37 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2023 Vendicated and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Button } from "@webpack/common";
import React from "react";
import { IconTooltipButton } from ".";
export const NewButton = (props: typeof Button["defaultProps"]) => {
return (
<IconTooltipButton
color={Button.Colors.PRIMARY}
tooltipText="New Profile"
icon={
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<line x1="12" y1="5" x2="12" y2="19"></line>
<line x1="5" y1="12" x2="19" y2="12"></line>
</svg>
}
{...props} />
);
};

View file

@ -0,0 +1,38 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2023 Vendicated and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Button } from "@webpack/common";
import React from "react";
import { IconTooltipButton } from ".";
export const SaveButton = (props: typeof Button["defaultProps"]) => {
return (
<IconTooltipButton
color={Button.Colors.GREEN}
tooltipText="Save Profile"
icon={
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" >
<path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"></path>
<polyline points="17 21 17 13 7 13 7 21"></polyline>
<polyline points="7 3 7 8 15 8"></polyline>
</svg>
}
{...props} />
);
};

View file

@ -0,0 +1,23 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2023 Vendicated and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
export * from "./CopyButton";
export * from "./DeleteButton";
export * from "./IconTooltipButton";
export * from "./NewButton";
export * from "./SaveButton";

View file

@ -0,0 +1,23 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2023 Vendicated and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
export * from "./AuthorSummaryItem";
export * from "./buttons";
export * from "./ContributorAuthorSummary";
export * from "./settingsModal";
export * from "./settingsPanel";

View file

@ -0,0 +1,77 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2023 Vendicated and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Flex } from "@components/Flex";
import { ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalRoot } from "@utils/modal";
import { Button, Text } from "@webpack/common";
import React from "react";
import { Author, Contributor } from "../../types";
import { ContributorAuthorSummary } from "../ContributorAuthorSummary";
export interface SettingsModalProps extends React.ComponentProps<typeof ModalRoot> {
title?: string;
onClose: () => void;
onDone?: () => void;
footerContent?: JSX.Element;
closeButtonName?: string;
author?: Author,
contributors?: Contributor[];
}
export const SettingsModal = (props: SettingsModalProps) => {
const doneButton =
<Button
size={Button.Sizes.SMALL}
color={Button.Colors.BRAND}
onClick={props.onDone}
>
{props.closeButtonName ?? "Done"}
</Button>;
return (
<ModalRoot {...props}>
<ModalHeader separator={false}>
{props.title && <Text variant="heading-lg/semibold" style={{ flexGrow: 1 }}>{props.title}</Text>}
<div style={{ marginLeft: "auto" }}>
<ModalCloseButton onClick={props.onClose} />
</div>
</ModalHeader>
<ModalContent style={{ marginBottom: "1em", display: "flex", flexDirection: "column", gap: "1em" }}>
{props.children}
</ModalContent>
<ModalFooter>
<Flex style={{ width: "100%" }}>
<div style={{ flex: 1, display: "flex" }}>
{(props.author || props.contributors && props.contributors.length > 0) &&
<Flex style={{ justifyContent: "flex-start", alignItems: "center", flex: 1 }}>
<ContributorAuthorSummary
author={props.author}
contributors={props.contributors} />
</Flex>
}
{props.footerContent}
</div>
<div style={{ marginLeft: "auto" }}>{doneButton}</div>
</Flex>
</ModalFooter>
</ModalRoot >
);
};

View file

@ -0,0 +1,83 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2023 Vendicated and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Switch } from "@components/Switch";
import { Card, Forms } from "@webpack/common";
import React from "react";
export interface SettingsModalItemProps extends Pick<React.ComponentProps<"div">,
| "children"> {
title?: string;
switchEnabled?: boolean;
switchProps?: React.ComponentProps<typeof Switch>;
flex?: number;
cardProps?: React.ComponentProps<typeof Card>;
}
export const SettingsModalCard = ({ children, title, switchProps, switchEnabled, flex, cardProps }: SettingsModalItemProps) => {
return (
<Card
{...cardProps}
style={{
padding: "1em",
boxSizing: "border-box",
display: "flex",
flexDirection: "column",
flex: flex ?? 1,
...(cardProps?.style ? cardProps.style : {})
}}>
{title && <Forms.FormTitle tag="h5" style={{ margin: 0 }}>{title}</Forms.FormTitle>}
<div style={{
display: "flex",
gap: "1em",
height: "100%",
justifyContent: "center",
alignItems: "center",
paddingTop: "0.6em",
}}>
{children &&
<div style={{
display: "flex",
alignItems: "flex-end",
gap: "1em",
flex: 1
}}>
{children}
</div>
}
{switchEnabled &&
<div style={{
display: "flex",
height: "100%",
flexDirection: "column",
justifyContent: "center",
alignItems: "center",
}}>
<Forms.FormTitle tag="h5">Status</Forms.FormTitle>
<Switch
checked={false}
onChange={() => void 0}
disabled={false}
{...switchProps}
/>
</div>
}
</div>
</Card >
);
};

View file

@ -0,0 +1,39 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2023 Vendicated and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Forms } from "@webpack/common";
import React from "react";
export interface SettingsModalCardItemProps extends Pick<React.ComponentProps<"div">,
| "children"> {
title?: string;
}
export const SettingsModalCardItem = ({ children, title }: SettingsModalCardItemProps) => {
return (
<div style={{
display: "flex",
flexDirection: "column",
gap: "0.4em",
width: "100%"
}}>
{title && <Forms.FormTitle tag="h5" style={{ margin: 0 }}>{title}</Forms.FormTitle>}
{children}
</div>
);
};

View file

@ -0,0 +1,31 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2023 Vendicated and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import React from "react";
export interface SettingsModalRowProps extends Pick<React.ComponentProps<"div">,
| "children"
| "style"> {
gap?: string;
}
export const SettingsModalCardRow = ({ children, style, gap }: SettingsModalRowProps) => {
return (
<div style={{ display: "flex", gap: gap ?? "1em", ...style }}>{children}</div>
);
};

View file

@ -0,0 +1,113 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2023 Vendicated and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Flex } from "@components/Flex";
import { Select, TextInput, useEffect, useState } from "@webpack/common";
import { PluginSettings, ProfilableStore } from "../../../philsPluginLibrary";
import { CopyButton, DeleteButton, NewButton, SaveButton } from "../buttons";
import { SettingsModalCard } from "./SettingsModalCard";
import { SettingsModalCardItem } from "./SettingsModalCardItem";
export interface SettingsModalProfilesCardProps<T extends PluginSettings = {}> extends React.ComponentProps<typeof SettingsModalCard> {
profileableStore: ProfilableStore<T, any>;
onSaveStateChanged: (isSaving: boolean) => void;
}
export const SettingsModalProfilesCard = <T extends PluginSettings = {},>(props: SettingsModalProfilesCardProps<T>) => {
const { profileableStore: { use } } = props;
const {
currentProfile,
setCurrentProfile,
deleteProfile,
getCurrentProfile,
getDefaultProfiles,
getProfile,
getProfiles,
saveProfile,
isCurrentProfileADefaultProfile
} = use();
const { name } = currentProfile;
const [isSaving, setIsSaving] = useState(false);
const [profileNameInput, setProfileNameInput] = useState<string>("");
useEffect(() => {
props.onSaveStateChanged(isSaving);
}, [isSaving]);
const onSaveProfile = () => {
if (!isSaving) {
setIsSaving(true);
} else {
if (profileNameInput.length && !getDefaultProfiles().some(value => value.name === profileNameInput)) {
saveProfile({ ...getCurrentProfile(), name: profileNameInput });
setCurrentProfile(getProfile(profileNameInput) || { name: "" });
setIsSaving(false);
}
}
};
const onCopyProfile = () => {
setCurrentProfile({ ...getCurrentProfile(), name: "" });
};
const onNewProfile = () => {
setCurrentProfile({ name: "" });
};
const onDeleteProfile = () => {
deleteProfile(currentProfile);
};
return (
<SettingsModalCard
title="Profile"
{...props}>
<SettingsModalCardItem>
<Flex style={{ alignItems: "center" }}>
<div style={{ flex: 1 }}>
{isSaving
? <TextInput
style={{ width: "100%" }}
placeholder="Insert name"
value={profileNameInput}
onChange={setProfileNameInput} />
: <Select
isSelected={value => name === value}
options={getProfiles(true).map(profile => ({
label: profile.name,
value: profile.name
}))}
select={value => setCurrentProfile(getProfile(value) || { name: "" })}
serialize={() => ""} />}
</div>
<Flex style={{ gap: "0.8em" }}>
<SaveButton onClick={onSaveProfile} />
<NewButton onClick={onNewProfile} disabled={isSaving} />
<CopyButton onClick={onCopyProfile} disabled={isSaving} />
<DeleteButton onClick={onDeleteProfile} disabled={isSaving || isCurrentProfileADefaultProfile() || !currentProfile.name.length} />
</Flex>
</Flex>
</SettingsModalCardItem>
</SettingsModalCard>
);
};

View file

@ -0,0 +1,23 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2023 Vendicated and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
export * from "./SettingsModal";
export * from "./SettingsModalCard";
export * from "./SettingsModalCardItem";
export * from "./SettingsModalCardRow";
export * from "./SettingsModalProfilesCard";

View file

@ -0,0 +1,35 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2023 Vendicated and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { React } from "@webpack/common";
import { panelClasses } from "../../discordModules";
export interface SettingsPanelProps {
children: React.ComponentProps<"div">["children"];
}
export const SettingsPanel = ({ children }: SettingsPanelProps) => {
return (
<div
className={panelClasses.container}>
{children}
</div>
);
};

View file

@ -0,0 +1,40 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2023 Vendicated and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { classes } from "@utils/misc";
import { Button } from "@webpack/common";
import React from "react";
import { panelClasses } from "../../../philsPluginLibrary";
export type IconComponent = <T extends { className: string; }>(props: T) => JSX.Element;
export interface SettingsPanelButtonProps extends Partial<React.ComponentProps<typeof Button>> {
icon?: IconComponent;
}
export const SettingsPanelButton = (props: SettingsPanelButtonProps) => {
return (
<Button
size={Button.Sizes.SMALL}
className={classes(panelClasses.button, panelClasses.buttonColor)}
innerClassName={classes(panelClasses.buttonContents)}
wrapperClassName={classes(panelClasses.button)}
children={props.icon && <props.icon className={classes(panelClasses.buttonIcon)} />}
{...props} />
);
};

View file

@ -0,0 +1,37 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2023 Vendicated and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { classes } from "@utils/misc";
import React from "react";
import { panelClasses } from "../../discordModules";
export interface SettingsPanelRowProps {
children: React.ComponentProps<"div">["children"];
}
export const SettingsPanelRow = ({ children }: SettingsPanelRowProps) => {
return (
<div
className={classes(panelClasses.actionButtons)}
style={{ padding: 0 }}
>
{children}
</div>
);
};

View file

@ -0,0 +1,34 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2023 Vendicated and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Tooltip } from "@webpack/common";
import React from "react";
import { SettingsPanelButton, SettingsPanelButtonProps } from "./SettingsPanelButton";
export interface SettingsPanelTooltipButtonProps extends SettingsPanelButtonProps {
tooltipProps: Omit<React.ComponentProps<typeof Tooltip>, "children">;
}
export const SettingsPanelTooltipButton = (props: SettingsPanelTooltipButtonProps) => {
return (
<Tooltip {...props.tooltipProps}>
{tooltipProps => <SettingsPanelButton {...tooltipProps} {...props} />}
</Tooltip>
);
};

View file

@ -0,0 +1,22 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2023 Vendicated and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
export * from "./SettingsPanel";
export * from "./SettingsPanelButton";
export * from "./SettingsPanelRow";
export * from "./SettingsPanelTooltipButton";

View file

@ -0,0 +1,30 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2023 Vendicated and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Devs } from "@utils/constants";
import * as types from "../types/constants";
export const PluginInfo: types.PluginInfo = {
PLUGIN_NAME: "PhilsPluginLibrary",
DESCRIPTION: "A library for phil's plugins",
AUTHOR: {
...Devs.philhk,
github: "https://github.com/philhk"
},
} as const;

View file

@ -0,0 +1,19 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2023 Vendicated and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
export * from "./constants";

View file

@ -0,0 +1,25 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2023 Vendicated and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { findByPropsLazy } from "@webpack";
import * as types from "../types";
export const panelClasses: types.PanelClasses = findByPropsLazy("button", "buttonContents", "buttonColor");
// waitFor(filters.byProps("button", "buttonContents", "buttonColor"), result => panelClasses = result);

View file

@ -0,0 +1,24 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2023 Vendicated and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { LazyComponent } from "@utils/react";
import { findByCode } from "@webpack";
import { types } from "../";
export const UserSummaryItem = LazyComponent<React.ComponentProps<types.UserSummaryItem>>(() => findByCode("defaultRenderUser", "showDefaultAvatarsForNullUsers"));

View file

@ -0,0 +1,22 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2023 Vendicated and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
export * from "./classes";
export * from "./components";
export * from "./modules";
export * from "./stores";

View file

@ -0,0 +1,25 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2023 Vendicated and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { filters, waitFor } from "@webpack";
import * as types from "../types";
export let utils: types.Utils;
waitFor(filters.byProps("getPidFromDesktopSource"), result => utils = result);

View file

@ -0,0 +1,25 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2023 Vendicated and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { waitForStore } from "webpack/common/internal";
import * as types from "../types";
export let MediaEngineStore: types.MediaEngineStore;
waitForStore("MediaEngineStore", store => MediaEngineStore = store);

View file

@ -0,0 +1,86 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2023 Vendicated and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import EventEmitter from "events";
import TypedEmitter from "typed-emitter";
export type TypedEmitterEvents<J extends TypedEmitter<any>> = J extends TypedEmitter<
infer N
>
? N
: never;
export interface EmitterEvent {
emitter: TypedEmitter<any> | EventEmitter;
event: any;
fn: (...args: any[]) => any;
plugin?: string;
}
export class Emitter {
private static events: EmitterEvent[] = [];
public static addListener<
T extends TypedEmitter<any>,
U extends keyof TypedEmitterEvents<T>,
V extends TypedEmitterEvents<T>[U]
>(
emitter: T,
type: keyof Pick<EventEmitter, "on" | "once">,
event: U,
fn: V,
plugin?: string
): () => void;
public static addListener(
emitter: EventEmitter,
type: keyof Pick<EventEmitter, "on" | "once">,
event: string,
fn: (...args: any[]) => void,
plugin?: string
): () => void {
emitter[type](event, fn);
const emitterEvenet: EmitterEvent = {
emitter,
event,
fn,
plugin: plugin
};
this.events.push(emitterEvenet);
return () => this.removeListener(emitterEvenet);
}
public static removeListener(emitterEvent: EmitterEvent) {
emitterEvent.emitter.removeListener(emitterEvent.event, emitterEvent.fn);
this.events = this.events.filter(
emitterEvent_ => emitterEvent_ !== emitterEvent
);
}
public static removeAllListeners(plugin?: string) {
if (!plugin) {
this.events.forEach(emitterEvent =>
this.removeListener(emitterEvent)
);
} else
this.events.forEach(emitterEvent =>
plugin === emitterEvent.plugin && this.removeListener(emitterEvent)
);
}
}

View file

@ -0,0 +1,19 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2023 Vendicated and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
export * from "./emitter";

View file

@ -0,0 +1,142 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2023 Vendicated and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
export const ScreenshareSettingsIcon =
(props: React.ComponentProps<"svg">) =>
<svg
xmlns="http://www.w3.org/2000/svg"
width="1000"
height="1000"
viewBox="0 0 1000 1000"
{...props}
>
<defs>
<mask id="screenshareSettingsIconMask">
<path
fill="currentColor"
fillRule="evenodd"
d="M2 4.5c0-1.103.897-2 2-2h16c1.103 0 2 .897 2 2v11c0 1.104-.897 2-2 2h-7v2h4v2H7v-2h4v-2H4c-1.103 0-2-.896-2-2v-11zm11.2 9.838V11.6c-3.336 0-5.532 1.063-7.2 3.4.672-3.338 2.532-6.662 7.2-7.338V5L18 9.662l-4.8 4.675z"
transform="matrix(43.2813 0 0 43.3063 567.187 588.59) translate(-12 -12)"
vectorEffect="non-scaling-stroke"
></path>
<path
fill="#000"
strokeWidth="0"
// Circle
d="M132.5 67.5c0 35.899-29.101 65-65 65-35.898 0-65-29.101-65-65 0-35.898 29.102-65 65-65 35.899 0 65 29.102 65 65z"
transform="translate(229.14 230.807) scale(4.9157) translate(-67.5 -67.5)"
vectorEffect="non-scaling-stroke"
></path>
</mask>
</defs>
<rect width="100%" height="100%" fill="#fff" mask="url(#screenshareSettingsIconMask)"></rect>
<path
fill="currentColor"
fillRule="evenodd"
strokeWidth="0"
// Settings Icon
d="M19.738 10H22v4h-2.261a7.952 7.952 0 01-1.174 2.564L20 18l-2 2-1.435-1.436A7.946 7.946 0 0114 19.738V22h-4v-2.262a7.94 7.94 0 01-2.564-1.174L6 20l-2-2 1.436-1.436A7.911 7.911 0 014.262 14H2v-4h2.262a7.9 7.9 0 011.174-2.564L4 6l2-2 1.436 1.436A7.9 7.9 0 0110 4.262V2h4v2.261a7.967 7.967 0 012.565 1.174L18 3.999l2 2-1.436 1.437A7.93 7.93 0 0119.738 10zM12 16a4 4 0 100-8 4 4 0 000 8z"
transform="translate(229.812 230.81) scale(23.0217) translate(-12 -12)"
vectorEffect="non-scaling-stroke"
></path>
</svg>;
export const CameraSettingsIcon =
(props: React.ComponentProps<"svg">) =>
<svg
xmlns="http://www.w3.org/2000/svg"
width="1000"
height="1000"
viewBox="0 0 1000 1000"
{...props}
>
<defs>
<mask id="m">
<path
fill="currentColor"
fillRule="evenodd"
d="M21.526 8.149a1 1 0 00-.973-.044L18 9.382V7c0-1.103-.897-2-2-2H4c-1.103 0-2 .897-2 2v10c0 1.104.897 2 2 2h12c1.103 0 2-.896 2-2v-2.382l2.553 1.276a.992.992 0 00.973-.043c.294-.183.474-.504.474-.851V9c0-.347-.18-.668-.474-.851z"
transform="translate(586.527 617.666) scale(41.3472) translate(-12 -12)"
vectorEffect="non-scaling-stroke"
></path>
<path
fill="#000"
strokeWidth="0"
// Circle
d="M132.5 67.5c0 35.899-29.101 65-65 65-35.898 0-65-29.101-65-65 0-35.898 29.102-65 65-65 35.899 0 65 29.102 65 65z"
transform="translate(229.14 230.807) scale(4.9157) translate(-67.5 -67.5)"
vectorEffect="non-scaling-stroke"
></path>
</mask>
</defs>
<rect width="100%" height="100%" fill="#fff" mask="url(#m)"></rect>
<path
fill="currentColor"
fillRule="evenodd"
strokeWidth="0"
// Settings Icon
d="M19.738 10H22v4h-2.261a7.952 7.952 0 01-1.174 2.564L20 18l-2 2-1.435-1.436A7.946 7.946 0 0114 19.738V22h-4v-2.262a7.94 7.94 0 01-2.564-1.174L6 20l-2-2 1.436-1.436A7.911 7.911 0 014.262 14H2v-4h2.262a7.9 7.9 0 011.174-2.564L4 6l2-2 1.436 1.436A7.9 7.9 0 0110 4.262V2h4v2.261a7.967 7.967 0 012.565 1.174L18 3.999l2 2-1.436 1.437A7.93 7.93 0 0119.738 10zM12 16a4 4 0 100-8 4 4 0 000 8z"
transform="translate(229.812 230.81) scale(23.0217) translate(-12 -12)"
vectorEffect="non-scaling-stroke"
></path>
</svg>;
export const MicrophoneSettingsIcon =
(props: React.ComponentProps<"svg">) =>
<svg
xmlns="http://www.w3.org/2000/svg"
width="1000"
height="1000"
viewBox="0 0 1000 1000"
{...props}
>
<defs>
<mask id="microphoneSettingsIcon">
<path
fill="currentColor"
fillRule="evenodd"
d="M14.99 11c0 1.66-1.33 3-2.99 3-1.66 0-3-1.34-3-3V5c0-1.66 1.34-3 3-3s3 1.34 3 3l-.01 6zM12 16.1c2.76 0 5.3-2.1 5.3-5.1H19c0 3.42-2.72 6.24-6 6.72V21h-2v-3.28c-3.28-.49-6-3.31-6-6.72h1.7c0 3 2.54 5.1 5.3 5.1zM12 4c-.8 0-1 .667-1 1v6c0 .333.2 1 1 1s1-.667 1-1V5c0-.333-.2-1-1-1z"
transform="translate(689.616 574.556) scale(48.6222) translate(0 -.417) scale(.8333) translate(-12 -11.5)"
></path>
<path
fill="currentColor"
fillRule="evenodd"
d="M14.99 11c0 1.66-1.33 3-2.99 3-1.66 0-3-1.34-3-3V5c0-1.66 1.34-3 3-3s3 1.34 3 3l-.01 6zM12 16.1c2.76 0 5.3-2.1 5.3-5.1H19c0 3.42-2.72 6.24-6 6.72V22h-2v-4.28c-3.28-.49-6-3.31-6-6.72h1.7c0 3 2.54 5.1 5.3 5.1z"
transform="translate(689.616 574.556) scale(48.6222) scale(.8333) translate(-12 -12)"
></path>
<path
fill="#000"
strokeWidth="0"
// Circle
d="M132.5 67.5c0 35.899-29.101 65-65 65-35.898 0-65-29.101-65-65 0-35.898 29.102-65 65-65 35.899 0 65 29.102 65 65z"
transform="translate(229.14 230.807) scale(4.9157) translate(-67.5 -67.5)"
vectorEffect="non-scaling-stroke"
></path>
</mask>
</defs>
<rect width="100%" height="100%" fill="#fff" mask="url(#microphoneSettingsIcon)"></rect>
<path
fill="currentColor"
fillRule="evenodd"
strokeWidth="0"
// Settings Icon
d="M19.738 10H22v4h-2.261a7.952 7.952 0 01-1.174 2.564L20 18l-2 2-1.435-1.436A7.946 7.946 0 0114 19.738V22h-4v-2.262a7.94 7.94 0 01-2.564-1.174L6 20l-2-2 1.436-1.436A7.911 7.911 0 014.262 14H2v-4h2.262a7.9 7.9 0 011.174-2.564L4 6l2-2 1.436 1.436A7.9 7.9 0 0110 4.262V2h4v2.261a7.967 7.967 0 012.565 1.174L18 3.999l2 2-1.436 1.437A7.93 7.93 0 0119.738 10zM12 16a4 4 0 100-8 4 4 0 000 8z"
transform="translate(229.812 230.81) scale(23.0217) translate(-12 -12)"
vectorEffect="non-scaling-stroke"
></path>
</svg>;

View file

@ -0,0 +1,55 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2023 Vendicated and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Patch, PluginAuthor, PluginDef } from "@utils/types";
import { PluginInfo } from "./constants";
import { replacedUserPanelComponent } from "./patches";
export default new class Plugin implements PluginDef {
readonly name: string;
readonly description: string;
readonly authors: PluginAuthor[];
readonly patches: Omit<Patch, "plugin">[];
readonly replacedUserPanelComponent: typeof replacedUserPanelComponent;
constructor() {
this.name = PluginInfo.PLUGIN_NAME;
this.description = PluginInfo.DESCRIPTION;
this.authors = PluginInfo.AUTHORS as PluginAuthor[];
this.patches = [{
find: "Messages.ACCOUNT_A11Y_LABEL",
replacement: {
match: /(?<=function)( .{0,8}(?={).)(.{0,1000}isFullscreenInContext\(\).+?\)]}\))(})/,
replace: "$1return $self.replacedUserPanelComponent(function(){$2}, this, arguments)$3"
}
}];
this.replacedUserPanelComponent = replacedUserPanelComponent;
}
};
export * from "./components";
export * from "./discordModules";
export * from "./emitter";
export * from "./icons";
export * from "./patchers";
export * from "./patches";
export * from "./store";
export * as types from "./types";
export * from "./utils";

View file

@ -0,0 +1,19 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2023 Vendicated and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
export * from "./patcher";

View file

@ -0,0 +1,29 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2023 Vendicated and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
export abstract class Patcher {
protected unpatchFunctions: (() => any)[] = [];
public abstract patch(): this;
public abstract unpatch(): this;
protected _unpatch(): this {
this.unpatchFunctions.forEach(fn => fn());
this.unpatchFunctions = [];
return this;
}
}

View file

@ -0,0 +1,88 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2023 Vendicated and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Logger } from "@utils/Logger";
import { lodash } from "@webpack/common";
import { MicrophoneProfile, MicrophoneStore } from "../../betterMicrophone.desktop/stores";
import { ProfilableStore, replaceObjectValuesIfExist, types } from "../../philsPluginLibrary";
export function getDefaultAudioTransportationOptions(connection: types.Connection) {
return {
audioEncoder: {
...connection.getCodecOptions("opus").audioEncoder,
},
encodingVoiceBitRate: 64000
};
}
export function getReplaceableAudioTransportationOptions(connection: types.Connection, get: ProfilableStore<MicrophoneStore, MicrophoneProfile>["get"]) {
const { currentProfile } = get();
const {
channels,
channelsEnabled,
freq,
freqEnabled,
pacsize,
pacsizeEnabled,
rate,
rateEnabled,
voiceBitrate,
voiceBitrateEnabled
} = currentProfile;
return {
...(voiceBitrateEnabled && voiceBitrate
? {
encodingVoiceBitRate: voiceBitrate * 1000
}
: {}
),
audioEncoder: {
...connection.getCodecOptions("opus").audioEncoder,
...(rateEnabled && rate ? { rate } : {}),
...(pacsizeEnabled && pacsize ? { pacsize } : {}),
...(freqEnabled && freq ? { freq } : {}),
...(channelsEnabled && channels ? { channels } : {})
}
};
}
export function patchConnectionAudioTransportOptions(
connection: types.Connection,
get: ProfilableStore<MicrophoneStore, MicrophoneProfile>["get"],
logger?: Logger
) {
const oldSetTransportOptions = connection.conn.setTransportOptions;
connection.conn.setTransportOptions = function (this: any, options: Record<string, any>) {
replaceObjectValuesIfExist(options, getReplaceableAudioTransportationOptions(connection, get));
return Reflect.apply(oldSetTransportOptions, this, [options]);
};
const forceUpdateTransportationOptions = () => {
const transportOptions = lodash.merge({ ...getDefaultAudioTransportationOptions(connection) }, getReplaceableAudioTransportationOptions(connection, get));
logger?.info("Overridden Transport Options", transportOptions);
oldSetTransportOptions(transportOptions);
};
return { oldSetTransportOptions, forceUpdateTransportationOptions };
}

View file

@ -0,0 +1,21 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2023 Vendicated and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
export * from "./audio";
export * from "./userPanel";
export * from "./video";

View file

@ -0,0 +1,107 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2023 Vendicated and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { React } from "@webpack/common";
import { SettingsPanel } from "../components";
import { IconComponent, SettingsPanelButton } from "../components/settingsPanel/SettingsPanelButton";
import { SettingsPanelRow } from "../components/settingsPanel/SettingsPanelRow";
import { SettingsPanelTooltipButton } from "../components/settingsPanel/SettingsPanelTooltipButton";
export interface PanelButton {
name: string,
tooltipText?: string,
icon?: IconComponent;
onClick?: () => void;
}
const settingsPanelButtonsSubscriptions = new Set<React.DispatchWithoutAction>();
export const settingsPanelButtons: PanelButton[] = new Proxy<PanelButton[]>([], {
set: (target, p, newValue) => {
target[p] = newValue;
settingsPanelButtonsSubscriptions.forEach(fn => fn());
return true;
},
});
export const useButtons = () => {
const [, forceUpdate] = React.useReducer(() => ({}), {});
React.useEffect(() => {
settingsPanelButtonsSubscriptions.add(forceUpdate);
return () => void settingsPanelButtonsSubscriptions.delete(() => forceUpdate);
}, []);
return settingsPanelButtons;
};
export const ButtonsSettingsPanel = () => {
const rawPanelButtons = useButtons();
const convertRawPanelButtons = (buttons: PanelButton[]) => {
const settingsPanelButtonsClone = [...buttons].sort();
const groupedButtons: JSX.Element[][] = [];
while (settingsPanelButtonsClone.length) {
const splicedButtons =
settingsPanelButtonsClone
.splice(0, 3)
.map(({ icon, tooltipText, onClick }) =>
tooltipText
? <SettingsPanelTooltipButton tooltipProps={{ text: tooltipText }} icon={icon} onClick={onClick} />
: <SettingsPanelButton icon={icon} onClick={onClick} />
);
groupedButtons.push(splicedButtons);
}
return groupedButtons;
};
return rawPanelButtons.length > 0
? <SettingsPanel>
{...convertRawPanelButtons(rawPanelButtons).map(value => <SettingsPanelRow children={value} />)}
</SettingsPanel>
: <>
</>;
};
export function replacedUserPanelComponent(oldComponent: (...args: any[]) => any, thisContext: any, functionArguments: any) {
const componentResult: JSX.Element = Reflect.apply(oldComponent, thisContext, functionArguments);
if (!componentResult?.props) return componentResult;
const { children } = componentResult.props;
const userPanel = children.at(-2);
const userPanelChildren = userPanel.props.children;
userPanelChildren.splice(userPanelChildren.length - 1, 0,
<ButtonsSettingsPanel />
);
return componentResult;
}
export function addSettingsPanelButton(settings: PanelButton) {
settingsPanelButtons.push(settings);
}
export function removeSettingsPanelButton(name: string) {
settingsPanelButtons.splice(0, settingsPanelButtons.length, ...settingsPanelButtons.filter(value => value.name !== name));
}

View file

@ -0,0 +1,253 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2023 Vendicated and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Logger } from "@utils/Logger";
import { lodash } from "@webpack/common";
import { ScreenshareProfile, ScreenshareStore } from "../../betterScreenshare.desktop/stores";
import { ProfilableStore, replaceObjectValuesIfExist, types, utils } from "../../philsPluginLibrary";
export function getDefaultVideoTransportationOptions(connection: types.Connection) {
return {
...connection.videoQualityManager.applyQualityConstraints({}).constraints,
videoEncoder: {
...connection.getCodecOptions("", "H264", "stream").videoEncoder
},
streamParameters: connection.videoStreamParameters[0],
keyframeInterval: 0,
};
}
export function getDefaultVideoDesktopSourceOptions(connection: types.Connection) {
const [type, sourceId] = connection.goLiveSourceIdentifier?.split(":") ?? ["screen", 0];
return {
hdrCaptureMode: "never",
allowScreenCaptureKit: true,
useQuartzCapturer: true,
useGraphicsCapture: true,
useVideoHook: true,
sourceId: sourceId,
type: type
};
}
export function getStreamParameters(connection: types.Connection, get: ProfilableStore<ScreenshareStore, ScreenshareProfile>["get"]) {
const { currentProfile } = get();
const {
framerate,
framerateEnabled,
height,
resolutionEnabled,
videoBitrate,
videoBitrateEnabled,
width,
} = currentProfile;
const { bitrateMax, capture } = connection.applyQualityConstraints({}).quality;
return {
...connection.videoStreamParameters[0],
...(videoBitrateEnabled && videoBitrate
? {
maxBitrate: videoBitrate * 1000,
}
: {
maxBitrate: bitrateMax
}
),
...((resolutionEnabled && width && height)
? {
maxResolution: {
height: height,
width: width,
type: "fixed"
},
maxPixelCount: width * height
}
: {
maxResolution: !capture.height || !capture.width ? {
height: capture.height,
width: capture.width,
type: "source"
} : {
height: capture.height,
width: capture.width,
type: "fixed"
}
}
),
...(framerateEnabled && framerate
? {
maxFrameRate: framerate,
}
: {
maxFrameRate: capture.framerate
}
),
active: true,
};
}
export function getReplaceableVideoTransportationOptions(connection: types.Connection, get: ProfilableStore<ScreenshareStore, ScreenshareProfile>["get"]) {
const { currentProfile, audioSource, audioSourceEnabled } = get();
const {
framerate,
framerateEnabled,
height,
keyframeInterval,
keyframeIntervalEnabled,
resolutionEnabled,
videoBitrate,
videoBitrateEnabled,
videoCodec,
videoCodecEnabled,
width,
} = currentProfile;
return {
...(videoBitrateEnabled && videoBitrate
? {
encodingVideoBitRate: videoBitrate * 1000,
encodingVideoMinBitRate: videoBitrate * 1000,
encodingVideoMaxBitRate: videoBitrate * 1000,
callBitRate: videoBitrate * 1000,
callMinBitRate: videoBitrate * 1000,
callMaxBitRate: videoBitrate * 1000
}
: {}
),
...((resolutionEnabled && width && height)
? {
encodingVideoHeight: height,
encodingVideoWidth: width,
remoteSinkWantsPixelCount: height * width
}
: {}
),
...(framerateEnabled && framerate
? {
encodingVideoFrameRate: framerate,
remoteSinkWantsMaxFramerate: framerate
}
: {}
),
...(keyframeIntervalEnabled && keyframeInterval
? {
keyframeInterval: keyframeInterval
}
: {}
),
...(videoCodecEnabled && videoCodec
? {
videoEncoder: connection.getCodecOptions("", videoCodec, "stream").videoEncoder
}
: {}
),
...(audioSourceEnabled && audioSource && utils.getPidFromDesktopSource(audioSource)
? {
soundsharePid: utils.getPidFromDesktopSource(audioSource),
soundshareEventDriven: true,
soundshareLoopback: true
}
: {}
),
streamParameters: getStreamParameters(connection, get)
};
}
export function getReplaceableVideoDesktopSourceOptions(get: ProfilableStore<ScreenshareStore, ScreenshareProfile>["get"]) {
const { currentProfile } = get();
const {
hdrEnabled,
} = currentProfile;
return {
...(hdrEnabled
? {
hdrCaptureMode: "always"
}
: {}
),
};
}
export function patchConnectionVideoSetDesktopSourceWithOptions(
connection: types.Connection,
get: ProfilableStore<ScreenshareStore, ScreenshareProfile>["get"],
logger?: Logger
) {
const oldSetDesktopSourceWithOptions = connection.conn.setDesktopSourceWithOptions;
connection.conn.setDesktopSourceWithOptions = function (this: any, options: Record<string, any>) {
const replaceableDesktopSourceOptions = getReplaceableVideoDesktopSourceOptions(get);
replaceObjectValuesIfExist(options, replaceableDesktopSourceOptions);
logger?.info("Overridden Desktop Source Options", options);
return Reflect.apply(oldSetDesktopSourceWithOptions, this, [options]);
};
const forceUpdateDesktopSourceOptions = () => {
const desktopSourceOptions = lodash.merge({ ...getDefaultVideoDesktopSourceOptions(connection) }, getReplaceableVideoDesktopSourceOptions(get));
logger?.info("Force Updated Desktop Source Options", desktopSourceOptions);
oldSetDesktopSourceWithOptions(desktopSourceOptions);
};
return {
oldSetDesktopSourceWithOptions,
forceUpdateDesktopSourceOptions
};
}
export function patchConnectionVideoTransportOptions(
connection: types.Connection,
get: ProfilableStore<ScreenshareStore, ScreenshareProfile>["get"],
logger?: Logger
) {
const oldSetTransportOptions = connection.conn.setTransportOptions;
connection.conn.setTransportOptions = function (this: any, options: Record<string, any>) {
const replaceableTransportOptions = getReplaceableVideoTransportationOptions(connection, get);
if (options.streamParameters)
connection.videoStreamParameters = Array.isArray(options.streamParameters) ? options.streamParameters : [options.streamParameters];
replaceObjectValuesIfExist(options, replaceableTransportOptions);
logger?.info("Overridden Transport Options", options);
return Reflect.apply(oldSetTransportOptions, this, [options]);
};
const forceUpdateTransportationOptions = () => {
const transportOptions = lodash.merge({ ...getDefaultVideoTransportationOptions(connection) }, getReplaceableVideoTransportationOptions(connection, get));
logger?.info("Force Updated Transport Options", transportOptions);
oldSetTransportOptions(transportOptions);
};
return {
oldSetTransportOptions,
forceUpdateTransportationOptions,
};
}

View file

@ -0,0 +1,20 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2023 Vendicated and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
export * from "./profileable";
export * from "./store";

View file

@ -0,0 +1,85 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2023 Vendicated and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { PluginInitializer, PluginSettings, PluginStore } from "./store";
export interface ProfileableProfile {
name: string;
}
export interface ProfileableSettings<T extends PluginSettings = {}, B extends ProfileableProfile & T = T & ProfileableProfile> {
currentProfile: B;
profiles: B[];
setCurrentProfile: (f: ((currentProfile: B) => B | undefined) | B | undefined) => void;
getCurrentProfile: () => B;
duplicateProfile: (profile: string | B, name: string) => void;
deleteProfile: (profile: string | B) => void;
saveProfile: (profile: B) => void;
getProfile: (profile: string) => B | undefined;
getProfiles: (defaultProfiles: boolean) => B[],
isCurrentProfileADefaultProfile: () => boolean;
getDefaultProfiles: () => B[];
}
export type ProfilableStore<
T extends PluginSettings = {},
S extends PluginSettings = {}
> = PluginStore<T & ProfileableSettings<S>>;
export type ProfilableMiddleware<
T extends PluginSettings = {},
S extends PluginSettings = {},
B = T & ProfileableSettings<S>
> = PluginInitializer<T & ProfileableSettings<S>, B>;
export type ProfilableInitializer<
T extends PluginSettings = {},
S extends PluginSettings = {}
> = ProfilableMiddleware<T, S, T & Partial<ProfileableSettings<S>>>;
export function profileable<
T extends PluginSettings = {},
S extends PluginSettings = {}
>(f: ProfilableInitializer<T, S>, defaultProfile: ProfileableProfile & S, defaultProfiles: (ProfileableProfile & S)[] = []): ProfilableMiddleware<T, S> {
return (set, get) => ({
currentProfile: defaultProfile,
profiles: [],
getCurrentProfile: () => get().currentProfile,
getProfile: profile => [...get().profiles, ...(defaultProfiles ?? [])].find(p => p.name === profile),
deleteProfile: profile => get().profiles = get().profiles.filter(p => typeof profile === "string" ? p.name !== profile : p.name !== profile.name),
duplicateProfile: (profile, name) => {
const foundProfile = get().profiles.find(p => typeof profile === "string" ? p.name === profile : p.name === profile.name);
if (foundProfile) {
foundProfile.name = name;
get().profiles.push(foundProfile);
}
},
setCurrentProfile: f => {
const currProfile = get().currentProfile;
get().currentProfile = (typeof f === "function" ? f(currProfile) ?? currProfile : f ?? currProfile);
},
saveProfile: profile => {
get().deleteProfile(profile.name);
get().profiles.push(profile);
},
isCurrentProfileADefaultProfile: () => defaultProfiles.some(profile => get().currentProfile.name === profile.name),
getDefaultProfiles: () => defaultProfiles,
getProfiles: defaultProfiles => [...get().profiles, ...(defaultProfiles ? get().getDefaultProfiles() : [])],
...f(set as any, get as any)
});
}

View file

@ -0,0 +1,102 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2023 Vendicated and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Settings, useSettings } from "@api/Settings";
export type PluginSettings = {
[key: string]: any;
};
export type PluginUse<Z extends PluginSettings> = () => Z;
export type PluginGet<Z extends PluginSettings> = () => Z;
export type PluginSet<Z extends PluginSettings> = (s: Z | ((settings: Z) => Z | undefined)) => Z;
export type PluginInitializer<Z extends PluginSettings, T = Z> = (set: PluginSet<Z>, get: PluginGet<Z>) => T;
export interface PluginStore<Z extends PluginSettings> {
use: PluginUse<Z>,
get: PluginGet<Z>,
set: PluginSet<Z>;
}
function createObjectProxy<T extends object>(obj1: T, onUpdate: (updatedObject: T) => void): T {
const handler: ProxyHandler<T> = {
set(target, property, value, receiver) {
const success = Reflect.set(target, property, value, receiver);
const nestedObj = target[property];
if (typeof nestedObj === "object") {
target[property] = createObjectProxy(nestedObj, () => { onUpdate(obj1); }); // On update will call itself until the top level object
}
onUpdate(obj1); // This will recursively call on nested objects
return success;
}
};
return new Proxy(obj1, handler);
}
const startupStates = {};
const settingStorage = new Map();
export function createPluginStore<Z extends PluginSettings = {}>(pluginName: string, storeName: string, f: PluginInitializer<Z>): PluginStore<Z> {
if (!Settings.plugins[pluginName])
throw new Error("The specified plugin does not exist");
if (!Settings.plugins[pluginName].stores)
Settings.plugins[pluginName].stores = {};
if (!Settings.plugins[pluginName].stores[storeName]) // Just incase the store doesn't exist we create it here (otherwise we crash)
Settings.plugins[pluginName].stores[storeName] = {};
const get: PluginGet<Z> = () => {
const storeSettings = settingStorage.get(storeName);
if (!startupStates[storeName]) { // We do this so that we can load all the saved data without the proxy attempting to overwrite it
const startupInfo = Settings.plugins[pluginName].stores[storeName];
Object.keys(startupInfo).forEach(prop => storeSettings[prop] = startupInfo[prop]);
startupStates[storeName] = true;
}
return storeSettings;
};
const set: PluginSet<Z> = (s: ((settings: Z) => Z | undefined) | Z) =>
(typeof s === "function" ? s(get()) : s) || get();
const use: PluginUse<Z> = () => { useSettings().plugins[pluginName].stores[storeName]; return get(); }; // useSettings is called to update renderer (after settings change)
const initialSettings: Z = f(set, get);
const proxiedSettings = createObjectProxy(initialSettings as unknown, updateCallback); // Setup our proxy that allows us connections to the datastore
function updateCallback(updatedObject: any) {
if (!startupStates[storeName]) return; // Wait for the startup information to overwrite the blank proxy
Settings.plugins[pluginName].stores[storeName] = JSON.parse(JSON.stringify(updatedObject));
}
for (const key of Object.keys(initialSettings)) { proxiedSettings[key] = initialSettings[key]; } // Set them so the nested objects also become proxies
settingStorage.set(storeName, proxiedSettings);
updateCallback(initialSettings);
return {
use,
get,
set
};
}

View file

@ -0,0 +1,19 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2023 Vendicated and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
export * from "./styles";

View file

@ -0,0 +1,25 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2023 Vendicated and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
export const Styles = {
infoCard: {
padding: "1em",
width: "100%",
boxSizing: "border-box"
},
} as const satisfies Record<string, React.CSSProperties>;

View file

@ -0,0 +1,46 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2023 Vendicated and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
export interface Resolution {
width: number;
height: number;
}
export interface Framerate {
framerate: number;
}
export interface Bitrate {
min: number;
target: number;
max: number;
}
export type DeepPartial<T> = T extends Function
? T
: T extends Array<infer InferredArrayMember>
? DeepPartialArray<InferredArrayMember>
: T extends object
? DeepPartialObject<T>
: T | undefined;
interface DeepPartialArray<T> extends Array<DeepPartial<T>> { }
type DeepPartialObject<T> = {
[key in keyof T]?: DeepPartial<T[key]>;
};

View file

@ -0,0 +1,31 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2023 Vendicated and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { PluginAuthor } from "@utils/types";
export type Author = PluginAuthor & { github?: string; };
export type Contributor = Author;
export interface PluginInfo {
[key: string]: any;
PLUGIN_NAME: string,
DESCRIPTION: string,
AUTHOR: PluginAuthor & { github?: string; },
CONTRIBUTORS?: Record<string, PluginAuthor & { github?: string; }>,
README?: string;
}

View file

@ -0,0 +1,19 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2023 Vendicated and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
export * from "./panelClasses";

View file

@ -0,0 +1,53 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2023 Vendicated and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
export interface PanelClasses {
container: string;
inner: string;
channel: string;
statusWithPopout: string;
hotspot: string;
customStatusContainer: string;
noiseCancellationPopout: string;
noiseCancellationTooltip: string;
krispLogo: string;
krispLink: string;
micTestButton: string;
beta: string;
connection: string;
voiceUsers: string;
actionButtons: string;
button: string;
buttonColor: string;
buttonActive: string;
fauxDisabled: string;
buttonDeveloperActivityShelf: string;
active: string;
buttonContents: string;
buttonIcon: string;
withText: string;
voicePanelIntroductionHeader: string;
voicePanelIntroductionText: string;
voicePanelIntroductionButton: string;
voicePanelIntroductionWrapper: string;
wrapper: string;
viewAsRolesWarning: string;
viewAsRolesWarningText: string;
viewAsRolesWarningButton: string;
disabled: string;
}

View file

@ -0,0 +1,19 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2023 Vendicated and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
export * from "./userSummaryItem";

View file

@ -0,0 +1,35 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2023 Vendicated and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { User } from "discord-types/general";
import type { ComponentType } from "react";
export interface UserSummaryItemProps {
guildId?: string;
className?: string;
users?: User[];
renderUser?: (...props: any[]) => any;
renderMoreUsers?: (...props: any[]) => any;
max?: number;
showUserPopout?: boolean;
renderIcon?: boolean;
showDefaultAvatarsForNullUsers?: boolean;
size?: number;
}
export type UserSummaryItem = ComponentType<UserSummaryItemProps>;

View file

@ -0,0 +1,22 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2023 Vendicated and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
export * from "./classes";
export * from "./components";
export * from "./modules";
export * from "./stores";

View file

@ -0,0 +1,56 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2023 Vendicated and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
export interface Conn {
destroy: (...args: any[]) => any;
setTransportOptions: (options: Record<string, any>) => any;
setSelfMute: (...args: any[]) => any;
setSelfDeafen: (...args: any[]) => any;
mergeUsers: (...args: any[]) => any;
destroyUser: (...args: any[]) => any;
setLocalVolume: (...args: any[]) => any;
setLocalMute: (...args: any[]) => any;
setLocalPan: (...args: any[]) => any;
setDisableLocalVideo: (...args: any[]) => any;
setMinimumOutputDelay: (...args: any[]) => any;
getEncryptionModes: (...args: any[]) => any;
configureConnectionRetries: (...args: any[]) => any;
setOnSpeakingCallback: (...args: any[]) => any;
setOnSpeakingWhileMutedCallback: (...args: any[]) => any;
setPingInterval: (...args: any[]) => any;
setPingCallback: (...args: any[]) => any;
setPingTimeoutCallback: (...args: any[]) => any;
setRemoteUserSpeakingStatus: (...args: any[]) => any;
setRemoteUserCanHavePriority: (...args: any[]) => any;
setOnVideoCallback: (...args: any[]) => any;
setVideoBroadcast: (...args: any[]) => any;
setDesktopSource: (...args: any[]) => any;
setDesktopSourceWithOptions: (...args: any[]) => any;
clearDesktopSource: (...args: any[]) => any;
setDesktopSourceStatusCallback: (...args: any[]) => any;
setOnDesktopSourceEnded: (...args: any[]) => any;
setOnSoundshare: (...args: any[]) => any;
setOnSoundshareEnded: (...args: any[]) => any;
setOnSoundshareFailed: (...args: any[]) => any;
setPTTActive: (...args: any[]) => any;
getStats: (...args: any[]) => any;
getFilteredStats: (...args: any[]) => any;
startReplay: (...args: any[]) => any;
startSamplesPlayback: (...args: any[]) => any;
stopSamplesPlayback: (...args: any[]) => any;
}

View file

@ -0,0 +1,387 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2023 Vendicated and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import TypedEmitter from "typed-emitter";
import { Framerate, Resolution } from "../../../types";
import { Conn, FramerateReducer, VideoQualityManager } from "./";
export const ConnectionEvent = {
SPEAKING: "speaking",
MUTE: "mute",
NEW_LISTENER: "newListener",
DESTORY: "destroy",
CONNECTED: "connected",
SILENCE: "silence",
DESKTOP_SOURCE_END: "desktopsourceend",
SOUNDSHARE_ATTACHED: "soundshareattached",
SOUNDSHARE_FAILED: "soundsharefailed",
SOUNDSHARE_SPEAKING: "soundsharespeaking",
SOUNDSHARE_TRACE: "soundsharetrace",
INTERACTION_REQUIRED: "interactionrequired",
VIDEOHOOK_INITIALIZED: "videohook-initialize",
SCREENSHARE_FAILED: "screenshare-finish",
NOISE_CANCELLER_ERROR: "noisecancellererror",
VOICE_ACTIVITY_DETECTOR_ERROR: "voiceactivitydetectorerror",
VIDEO_STATE: "video-state",
VIDEO: "video",
FIRST_FRAME: "first-frame",
ERROR: "error",
CONNECTION_STATE_CHANGE: "connectionstatechange",
PING: "ping",
PING_TIMEOUT: "pingtimeout",
OUTBOUND_LOSSRATE: "outboundlossrate",
LOCAL_VIDEO_DISABLED: "local-video-disabled",
STATS: "stats",
} as const;
export type ConnectionEvent = typeof ConnectionEvent;
export type ConnectionEvents = {
[ConnectionEvent.SPEAKING]: (...args: any[]) => any;
[ConnectionEvent.MUTE]: (...args: any[]) => any;
[ConnectionEvent.NEW_LISTENER]: (...args: any[]) => any;
[ConnectionEvent.DESTORY]: (...args: any[]) => any;
[ConnectionEvent.CONNECTED]: (...args: any[]) => any;
[ConnectionEvent.SILENCE]: (...args: any[]) => any;
[ConnectionEvent.DESKTOP_SOURCE_END]: (...args: any[]) => any;
[ConnectionEvent.SOUNDSHARE_ATTACHED]: (...args: any[]) => any;
[ConnectionEvent.SOUNDSHARE_FAILED]: (...args: any[]) => any;
[ConnectionEvent.SOUNDSHARE_SPEAKING]: (...args: any[]) => any;
[ConnectionEvent.SOUNDSHARE_TRACE]: (...args: any[]) => any;
[ConnectionEvent.INTERACTION_REQUIRED]: (...args: any[]) => any;
[ConnectionEvent.VIDEOHOOK_INITIALIZED]: (...args: any[]) => any;
[ConnectionEvent.SCREENSHARE_FAILED]: (...args: any[]) => any;
[ConnectionEvent.NOISE_CANCELLER_ERROR]: (...args: any[]) => any;
[ConnectionEvent.VOICE_ACTIVITY_DETECTOR_ERROR]: (...args: any[]) => any;
[ConnectionEvent.VIDEO_STATE]: (...args: any[]) => any;
[ConnectionEvent.VIDEO]: (...args: any[]) => any;
[ConnectionEvent.FIRST_FRAME]: (...args: any[]) => any;
[ConnectionEvent.ERROR]: (...args: any[]) => any;
[ConnectionEvent.CONNECTION_STATE_CHANGE]: (...args: any[]) => any;
[ConnectionEvent.PING]: (...args: any[]) => any;
[ConnectionEvent.PING_TIMEOUT]: (...args: any[]) => any;
[ConnectionEvent.OUTBOUND_LOSSRATE]: (...args: any[]) => any;
[ConnectionEvent.LOCAL_VIDEO_DISABLED]: (...args: any[]) => any;
[ConnectionEvent.STATS]: (...args: any[]) => any;
};
export type Connection = TypedEmitter<ConnectionEvents> &
Connection__ &
Connection_ & {
streamUserId: string,
goLiveSourceIdentifier?: string;
emitter: TypedEmitter<ConnectionEvents>;
mediaEngineConnectionId: string;
destroyed: boolean;
audioSSRC: number;
selfDeaf: boolean;
localMutes: LocalMutes;
disabledLocalVideos: LocalMutes;
localVolumes: LocalVolumes;
isActiveOutputSinksEnabled: boolean;
activeOutputSinks: LocalMutes;
videoSupported: boolean;
useElectronVideo: boolean;
voiceBitrate: number;
remoteSinkWantsMaxFramerate: number;
wantsPriority: Set<any>;
localSpeakingFlags: LocalSpeakingFlags;
videoReady: boolean;
videoStreamParameters: VideoStreamParameter[];
remoteVideoSinkWants: VideoSinkWants;
localVideoSinkWants: VideoSinkWants;
connectionState: string;
experimentFlags: string[];
context: string;
ids: Ids;
selfMute: boolean;
selfVideo: boolean;
forceAudioNormal: boolean;
forceAudioPriority: boolean;
codecs: Codec[];
desktopDegradationPreference: number;
sourceDesktopDegradationPreference: number;
videoDegradationPreference: number;
localPans: LocalMutes;
remoteAudioSSRCs: LocalMutes;
remoteVideoSSRCs: LocalMutes;
inputMode: string;
vadThreshold: number;
vadAutoThreshold: boolean;
vadUseKrisp: boolean;
vadLeading: number;
vadTrailing: number;
pttReleaseDelay: number;
soundshareActive: boolean;
soundshareId?: any;
soundshareSentSpeakingEvent: boolean;
echoCancellation: boolean;
noiseSuppression: boolean;
automaticGainControl: boolean;
noiseCancellation: boolean;
experimentalEncoders: boolean;
hardwareH264: boolean;
attenuationFactor: number;
attenuateWhileSpeakingSelf: boolean;
attenuateWhileSpeakingOthers: boolean;
qos: boolean;
minimumJitterBufferLevel: number;
postponeDecodeLevel: number;
reconnectInterval: number;
keyframeInterval: number;
conn: Conn;
stats: Stats;
framerateReducer: FramerateReducer;
videoQualityManager: VideoQualityManager;
handleSpeakingNative: (...args: any[]) => any;
handleSpeakingFlags: (...args: any[]) => any;
handleSpeakingWhileMuted: (...args: any[]) => any;
handlePing: (...args: any[]) => any;
handlePingTimeout: (...args: any[]) => any;
handleVideo: (...args: any[]) => any;
handleFirstFrame: (...args: any[]) => any;
handleNoInput: (...args: any[]) => any;
handleDesktopSourceEnded: (...args: any[]) => any;
handleSoundshare: (...args: any[]) => any;
handleSoundshareFailed: (...args: any[]) => any;
handleSoundshareEnded: (...args: any[]) => any;
handleNewListenerNative: (...args: any[]) => any;
handleStats: (...args: any[]) => any;
__proto__: Connection_;
};
interface Connection_ {
initialize: (...args: any[]) => any;
destroy: (...args: any[]) => any;
setCodecs: (audioCodec: string, videoCodec: string, context: string) => any;
getStats: (...args: any[]) => any;
createUser: (...args: any[]) => any;
destroyUser: (...args: any[]) => any;
setSelfMute: (...args: any[]) => any;
setSelfDeaf: (...args: any[]) => any;
setSoundshareSource: (id: number, loopback: boolean) => void;
setLocalMute: (...args: any[]) => any;
setLocalVideoDisabled: (...args: any[]) => any;
setMinimumJitterBufferLevel: (...args: any[]) => any;
setPostponeDecodeLevel: (...args: any[]) => any;
setClipRecordSsrc: (...args: any[]) => any;
getLocalVolume: (...args: any[]) => any;
setLocalVolume: (...args: any[]) => any;
setLocalPan: (...args: any[]) => any;
isAttenuating: (...args: any[]) => any;
setAttenuation: (...args: any[]) => any;
setCanHavePriority: (...args: any[]) => any;
setBitRate: (...args: any[]) => any;
setVoiceBitRate: (target: number) => void;
setCameraBitRate: (target: number, min: number, max: number) => void;
setEchoCancellation: (...args: any[]) => any;
setNoiseSuppression: (...args: any[]) => any;
setAutomaticGainControl: (...args: any[]) => any;
setNoiseCancellation: (...args: any[]) => any;
setExperimentalEncoders: (...args: any[]) => any;
setHardwareH264: (...args: any[]) => any;
setQoS: (...args: any[]) => any;
setInputMode: (...args: any[]) => any;
setSilenceThreshold: (...args: any[]) => any;
setForceAudioInput: (...args: any[]) => any;
setSpeakingFlags: (...args: any[]) => any;
clearAllSpeaking: (...args: any[]) => any;
setEncryption: (...args: any[]) => any;
setReconnectInterval: (...args: any[]) => any;
setKeyframeInterval: (keyframeInterval: number) => void;
setVideoBroadcast: (...args: any[]) => any;
setDesktopSource: (
source: string | null,
options?: DesktopSourceOptions
) => void;
clearDesktopSource: (...args: any[]) => any;
setDesktopSourceStatusCallback: (...args: any[]) => any;
hasDesktopSource: (...args: any[]) => any;
setDesktopEncodingOptions: (
width: number,
height: number,
framerate: number
) => void;
setSDP: (...args: any[]) => any;
setRemoteVideoSinkWants: (...args: any[]) => any;
setLocalVideoSinkWants: (...args: any[]) => any;
startSamplesPlayback: (...args: any[]) => any;
stopSamplesPlayback: (...args: any[]) => any;
startSamplesLocalPlayback: (...args: any[]) => any;
stopAllSamplesLocalPlayback: (...args: any[]) => any;
stopSamplesLocalPlayback: (...args: any[]) => any;
updateVideoQualityCore: (...args: any[]) => any;
setStreamParameters: (...args: any[]) => any;
applyVideoTransportOptions: (...args: any[]) => any;
chooseEncryptionMode: (...args: any[]) => any;
getUserOptions: (...args: any[]) => any;
createInputModeOptions: (...args: any[]) => any;
getAttenuationOptions: (...args: any[]) => any;
getCodecParams: (...args: any[]) => any;
getCodecOptions: (...args: any[]) => any;
getConnectionTransportOptions: (...args: any[]) => any;
setStream: (...args: any[]) => any;
getUserIdBySsrc: (...args: any[]) => any;
setRtcLogEphemeralKey: (...args: any[]) => any;
setRtcLogMarker: (...args: any[]) => any;
__proto__: Connection__;
}
interface Connection__ {
destroy: (...args: any[]) => any;
getLocalMute: (...args: any[]) => any;
getLocalVideoDisabled: (...args: any[]) => any;
setLocalVideoDisabled: (...args: any[]) => any;
getHasActiveVideoOutputSink: (...args: any[]) => any;
setHasActiveVideoOutputSink: (...args: any[]) => any;
getActiveOutputSinkTrackingEnabled: (...args: any[]) => any;
setUseElectronVideo: (...args: any[]) => any;
setClipRecordSsrc: (...args: any[]) => any;
getStreamParameters: (...args: any[]) => any;
setExperimentFlag: (...args: any[]) => any;
setConnectionState: (...args: any[]) => any;
updateVideoQuality: (...args: any[]) => any;
applyVideoQualityMode: (...args: any[]) => any;
overwriteQualityForTesting: (args: {
encode: Resolution & Framerate;
capture: Resolution & Framerate;
bitrateMin: number;
bitrateMax: number;
bitrateTarget: number;
}) => any;
applyQualityConstraints: (...args: any[]) => any;
pickProperties: (...args: any[]) => any;
initializeStreamParameters: (...args: any[]) => any;
getLocalWant: (...args: any[]) => any;
emitStats: (...args: any[]) => any;
__proto__: TypedEmitter<ConnectionEvents>;
}
export interface Stats {
mediaEngineConnectionId: string;
transport: Transport;
camera?: any;
rtp: Rtp;
}
export interface Rtp {
inbound: LocalMutes;
outbound: Outbound[];
}
export interface Outbound {
type: string;
ssrc: number;
sinkWant: string;
codec: OutboundCodec;
bytesSent: number;
packetsSent: number;
packetsLost: number;
fractionLost: number;
audioLevel: number;
audioDetected: number;
framesCaptured: number;
framesRendered: number;
noiseCancellerProcessTime: number;
}
export interface OutboundCodec {
id: number;
name: string;
}
export interface Transport {
availableOutgoingBitrate: number;
ping: number;
decryptionFailures: number;
routingFailures: number;
localAddress: string;
pacerDelay: number;
receiverReports: any[];
receiverBitrateEstimate: number;
outboundBitrateEstimate: number;
inboundBitrateEstimate: number;
bytesSent: number;
}
export interface Codec {
type: string;
name: string;
priority: number;
payloadType: number;
rtxPayloadType?: number;
encode?: boolean;
decode?: boolean;
}
export interface Ids {
userId: string;
channelId: string;
guildId: string;
}
export interface VideoSinkWants {
any: number;
}
export interface VideoStreamParameter {
type: string;
active: boolean;
rid: string;
ssrc: number;
rtxSsrc: number;
quality: number;
maxBitrate: number;
maxFrameRate: number;
maxResolution: MaxResolution;
maxPixelCount: number;
}
export type MaxResolution = Partial<Resolution> & {
type: "fixed" | "source";
};
export interface LocalSpeakingFlags {
[key: string]: number;
}
export interface LocalVolumes {
[key: string]: number;
}
export interface LocalMutes {
[key: string]: any;
}
export const HdrCaptureMode = {
NEVER: "never",
ALWAYS: "always",
PERMITTED_DEVICES_ONLY: "permittedDevicesOnly",
} as const;
export type HdrCaptureMode = typeof HdrCaptureMode;
export interface DesktopSourceOptions extends Partial<Resolution> {
fps?: number;
useVideoHook?: boolean;
useGraphicsCapture?: boolean;
useQuartzCapturer?: boolean;
allowScreenCaptureKit?: boolean;
hdrCaptureMode?: HdrCaptureMode[keyof HdrCaptureMode];
}

View file

@ -0,0 +1,36 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2023 Vendicated and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Connection, VideoQualityManager } from "./";
export type FramerateReducer = FramerateReducer_ & {
connection: Connection;
sinkWants: VideoQualityManager;
framerateReductionTimeout?: number;
handleSelfMute: (...args: any[]) => any;
handleSpeaking: (...args: any[]) => any;
__proto__: FramerateReducer_;
};
export interface FramerateReducer_ {
destroy: (...args: any[]) => any;
destroyFramerateScaleFactorTimers: (...args: any[]) => any;
initialize: (...args: any[]) => any;
updateRemoteWantsFramerate: (...args: any[]) => any;
userSpeakingChange: (...args: any[]) => any;
}

View file

@ -0,0 +1,24 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2023 Vendicated and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
export * from "./conn";
export * from "./connection";
export * from "./framerateReducer";
export * from "./mediaEngine";
export * from "./utils";
export * from "./videoQualityManager";

View file

@ -0,0 +1,148 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2023 Vendicated and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import TypedEmitter from "typed-emitter";
import { Connection } from "./";
export const MediaEngineEvent = {
REMOVE_LISTENER: "removeListener",
NEW_LISTENER: "newListener",
DESTORY: "destroy",
CONNECTION: "connection",
DEVICE_CHANGE: "devicechange",
VOLUME_CHANGE: "volumechange",
DESKTOP_SOURCE_END: "desktopsourceend",
AUDIO_PERMISSION: "audio-permission",
VIDEO_PERMISSION: "video-permission",
WATCHDOG_TIMEOUT: "watchdogtimeout",
VIDEO_INPUT_INITIALIZED: "video-input-initialized",
CONNECTION_STATS: "connection-stats",
} as const;
export type MediaEngineEvents = {
[MediaEngineEvent.REMOVE_LISTENER]: (...args: any[]) => any;
[MediaEngineEvent.NEW_LISTENER]: (...args: any[]) => any;
[MediaEngineEvent.DESTORY]: (...args: any[]) => any;
[MediaEngineEvent.CONNECTION]: (connection: Connection) => void;
[MediaEngineEvent.DEVICE_CHANGE]: (...args: any[]) => any;
[MediaEngineEvent.VOLUME_CHANGE]: (...args: any[]) => any;
[MediaEngineEvent.DESKTOP_SOURCE_END]: (...args: any[]) => any;
[MediaEngineEvent.AUDIO_PERMISSION]: (...args: any[]) => any;
[MediaEngineEvent.VIDEO_PERMISSION]: (...args: any[]) => any;
[MediaEngineEvent.WATCHDOG_TIMEOUT]: (...args: any[]) => any;
[MediaEngineEvent.VIDEO_INPUT_INITIALIZED]: (...args: any[]) => any;
[MediaEngineEvent.CONNECTION_STATS]: (...args: any[]) => any;
[MediaEngineEvent.REMOVE_LISTENER]: (...args: any[]) => any;
};
export type MediaEngine = TypedEmitter<MediaEngineEvents> &
MediaEngine_ & {
Video: (...args: any[]) => any;
Camera: (...args: any[]) => any;
handleDeviceChange: (...args: any[]) => any;
handleVolumeChange: (...args: any[]) => any;
handleVoiceActivity: (...args: any[]) => any;
handleActiveSinksChange: (...args: any[]) => any;
handleNewListener: (...args: any[]) => any;
handleRemoveListener: (...args: any[]) => any;
handleVideoInputInitialization: (...args: any[]) => any;
emitter: TypedEmitter<MediaEngineEvents>;
videoInputDeviceId: string;
lastVoiceActivity: number;
audioSubsystem: string;
audioLayer: string;
loopback: boolean;
deviceChangeGeneration: number;
consecutiveWatchdogFailures: number;
codecSurvey?: any;
connections: Set<Connection>;
__proto__: MediaEngine_;
};
export interface MediaEngine_ {
destroy: (...args: any[]) => any;
interact: (...args: any[]) => any;
supported: (...args: any[]) => any;
supports: (...args: any[]) => any;
connect: (...args: any[]) => any;
shouldConnectionBroadcastVideo: (...args: any[]) => any;
eachConnection: (callback: (connection: Connection) => void) => void;
enable: (...args: any[]) => any;
setInputVolume: (...args: any[]) => any;
setOutputVolume: (...args: any[]) => any;
getAudioInputDevices: (...args: any[]) => any;
setAudioInputDevice: (...args: any[]) => any;
getAudioOutputDevices: (...args: any[]) => any;
setAudioOutputDevice: (...args: any[]) => any;
getVideoInputDevices: (...args: any[]) => any;
setVideoInputDevice: (...args: any[]) => any;
getSupportedVideoCodecs: (...args: any[]) => any;
getCodecCapabilities: (callback: (codecs: string) => void) => void;
setDesktopSource: (...args: any[]) => any;
setSoundshareSource: (...args: any[]) => any;
getDesktopSource: (...args: any[]) => any;
getDesktopSources: (...args: any[]) => any;
getScreenPreviews: (...args: any[]) => any;
setClipBufferLength: (...args: any[]) => any;
saveClip: (...args: any[]) => any;
updateClipMetadata: (...args: any[]) => any;
exportClip: (...args: any[]) => any;
getWindowPreviews: (
width: number,
height: number
) => Promise<WindowPreview[]>;
setAudioSubsystem: (...args: any[]) => any;
getAudioSubsystem: (...args: any[]) => any;
getAudioLayer: (...args: any[]) => any;
getDebugLogging: (...args: any[]) => any;
setDebugLogging: (...args: any[]) => any;
setExperimentalAdm: (...args: any[]) => any;
setLoopback: (...args: any[]) => any;
getLoopback: (...args: any[]) => any;
setH264Enabled: (...args: any[]) => any;
setAv1Enabled: (...args: any[]) => any;
getCodecSurvey: () => Promise<string>;
writeAudioDebugState: (...args: any[]) => any;
startAecDump: (...args: any[]) => any;
stopAecDump: (...args: any[]) => any;
setAecDump: (...args: any[]) => any;
rankRtcRegions: (...args: any[]) => any;
getSoundshareStatus: (...args: any[]) => any;
enableSoundshare: (...args: any[]) => any;
createReplayConnection: (...args: any[]) => any;
setUseDirectVideo: (...args: any[]) => any;
setMaxSyncDelayOverride: (...args: any[]) => any;
applyMediaFilterSettings: (...args: any[]) => any;
startLocalAudioRecording: (...args: any[]) => any;
stopLocalAudioRecording: (...args: any[]) => any;
watchdogTick: (...args: any[]) => any;
__proto__: TypedEmitter<MediaEngineEvents>;
}
export interface CodecCapabilities {
codec: string;
decode: boolean;
encode: boolean;
}
export interface WindowPreview {
id: string;
url: string;
name: string;
}

View file

@ -0,0 +1,103 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2023 Vendicated and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
export interface Utils {
requireModule: (...args: any[]) => any;
ensureModule: (...args: any[]) => any;
getCrashReporterMetadata: (...args: any[]) => any;
getSetting: (...args: any[]) => any;
beforeUnload: (...args: any[]) => any;
inputEventRegister: (...args: any[]) => any;
inputEventUnregister: (...args: any[]) => any;
setOnInputEventCallback: (...args: any[]) => any;
setFocused: (...args: any[]) => any;
getIdleMilliseconds: (...args: any[]) => any;
setObservedGamesCallback: (...args: any[]) => any;
setCandidateGamesCallback: (...args: any[]) => any;
clearCandidateGamesCallback: (...args: any[]) => any;
setGameCandidateOverrides: (...args: any[]) => any;
detectPid: (...args: any[]) => any;
undetectPid: (...args: any[]) => any;
shouldDisplayNotifications: (...args: any[]) => any;
getVoiceEngine: (...args: any[]) => any;
getDiscordUtils: (...args: any[]) => any;
isSystemDarkMode: (...args: any[]) => any;
getGameUtils: (...args: any[]) => any;
getCloudSync: (...args: any[]) => any;
getDispatch: (...args: any[]) => any;
setBadge: (...args: any[]) => any;
setSystemTrayIcon: (...args: any[]) => any;
setThumbarButtons: (...args: any[]) => any;
bounceDock: (...args: any[]) => any;
setSystemTrayApplications: (...args: any[]) => any;
architecture: (...args: any[]) => any;
moduleVersions: (...args: any[]) => any;
copy: (...args: any[]) => any;
copyImage: (...args: any[]) => any;
saveImage: (...args: any[]) => any;
saveFile: (...args: any[]) => any;
canCopyImage: (...args: any[]) => any;
cut: (...args: any[]) => any;
paste: (...args: any[]) => any;
readClipboard: (...args: any[]) => any;
on: (...args: any[]) => any;
invoke: (...args: any[]) => any;
send: (...args: any[]) => any;
flashFrame: (...args: any[]) => any;
minimize: (...args: any[]) => any;
restore: (...args: any[]) => any;
maximize: (...args: any[]) => any;
focus: (...args: any[]) => any;
blur: (...args: any[]) => any;
fullscreen: (...args: any[]) => any;
close: (...args: any[]) => any;
setAlwaysOnTop: (...args: any[]) => any;
isAlwaysOnTop: (...args: any[]) => any;
purgeMemory: (...args: any[]) => any;
updateCrashReporter: (...args: any[]) => any;
flushDNSCache: (...args: any[]) => any;
supportsFeature: (...args: any[]) => any;
getEnableHardwareAcceleration: (...args: any[]) => any;
setEnableHardwareAcceleration: (...args: any[]) => any;
getGPUDriverVersions: (...args: any[]) => any;
setZoomFactor: (...args: any[]) => any;
setBackgroundThrottling: (...args: any[]) => any;
getPidFromDesktopSource: (...args: any[]) => any;
getDesktopSourceFromPid: (...args: any[]) => any;
generateSessionFromPid: (...args: any[]) => any;
getAudioPid: (...args: any[]) => any;
setForegroundProcess: (...args: any[]) => any;
getDiscordMemoryUsage: (...args: any[]) => any;
showOpenDialog: (...args: any[]) => any;
flushStorageData: (...args: any[]) => any;
flushCookies: (...args: any[]) => any;
setCrashInformation: (...args: any[]) => any;
blockDisplaySleep: (...args: any[]) => any;
unblockDisplaySleep: (...args: any[]) => any;
cleanupDisplaySleep: (...args: any[]) => any;
relaunch: (...args: any[]) => any;
makeChunkedRequest: (...args: any[]) => any;
submitLiveCrashReport: (...args: any[]) => any;
crash: (...args: any[]) => any;
setApplicationBackgroundColor: (...args: any[]) => any;
asyncify: (...args: any[]) => any;
releaseChannel: string,
canBootstrapNewUpdater: boolean;
buildNumber: number;
version: number[];
}

View file

@ -0,0 +1,81 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2023 Vendicated and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Bitrate, Framerate, Resolution } from "../../";
import { Connection } from "./";
export type VideoQualityManager = VideoQualityManager_ & {
connection: Connection;
contextType: string;
isMuted: boolean;
isStreamContext: boolean;
ladder: Ladder;
options: Options;
qualityOverwrite: QualityOverwrite;
__proto__: VideoQualityManager_;
};
export interface VideoQualityManager_ {
applyQualityConstraints: (...args: any[]) => any;
getDesktopQuality: (...args: any[]) => any;
getQuality: (...args: any[]) => any;
getVideoQuality: (...args: any[]) => any;
setQuality: (...args: any[]) => any;
}
export interface QualityOverwrite {
bitrateMax?: number;
bitrateMin?: number;
bitrateTarget?: number;
capture?: Resolution & Framerate;
encode?: Resolution & Framerate;
}
export interface Options {
videoBudget: VideoBudget;
videoCapture: VideoBudget;
videoBitrate: VideoBitrate;
desktopBitrate: DesktopBitrate;
videoBitrateFloor: number;
}
export type DesktopBitrate = Bitrate;
export type VideoBitrate = Omit<Bitrate, "target">;
export type VideoBudget = Resolution & Framerate;
export interface Ladder {
pixelBudget: number;
ladder: {
[key: number]: LadderValue;
};
orderedLadder: OrderedLadder[];
}
export interface OrderedLadder extends Resolution, Framerate {
pixelCount: number;
wantValue: number;
budgetPortion: number;
mutedFramerate: Framerate;
}
export interface LadderValue extends Resolution, Framerate {
budgetPortion: number;
mutedFramerate: Framerate;
}

View file

@ -0,0 +1,19 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2023 Vendicated and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
export * from "./mediaEngineStore";

View file

@ -0,0 +1,119 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2023 Vendicated and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { types } from "../../..";
export type MediaEngineStore = MediaEngineStore__ &
MediaEngineStore_ & {
__proto__: MediaEngineStore_;
};
export interface MediaEngineStore_ {
initialize: (...args: any[]) => any;
supports: (...args: any[]) => any;
supportsInApp: (...args: any[]) => any;
isSupported: (...args: any[]) => any;
isExperimentalEncodersSupported: (...args: any[]) => any;
isNoiseSuppressionSupported: (...args: any[]) => any;
isNoiseCancellationSupported: (...args: any[]) => any;
isNoiseCancellationError: (...args: any[]) => any;
isAutomaticGainControlSupported: (...args: any[]) => any;
isAdvancedVoiceActivitySupported: (...args: any[]) => any;
isAecDumpSupported: (...args: any[]) => any;
isSimulcastSupported: (...args: any[]) => any;
getAecDump: (...args: any[]) => any;
getMediaEngine: () => types.MediaEngine;
getVideoComponent: (...args: any[]) => any;
getCameraComponent: (...args: any[]) => any;
isEnabled: (...args: any[]) => any;
isMute: (...args: any[]) => any;
isDeaf: (...args: any[]) => any;
hasContext: (...args: any[]) => any;
isSelfMutedTemporarily: (...args: any[]) => any;
isSelfMute: (...args: any[]) => any;
isHardwareMute: (...args: any[]) => any;
isSelfDeaf: (...args: any[]) => any;
isVideoEnabled: (...args: any[]) => any;
isVideoAvailable: (...args: any[]) => any;
isScreenSharing: (...args: any[]) => any;
isSoundSharing: (...args: any[]) => any;
isLocalMute: (...args: any[]) => any;
supportsDisableLocalVideo: (...args: any[]) => any;
isLocalVideoDisabled: (...args: any[]) => any;
isLocalVideoAutoDisabled: (...args: any[]) => any;
isMediaFilterSettingLoading: (...args: any[]) => any;
isNativeAudioPermissionReady: (...args: any[]) => any;
getDesktopSource: (...args: any[]) => any;
getDesktopSourceContext: (...args: any[]) => any;
getLocalPan: (...args: any[]) => any;
getLocalVolume: (...args: any[]) => any;
getInputVolume: (...args: any[]) => any;
getOutputVolume: (...args: any[]) => any;
getMode: (...args: any[]) => any;
getModeOptions: (...args: any[]) => any;
getShortcuts: (...args: any[]) => any;
getInputDeviceId: (...args: any[]) => any;
getOutputDeviceId: (...args: any[]) => any;
getVideoDeviceId: (...args: any[]) => any;
getInputDevices: (...args: any[]) => any;
getOutputDevices: (...args: any[]) => any;
getVideoDevices: (...args: any[]) => any;
getEchoCancellation: (...args: any[]) => any;
getLoopback: (...args: any[]) => any;
getNoiseSuppression: (...args: any[]) => any;
getAutomaticGainControl: (...args: any[]) => any;
getNoiseCancellation: (...args: any[]) => any;
getExperimentalEncoders: (...args: any[]) => any;
getHardwareH264: (...args: any[]) => any;
getEnableSilenceWarning: (...args: any[]) => any;
getDebugLogging: (...args: any[]) => any;
getQoS: (...args: any[]) => any;
getAttenuation: (...args: any[]) => any;
getAttenuateWhileSpeakingSelf: (...args: any[]) => any;
getAttenuateWhileSpeakingOthers: (...args: any[]) => any;
getAudioSubsystem: (...args: any[]) => any;
getSettings: (...args: any[]) => any;
getState: (...args: any[]) => any;
getInputDetected: (...args: any[]) => any;
getNoInputDetectedNotice: (...args: any[]) => any;
getPacketDelay: (...args: any[]) => any;
setCanHavePriority: (...args: any[]) => any;
isInteractionRequired: (...args: any[]) => any;
getVideoHook: (...args: any[]) => any;
getExperimentalSoundshare: (...args: any[]) => any;
supportsExperimentalSoundshare: (...args: any[]) => any;
getOpenH264: (...args: any[]) => any;
getAv1Enabled: (...args: any[]) => any;
getEverSpeakingWhileMuted: (...args: any[]) => any;
getSoundshareEnabled: (...args: any[]) => any;
supportsEnableSoundshare: (...args: any[]) => any;
getVideoStreamParameters: (...args: any[]) => any;
__proto__: MediaEngineStore__;
}
export interface MediaEngineStore__ {
registerActionHandlers: (...args: any[]) => any;
getName: (...args: any[]) => any;
initializeIfNeeded: (...args: any[]) => any;
initialize: (...args: any[]) => any;
syncWith: (...args: any[]) => any;
waitFor: (...args: any[]) => any;
emitChange: (...args: any[]) => any;
getDispatchToken: (...args: any[]) => any;
mustEmitChanges: (...args: any[]) => any;
}

View file

@ -0,0 +1,21 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2023 Vendicated and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
export * from "./common";
export * from "./constants";
export * from "./discordModules";

View file

@ -0,0 +1,19 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2023 Vendicated and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
export * from "./utils";

View file

@ -0,0 +1,27 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2023 Vendicated and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { UserStore } from "@webpack/common";
import { User } from "discord-types/general";
export const createDummyUser = (props: Partial<User>) => new (UserStore.getCurrentUser().constructor as any)(props);
export const openURL = (url: string) => VencordNative.native.openExternal(url);
export const validateNumberInput = (value: string) => parseInt(value) ? parseInt(value) : undefined;
export const validateTextInputNumber = (value: string) => /^[0-9\b]+$/.test(value) || value === "";
export const replaceObjectValuesIfExist =
(target: Object, replace: Object) => Object.entries(target).forEach(([key, value]) => replace[key] && (target[key] = replace[key]));

View file

@ -88,7 +88,7 @@ export const userContextPatch: NavContextMenuPatchCallback = (children, { user }
export default definePlugin({
name: "BiggerStreamPreview",
description: "This plugin allows you to enlarge stream previews",
authors: [Devs.phil],
authors: [Devs.philhk],
contextMenus: {
"user-context": userContextPatch,
"stream-context": streamContextPatch

View file

@ -320,8 +320,8 @@ export const Devs = /* #__PURE__*/ Object.freeze({
name: "amia",
id: 142007603549962240n
},
phil: {
name: "phil",
philhk: {
name: "philhk",
id: 305288513941667851n
},
ImLvna: {