Stuff
This commit is contained in:
parent
7809f5b67c
commit
3c4d217312
108 changed files with 7134 additions and 38 deletions
24
src/equicordplugins/amITyping/index.ts
Normal file
24
src/equicordplugins/amITyping/index.ts
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2024 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { EquicordDevs } from "@utils/constants";
|
||||
import definePlugin from "@utils/types";
|
||||
|
||||
export default definePlugin({
|
||||
name: "AmITyping",
|
||||
description: "Shows you if other people can see you typing.",
|
||||
authors: [EquicordDevs.MrDiamond],
|
||||
|
||||
patches: [
|
||||
{
|
||||
find: "\"handleDismissInviteEducation\"",
|
||||
replacement: {
|
||||
match: /\i\.default\.getCurrentUser\(\)/,
|
||||
replace: "\"\""
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
100
src/equicordplugins/betterBanReasons/index.tsx
Normal file
100
src/equicordplugins/betterBanReasons/index.tsx
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2024 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import "./style.css";
|
||||
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import { Devs } from "@utils/constants";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import { Button, Forms, i18n, TextInput } from "@webpack/common";
|
||||
|
||||
function ReasonsComponent() {
|
||||
const { reasons } = settings.use(["reasons"]);
|
||||
|
||||
return (
|
||||
<Forms.FormSection title="Reasons">
|
||||
{reasons.map((reason: string, index: number) => (
|
||||
<div
|
||||
className="vc-bbr-reason-wrapper"
|
||||
>
|
||||
<TextInput
|
||||
type="text"
|
||||
key={index}
|
||||
value={reason}
|
||||
onChange={v => {
|
||||
reasons[index] = v;
|
||||
settings.store.reasons = [...reasons];
|
||||
}}
|
||||
placeholder="Reason"
|
||||
/>
|
||||
<Button
|
||||
color={Button.Colors.RED}
|
||||
className="vc-bbr-remove-button"
|
||||
onClick={() => {
|
||||
reasons.splice(index, 1);
|
||||
settings.store.reasons = [...reasons];
|
||||
}}
|
||||
>
|
||||
Remove
|
||||
</Button>
|
||||
</div>
|
||||
))}
|
||||
<Button
|
||||
onClick={() => {
|
||||
settings.store.reasons = [...reasons, ""];
|
||||
}}
|
||||
>
|
||||
Add new
|
||||
</Button>
|
||||
</Forms.FormSection>
|
||||
);
|
||||
}
|
||||
|
||||
const settings = definePluginSettings({
|
||||
reasons: {
|
||||
description: "Your custom reasons",
|
||||
type: OptionType.COMPONENT,
|
||||
default: [],
|
||||
component: ReasonsComponent,
|
||||
},
|
||||
textInputDefault: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: 'Shows a text input instead of a select menu by default. (Equivalent to clicking the "Other" option)'
|
||||
}
|
||||
});
|
||||
|
||||
export default definePlugin({
|
||||
name: "BetterBanReasons",
|
||||
description: "Create custom reasons to use in the Discord ban modal, and/or show a text input by default instead of the options.",
|
||||
authors: [Devs.Inbestigator],
|
||||
patches: [
|
||||
{
|
||||
find: "Messages.BAN_MULTIPLE_CONFIRM_TITLE",
|
||||
replacement: [{
|
||||
match: /\[\{name:\i\.\i\.Messages\.BAN_REASON_OPTION_SPAM_ACCOUNT.+?\}\]/,
|
||||
replace: "$self.getReasons()"
|
||||
},
|
||||
{
|
||||
match: /useState\(0\)(?=.{0,100}targetUserId:)/,
|
||||
replace: "useState($self.getDefaultState())"
|
||||
}]
|
||||
}
|
||||
],
|
||||
getReasons() {
|
||||
const reasons = settings.store.reasons.length
|
||||
? settings.store.reasons
|
||||
: [
|
||||
i18n.Messages.BAN_REASON_OPTION_SPAM_ACCOUNT,
|
||||
i18n.Messages.BAN_REASON_OPTION_HACKED_ACCOUNT,
|
||||
i18n.Messages.BAN_REASON_OPTION_BREAKING_RULES
|
||||
];
|
||||
return reasons.map(s => ({ name: s, value: s }));
|
||||
},
|
||||
getDefaultState() {
|
||||
return settings.store.textInputDefault ? 1 : 0;
|
||||
},
|
||||
settings,
|
||||
});
|
||||
11
src/equicordplugins/betterBanReasons/style.css
Normal file
11
src/equicordplugins/betterBanReasons/style.css
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
.vc-bbr-reason-wrapper {
|
||||
display: grid;
|
||||
padding: 0;
|
||||
padding-bottom: 0.5rem;
|
||||
gap: 0.5rem;
|
||||
grid-template-columns: 6fr 1fr;
|
||||
}
|
||||
|
||||
.vc-bbr-remove-button {
|
||||
height: 100%;
|
||||
}
|
||||
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
|
@ -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";
|
||||
|
|
@ -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;
|
||||
|
|
@ -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";
|
||||
63
src/equicordplugins/betterMicrophone.desktop/index.tsx
Normal file
63
src/equicordplugins/betterMicrophone.desktop/index.tsx
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* 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 definePlugin 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";
|
||||
|
||||
var microphonePatcher;
|
||||
|
||||
// const settings = definePluginSettings({
|
||||
// openSettings: {
|
||||
// description: "",
|
||||
// type: OptionType.COMPONENT,
|
||||
// component: (() => {
|
||||
// return (
|
||||
// <Button onClick={() => openMicrophoneSettingsModal()}>Open Settings</Button>
|
||||
// );
|
||||
// })
|
||||
// }
|
||||
// });
|
||||
|
||||
export default definePlugin({
|
||||
name: "PhilsBetterMicrophone",
|
||||
description: "This plugin allows you to further customize your microphone.",
|
||||
authors: [Devs.philhk],
|
||||
dependencies: ["PhilsPluginLibrary"],
|
||||
|
||||
start() {
|
||||
initMicrophoneStore();
|
||||
microphonePatcher = new MicrophonePatcher().patch();
|
||||
addSettingsPanelButton({ name: PluginInfo.PLUGIN_NAME, icon: MicrophoneSettingsIcon, tooltipText: "Microphone Settings", onClick: openMicrophoneSettingsModal });
|
||||
},
|
||||
|
||||
stop() {
|
||||
microphonePatcher?.unpatch();
|
||||
Emitter.removeAllListeners(PluginInfo.PLUGIN_NAME);
|
||||
removeSettingsPanelButton(PluginInfo.PLUGIN_NAME);
|
||||
},
|
||||
|
||||
// settings,
|
||||
});
|
||||
|
||||
export { microphonePatcher };
|
||||
23
src/equicordplugins/betterMicrophone.desktop/logger/index.ts
Normal file
23
src/equicordplugins/betterMicrophone.desktop/logger/index.ts
Normal 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);
|
||||
|
|
@ -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} />;
|
||||
});
|
||||
|
|
@ -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";
|
||||
|
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
* 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",
|
||||
// @ts-ignore
|
||||
"connection",
|
||||
connectionEventFunction,
|
||||
PluginInfo.PLUGIN_NAME
|
||||
);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public unpatch(): this {
|
||||
return this._unpatch();
|
||||
}
|
||||
}
|
||||
19
src/equicordplugins/betterMicrophone.desktop/stores/index.ts
Normal file
19
src/equicordplugins/betterMicrophone.desktop/stores/index.ts
Normal 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";
|
||||
|
|
@ -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)
|
||||
)
|
||||
);
|
||||
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
|
@ -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";
|
||||
|
|
@ -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;
|
||||
19
src/equicordplugins/betterScreenshare.dev/constants/index.ts
Normal file
19
src/equicordplugins/betterScreenshare.dev/constants/index.ts
Normal 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";
|
||||
71
src/equicordplugins/betterScreenshare.dev/index.tsx
Normal file
71
src/equicordplugins/betterScreenshare.dev/index.tsx
Normal 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 { Devs } from "@utils/constants";
|
||||
import definePlugin 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";
|
||||
|
||||
var screensharePatcher;
|
||||
var screenshareAudioPatcher;
|
||||
|
||||
export default definePlugin({
|
||||
name: "PhilsBetterScreenshare",
|
||||
description: "This plugin allows you to further customize your screen sharing.",
|
||||
authors: [Devs.philhk, Devs.walrus],
|
||||
dependencies: ["PhilsPluginLibrary"],
|
||||
replacedScreenshareModalComponent: replacedScreenshareModalComponent,
|
||||
|
||||
patches: [
|
||||
{
|
||||
find: "Messages.SCREENSHARE_RELAUNCH",
|
||||
replacement: {
|
||||
match: /(function .{1,2}\(.{1,2}\){)(.{1,40}(?=selectGuild).+?(?:]}\)}\)))(})/,
|
||||
replace: "$1return $self.replacedScreenshareModalComponent(function(){$2}, this, arguments)$3"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
start() {
|
||||
initScreenshareStore();
|
||||
initScreenshareAudioStore();
|
||||
screensharePatcher = new ScreensharePatcher().patch();
|
||||
screenshareAudioPatcher = new ScreenshareAudioPatcher().patch();
|
||||
|
||||
addSettingsPanelButton({
|
||||
name: PluginInfo.PLUGIN_NAME,
|
||||
icon: ScreenshareSettingsIcon,
|
||||
tooltipText: "Screenshare Settings",
|
||||
onClick: openScreenshareModal
|
||||
});
|
||||
},
|
||||
|
||||
stop() {
|
||||
screensharePatcher?.unpatch();
|
||||
screenshareAudioPatcher?.unpatch();
|
||||
Emitter.removeAllListeners(PluginInfo.PLUGIN_NAME);
|
||||
removeSettingsPanelButton(PluginInfo.PLUGIN_NAME);
|
||||
},
|
||||
});
|
||||
|
||||
export { screenshareAudioPatcher, screensharePatcher };
|
||||
23
src/equicordplugins/betterScreenshare.dev/logger/index.ts
Normal file
23
src/equicordplugins/betterScreenshare.dev/logger/index.ts
Normal 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);
|
||||
55
src/equicordplugins/betterScreenshare.dev/modals/index.tsx
Normal file
55
src/equicordplugins/betterScreenshare.dev/modals/index.tsx
Normal 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} />;
|
||||
});
|
||||
20
src/equicordplugins/betterScreenshare.dev/patchers/index.ts
Normal file
20
src/equicordplugins/betterScreenshare.dev/patchers/index.ts
Normal 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";
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
19
src/equicordplugins/betterScreenshare.dev/patches/index.ts
Normal file
19
src/equicordplugins/betterScreenshare.dev/patches/index.ts
Normal 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";
|
||||
|
|
@ -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;
|
||||
}
|
||||
20
src/equicordplugins/betterScreenshare.dev/stores/index.ts
Normal file
20
src/equicordplugins/betterScreenshare.dev/stores/index.ts
Normal 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";
|
||||
|
|
@ -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 };
|
||||
|
|
@ -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)
|
||||
)
|
||||
);
|
||||
195
src/equicordplugins/commandPalette/commands.tsx
Normal file
195
src/equicordplugins/commandPalette/commands.tsx
Normal file
|
|
@ -0,0 +1,195 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2024 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { showNotification } from "@api/Notifications";
|
||||
import { Settings } from "@api/Settings";
|
||||
import { relaunch, showItemInFolder } from "@utils/native";
|
||||
import { checkForUpdates, getRepo } from "@utils/updater";
|
||||
import { Clipboard, GuildStore, NavigationRouter, SettingsRouter, Toasts } from "@webpack/common";
|
||||
|
||||
import gitHash from "~git-hash";
|
||||
import gitRemote from "~git-remote";
|
||||
import Plugins from "~plugins";
|
||||
|
||||
import { openMultipleChoice } from "./components/MultipleChoice";
|
||||
import { openSimpleTextInput } from "./components/TextInput";
|
||||
|
||||
export interface ButtonAction {
|
||||
id: string;
|
||||
label: string;
|
||||
callback?: () => void;
|
||||
registrar?: string;
|
||||
}
|
||||
|
||||
export const actions: ButtonAction[] = [
|
||||
{ id: "openSuncordSettings", label: "Open Suncord tab", callback: async () => await SettingsRouter.open("SuncordSettings"), registrar: "Suncord" },
|
||||
{ id: "openPluginSettings", label: "Open Plugin tab", callback: () => SettingsRouter.open("SuncordPlugins"), registrar: "Suncord" },
|
||||
{ id: "openThemesSettings", label: "Open Themes tab", callback: () => SettingsRouter.open("SuncordThemes"), registrar: "Suncord" },
|
||||
{ id: "openUpdaterSettings", label: "Open Updater tab", callback: () => SettingsRouter.open("SuncordUpdater"), registrar: "Suncord" },
|
||||
{ id: "openSuncordCloudSettings", label: "Open Cloud tab", callback: () => SettingsRouter.open("SuncordCloud"), registrar: "Suncord" },
|
||||
{ id: "openBackupSettings", label: "Open Backup & Restore tab", callback: () => SettingsRouter.open("SuncordSettingsSync"), registrar: "Suncord" },
|
||||
{ id: "restartClient", label: "Restart Client", callback: () => relaunch(), registrar: "Suncord" },
|
||||
{ id: "openQuickCSSFile", label: "Open Quick CSS File", callback: () => VencordNative.quickCss.openEditor(), registrar: "Suncord" },
|
||||
{ id: "openSettingsFolder", label: "Open Settings Folder", callback: async () => showItemInFolder(await VencordNative.settings.getSettingsDir()), registrar: "Suncord" },
|
||||
{ id: "openInGithub", label: "Open in Github", callback: async () => VencordNative.native.openExternal(await getRepo()), registrar: "Suncord" },
|
||||
|
||||
{
|
||||
id: "openInBrowser", label: "Open in Browser", callback: async () => {
|
||||
const url = await openSimpleTextInput("Enter a URL");
|
||||
const newUrl = url.replace(/(https?:\/\/)?([a-zA-Z0-9-]+)\.([a-zA-Z0-9-]+)/, "https://$2.$3");
|
||||
|
||||
try {
|
||||
new URL(newUrl); // Throws if invalid
|
||||
VencordNative.native.openExternal(newUrl);
|
||||
} catch {
|
||||
Toasts.show({
|
||||
message: "Invalid URL",
|
||||
type: Toasts.Type.FAILURE,
|
||||
id: Toasts.genId(),
|
||||
options: {
|
||||
position: Toasts.Position.BOTTOM
|
||||
}
|
||||
});
|
||||
}
|
||||
}, registrar: "Suncord"
|
||||
},
|
||||
|
||||
{
|
||||
id: "togglePlugin", label: "Toggle Plugin", callback: async () => {
|
||||
const plugins = Object.keys(Plugins);
|
||||
const options: ButtonAction[] = [];
|
||||
|
||||
for (const plugin of plugins) {
|
||||
options.push({
|
||||
id: plugin,
|
||||
label: plugin
|
||||
});
|
||||
}
|
||||
|
||||
const choice = await openMultipleChoice(options);
|
||||
|
||||
const enabled = await openMultipleChoice([
|
||||
{ id: "enable", label: "Enable" },
|
||||
{ id: "disable", label: "Disable" }
|
||||
]);
|
||||
|
||||
if (choice && enabled) {
|
||||
return togglePlugin(choice, enabled.id === "enable");
|
||||
}
|
||||
}, registrar: "Suncord"
|
||||
},
|
||||
|
||||
{
|
||||
id: "quickFetch", label: "Quick Fetch", callback: async () => {
|
||||
try {
|
||||
const url = await openSimpleTextInput("Enter URL to fetch (GET only)");
|
||||
const newUrl = url.replace(/(https?:\/\/)?([a-zA-Z0-9-]+)\.([a-zA-Z0-9-]+)/, "https://$2.$3");
|
||||
const res = (await fetch(newUrl));
|
||||
const text = await res.text();
|
||||
Clipboard.copy(text);
|
||||
|
||||
Toasts.show({
|
||||
message: "Copied response to clipboard!",
|
||||
type: Toasts.Type.SUCCESS,
|
||||
id: Toasts.genId(),
|
||||
options: {
|
||||
position: Toasts.Position.BOTTOM
|
||||
}
|
||||
});
|
||||
|
||||
} catch (e) {
|
||||
Toasts.show({
|
||||
message: "Issue fetching URL",
|
||||
type: Toasts.Type.FAILURE,
|
||||
id: Toasts.genId(),
|
||||
options: {
|
||||
position: Toasts.Position.BOTTOM
|
||||
}
|
||||
});
|
||||
}
|
||||
}, registrar: "Suncord"
|
||||
},
|
||||
|
||||
{
|
||||
id: "copyGitInfo", label: "Copy Git Info", callback: async () => {
|
||||
Clipboard.copy(`gitHash: ${gitHash}\ngitRemote: ${gitRemote}`);
|
||||
|
||||
Toasts.show({
|
||||
message: "Copied git info to clipboard!",
|
||||
type: Toasts.Type.SUCCESS,
|
||||
id: Toasts.genId(),
|
||||
options: {
|
||||
position: Toasts.Position.BOTTOM
|
||||
}
|
||||
});
|
||||
}, registrar: "Suncord"
|
||||
},
|
||||
|
||||
{
|
||||
id: "checkForUpdates", label: "Check for Updates", callback: async () => {
|
||||
const isOutdated = await checkForUpdates();
|
||||
|
||||
if (isOutdated) {
|
||||
setTimeout(() => showNotification({
|
||||
title: "A Suncord update is available!",
|
||||
body: "Click here to view the update",
|
||||
permanent: true,
|
||||
noPersist: true,
|
||||
onClick() {
|
||||
SettingsRouter.open("SuncordUpdater");
|
||||
}
|
||||
}), 10_000);
|
||||
} else {
|
||||
Toasts.show({
|
||||
message: "No updates available",
|
||||
type: Toasts.Type.MESSAGE,
|
||||
id: Toasts.genId(),
|
||||
options: {
|
||||
position: Toasts.Position.BOTTOM
|
||||
}
|
||||
});
|
||||
}
|
||||
}, registrar: "Suncord"
|
||||
},
|
||||
|
||||
{
|
||||
id: "navToServer", label: "Navigate to Server", callback: async () => {
|
||||
const allServers = Object.values(GuildStore.getGuilds());
|
||||
const options: ButtonAction[] = [];
|
||||
|
||||
for (const server of allServers) {
|
||||
options.push({
|
||||
id: server.id,
|
||||
label: server.name
|
||||
});
|
||||
}
|
||||
|
||||
const choice = await openMultipleChoice(options);
|
||||
|
||||
if (choice) {
|
||||
NavigationRouter.transitionToGuild(choice.id);
|
||||
}
|
||||
}, registrar: "Suncord"
|
||||
}
|
||||
];
|
||||
|
||||
function togglePlugin(plugin: ButtonAction, enabled: boolean) {
|
||||
|
||||
Settings.plugins[plugin.id].enabled = enabled;
|
||||
|
||||
Toasts.show({
|
||||
message: `Successfully ${enabled ? "enabled" : "disabled"} ${plugin.id}`,
|
||||
type: Toasts.Type.SUCCESS,
|
||||
id: Toasts.genId(),
|
||||
options: {
|
||||
position: Toasts.Position.BOTTOM
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function registerAction(action: ButtonAction) {
|
||||
actions.push(action);
|
||||
}
|
||||
130
src/equicordplugins/commandPalette/components/CommandPalette.tsx
Normal file
130
src/equicordplugins/commandPalette/components/CommandPalette.tsx
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2024 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import "./style.css";
|
||||
|
||||
import { classNameFactory } from "@api/Styles";
|
||||
import { Logger } from "@utils/Logger";
|
||||
import { closeAllModals, ModalRoot, ModalSize, openModal } from "@utils/modal";
|
||||
import { React, TextInput, useEffect, useState } from "@webpack/common";
|
||||
|
||||
import { settings } from "..";
|
||||
import { actions } from "../commands";
|
||||
|
||||
const logger = new Logger("CommandPalette", "#e5c890");
|
||||
|
||||
export function CommandPalette({ modalProps }) {
|
||||
const cl = classNameFactory("vc-command-palette-");
|
||||
const [focusedIndex, setFocusedIndex] = useState<number | null>(null);
|
||||
const [startIndex, setStartIndex] = useState(0);
|
||||
|
||||
const allowMouse = settings.store.allowMouseControl;
|
||||
|
||||
const sortedActions = actions.slice().sort((a, b) => a.label.localeCompare(b.label));
|
||||
|
||||
const [queryEh, setQuery] = useState("");
|
||||
|
||||
const filteredActions = sortedActions.filter(
|
||||
action => action.label.toLowerCase().includes(queryEh.toLowerCase())
|
||||
);
|
||||
|
||||
const visibleActions = filteredActions.slice(startIndex, startIndex + 20);
|
||||
|
||||
const totalActions = filteredActions.length;
|
||||
|
||||
const handleWheel = (e: React.WheelEvent) => {
|
||||
if (allowMouse && filteredActions.length > 20) {
|
||||
if (e.deltaY > 0) {
|
||||
setStartIndex(prev => Math.min(prev + 2, filteredActions.length - 20));
|
||||
} else {
|
||||
setStartIndex(prev => Math.max(prev - 2, 0));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleButtonClick = (actionId: string, index: number) => {
|
||||
const selectedAction = filteredActions.find(action => action.id === actionId);
|
||||
|
||||
if (selectedAction) {
|
||||
logger.log(`${selectedAction.id}'s action was triggered.`);
|
||||
}
|
||||
|
||||
closeAllModals();
|
||||
|
||||
selectedAction?.callback?.();
|
||||
setFocusedIndex(index);
|
||||
};
|
||||
|
||||
const handleKeyDown = (e: React.KeyboardEvent) => {
|
||||
const currentIndex = focusedIndex !== null ? focusedIndex : -1;
|
||||
let nextIndex;
|
||||
|
||||
switch (e.key) {
|
||||
case "ArrowUp":
|
||||
e.preventDefault();
|
||||
nextIndex = currentIndex > 0 ? currentIndex - 1 : visibleActions.length - 1;
|
||||
setFocusedIndex(nextIndex);
|
||||
|
||||
if (currentIndex === 0 && totalActions > 20) {
|
||||
setStartIndex(prev => Math.max(prev - 1, 0));
|
||||
setFocusedIndex(0);
|
||||
}
|
||||
|
||||
break;
|
||||
case "ArrowDown":
|
||||
e.preventDefault();
|
||||
nextIndex = currentIndex < visibleActions.length - 1 ? currentIndex + 1 : 0;
|
||||
setFocusedIndex(nextIndex);
|
||||
|
||||
if (currentIndex === visibleActions.length - 1 && totalActions > 20) {
|
||||
setStartIndex(prev => Math.min(prev + 1, filteredActions.length - 20));
|
||||
setFocusedIndex(19);
|
||||
}
|
||||
break;
|
||||
case "Enter":
|
||||
if (currentIndex !== null && currentIndex >= 0 && currentIndex < visibleActions.length) {
|
||||
handleButtonClick(visibleActions[currentIndex].id, currentIndex);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setFocusedIndex(0);
|
||||
setStartIndex(0);
|
||||
}, [queryEh]);
|
||||
|
||||
return (
|
||||
<ModalRoot className={cl("root")} {...modalProps} size={ModalSize.MEDIUM} onKeyDown={handleKeyDown} onWheel={handleWheel}>
|
||||
<div>
|
||||
<TextInput
|
||||
value={queryEh}
|
||||
onChange={e => setQuery(e)}
|
||||
style={{ width: "100%", borderBottomLeftRadius: "0", borderBottomRightRadius: "0", paddingLeft: "0.9rem" }}
|
||||
placeholder="Search the Command Palette"
|
||||
/>
|
||||
<div className={cl("option-container")}>
|
||||
{visibleActions.map((action, index) => (
|
||||
<button
|
||||
key={action.id}
|
||||
className={cl("option", { "key-hover": index === focusedIndex })}
|
||||
onClick={() => { if (allowMouse) handleButtonClick(action.id, index); }}
|
||||
onMouseMove={() => { if (allowMouse) setFocusedIndex(index); }}
|
||||
style={allowMouse ? { cursor: "pointer" } : { cursor: "default" }}
|
||||
>
|
||||
{action.label}
|
||||
{action.registrar && <span className={cl("registrar")}>{action.registrar}</span>}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</ModalRoot>
|
||||
);
|
||||
}
|
||||
|
||||
export const openCommandPalette = () => openModal(modalProps => <CommandPalette modalProps={modalProps} />);
|
||||
144
src/equicordplugins/commandPalette/components/MultipleChoice.tsx
Normal file
144
src/equicordplugins/commandPalette/components/MultipleChoice.tsx
Normal file
|
|
@ -0,0 +1,144 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2024 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import "./style.css";
|
||||
|
||||
import { classNameFactory } from "@api/Styles";
|
||||
import { closeAllModals, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal";
|
||||
import { React, TextInput, useEffect, useState } from "@webpack/common";
|
||||
|
||||
import { settings } from "..";
|
||||
import { ButtonAction } from "../commands";
|
||||
|
||||
interface MultipleChoiceProps {
|
||||
modalProps: ModalProps;
|
||||
onSelect: (selectedValue: any) => void;
|
||||
choices: ButtonAction[];
|
||||
}
|
||||
|
||||
export function MultipleChoice({ modalProps, onSelect, choices }: MultipleChoiceProps) {
|
||||
const cl = classNameFactory("vc-command-palette-");
|
||||
const [queryEh, setQuery] = useState("");
|
||||
const [focusedIndex, setFocusedIndex] = useState<number | null>(null);
|
||||
const [startIndex, setStartIndex] = useState(0);
|
||||
|
||||
const allowMouse = settings.store.allowMouseControl;
|
||||
|
||||
const sortedActions = choices.slice().sort((a, b) => a.label.localeCompare(b.label));
|
||||
|
||||
const filteredActions = sortedActions.filter(
|
||||
action => action.label.toLowerCase().includes(queryEh.toLowerCase())
|
||||
);
|
||||
|
||||
|
||||
const visibleActions = filteredActions.slice(startIndex, startIndex + 20);
|
||||
|
||||
const totalActions = filteredActions.length;
|
||||
|
||||
const handleButtonClick = (actionId: string, index: number) => {
|
||||
const selectedAction = filteredActions.find(action => action.id === actionId);
|
||||
|
||||
if (selectedAction) {
|
||||
onSelect(selectedAction);
|
||||
}
|
||||
|
||||
closeAllModals();
|
||||
};
|
||||
|
||||
const handleKeyDown = (e: React.KeyboardEvent) => {
|
||||
const currentIndex = focusedIndex !== null ? focusedIndex : -1;
|
||||
let nextIndex;
|
||||
|
||||
switch (e.key) {
|
||||
case "ArrowUp":
|
||||
e.preventDefault();
|
||||
nextIndex = currentIndex > 0 ? currentIndex - 1 : visibleActions.length - 1;
|
||||
setFocusedIndex(nextIndex);
|
||||
|
||||
if (currentIndex === 0 && totalActions > 20) {
|
||||
setStartIndex(prev => Math.max(prev - 1, 0));
|
||||
setFocusedIndex(0);
|
||||
}
|
||||
|
||||
break;
|
||||
case "ArrowDown":
|
||||
e.preventDefault();
|
||||
nextIndex = currentIndex < visibleActions.length - 1 ? currentIndex + 1 : 0;
|
||||
setFocusedIndex(nextIndex);
|
||||
|
||||
if (currentIndex === visibleActions.length - 1 && totalActions > 20) {
|
||||
setStartIndex(prev => Math.min(prev + 1, filteredActions.length - 20));
|
||||
setFocusedIndex(19);
|
||||
}
|
||||
break;
|
||||
case "Enter":
|
||||
if (currentIndex !== null && currentIndex >= 0 && currentIndex < visibleActions.length) {
|
||||
handleButtonClick(visibleActions[currentIndex].id, currentIndex);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
const handleWheel = (e: React.WheelEvent) => {
|
||||
if (allowMouse && filteredActions.length > 20) {
|
||||
if (e.deltaY > 0) {
|
||||
setStartIndex(prev => Math.min(prev + 2, filteredActions.length - 20));
|
||||
} else {
|
||||
setStartIndex(prev => Math.max(prev - 2, 0));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setFocusedIndex(0);
|
||||
setStartIndex(0);
|
||||
}, [queryEh]);
|
||||
|
||||
return (
|
||||
// @ts-ignore
|
||||
<ModalRoot className={cl("root")} {...modalProps} size={ModalSize.MEDIUM} onKeyDown={handleKeyDown} onWheel={handleWheel}>
|
||||
<div>
|
||||
<TextInput
|
||||
value={queryEh}
|
||||
onChange={e => setQuery(e)}
|
||||
style={{ width: "100%", borderBottomLeftRadius: "0", borderBottomRightRadius: "0", paddingLeft: "0.9rem" }}
|
||||
placeholder="Search the Command Palette"
|
||||
/>
|
||||
<div className={cl("option-container")}>
|
||||
{visibleActions.map((action, index) => (
|
||||
<button
|
||||
key={action.id}
|
||||
className={cl("option", { "key-hover": index === focusedIndex })}
|
||||
onClick={() => { if (allowMouse) handleButtonClick(action.id, index); }}
|
||||
onMouseMove={() => { if (allowMouse) setFocusedIndex(index); }}
|
||||
style={allowMouse ? { cursor: "pointer" } : { cursor: "default" }}
|
||||
>
|
||||
{action.label}
|
||||
{action.registrar && <span className={cl("registrar")}>{action.registrar}</span>}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</ModalRoot>
|
||||
);
|
||||
}
|
||||
|
||||
export function openMultipleChoice(choices: ButtonAction[]): Promise<ButtonAction> {
|
||||
return new Promise(resolve => {
|
||||
openModal(modalProps => (
|
||||
<MultipleChoice
|
||||
modalProps={modalProps}
|
||||
onSelect={selectedValue => {
|
||||
closeAllModals();
|
||||
resolve(selectedValue);
|
||||
}}
|
||||
choices={choices}
|
||||
/>
|
||||
));
|
||||
});
|
||||
}
|
||||
63
src/equicordplugins/commandPalette/components/TextInput.tsx
Normal file
63
src/equicordplugins/commandPalette/components/TextInput.tsx
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2024 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import "./style.css";
|
||||
|
||||
import { closeAllModals, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal";
|
||||
import { React, TextInput, useEffect, useState } from "@webpack/common";
|
||||
|
||||
interface SimpleTextInputProps {
|
||||
modalProps: ModalProps;
|
||||
onSelect: (inputValue: string) => void;
|
||||
placeholder?: string;
|
||||
info?: string;
|
||||
}
|
||||
|
||||
export function SimpleTextInput({ modalProps, onSelect, placeholder, info }: SimpleTextInputProps) {
|
||||
const [inputValue, setInputValue] = useState("");
|
||||
|
||||
const handleKeyDown = (e: React.KeyboardEvent) => {
|
||||
switch (e.key) {
|
||||
case "Enter":
|
||||
onSelect(inputValue);
|
||||
closeAllModals();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setInputValue("");
|
||||
}, []);
|
||||
|
||||
return (
|
||||
// @ts-ignore
|
||||
<ModalRoot className="vc-command-palette-simple-text" {...modalProps} size={ModalSize.DYNAMIC} onKeyDown={handleKeyDown}>
|
||||
<TextInput
|
||||
value={inputValue}
|
||||
onChange={e => setInputValue(e as unknown as string)}
|
||||
style={{ width: "30vw", borderRadius: "5px" }}
|
||||
placeholder={placeholder ?? "Type and press Enter"}
|
||||
/>
|
||||
{info && <div className="vc-command-palette-textinfo">{info}</div>}
|
||||
</ModalRoot>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
export function openSimpleTextInput(placeholder?: string, info?: string): Promise<string> {
|
||||
return new Promise(resolve => {
|
||||
openModal(modalProps => (
|
||||
<SimpleTextInput
|
||||
modalProps={modalProps}
|
||||
onSelect={inputValue => resolve(inputValue)}
|
||||
placeholder={placeholder}
|
||||
info={info}
|
||||
/>
|
||||
));
|
||||
});
|
||||
}
|
||||
112
src/equicordplugins/commandPalette/components/style.css
Normal file
112
src/equicordplugins/commandPalette/components/style.css
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
/* cl = vc-command-palette- */
|
||||
|
||||
.vc-command-palette-root {
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
background-color: var(--background-tertiary);
|
||||
}
|
||||
|
||||
.vc-command-palette-option {
|
||||
padding: 5px;
|
||||
background-color: var(--background-tertiary);
|
||||
color: var(--white-500);
|
||||
font-family: var(--font-display);
|
||||
text-align: left;
|
||||
padding-left: 0.8rem;
|
||||
}
|
||||
|
||||
.vc-command-palette-key-hover {
|
||||
padding: 5px;
|
||||
background-color: var(--background-modifier-selected);
|
||||
border-radius: 3px;
|
||||
font-family: var(--font-display);
|
||||
color: var(--interactive-hover);
|
||||
padding-left: 0.8rem;
|
||||
}
|
||||
|
||||
.vc-command-palette-option-container {
|
||||
display: grid;
|
||||
gap: 2px;
|
||||
margin-left: 0.8rem;
|
||||
margin-right: 0.8rem;
|
||||
}
|
||||
|
||||
.vc-command-palette-textinfo {
|
||||
font-family: var(--font-display);
|
||||
color: var(--white-500);
|
||||
margin-left: 0.8rem;
|
||||
margin-right: 0.8rem;
|
||||
padding: 0.8rem 0;
|
||||
}
|
||||
|
||||
.vc-command-palette-simple-text {
|
||||
background-color: var(--input-background);
|
||||
width: 30vh;
|
||||
}
|
||||
|
||||
.vc-command-palette-registrar {
|
||||
position: absolute;
|
||||
color: var(--interactive-normal);
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
right: 1.6rem;
|
||||
}
|
||||
|
||||
.vc-command-palette-key-recorder-container {
|
||||
position: relative;
|
||||
display: block;
|
||||
cursor: pointer;
|
||||
height: 40px;
|
||||
width: 20rem;
|
||||
}
|
||||
|
||||
.vc-command-palette-key-recorder {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border-radius: 3px;
|
||||
background-color: hsl(var(--black-500-hsl) / 10%);
|
||||
color: var(--header-primary);
|
||||
line-height: 22px;
|
||||
font-weight: 600;
|
||||
padding: 10px 0 10px 10px;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
border: 1px solid;
|
||||
border-color: hsl(var(--black-500-hsl) / 30%);
|
||||
transition: border.15s ease;
|
||||
user-select: none;
|
||||
font-family: var(--font-primary);
|
||||
}
|
||||
|
||||
.vc-command-palette-recording {
|
||||
animation: shadowPulse_b16790 1s ease-in infinite;
|
||||
box-shadow: 0 0 6px hsl(var(--red-400-hsl) / 30%);
|
||||
border-color: hsl(var(--red-400-hsl) / 30%);
|
||||
color: var(--status-danger);
|
||||
}
|
||||
|
||||
.vc-command-palette-key-recorder:hover {
|
||||
border-color: hsl(var(--red-400-hsl) / 30%);
|
||||
}
|
||||
|
||||
.vc-command-palette-recording-button {
|
||||
color: var(--status-danger) !important;
|
||||
background-color: hsl(var(--red-400-hsl) / 10%) !important;
|
||||
opacity: 1;
|
||||
transition: opacity.2s ease-in-out, transform.2s ease-in-out;
|
||||
}
|
||||
|
||||
.vc-command-palette-key-recorder-button {
|
||||
position: absolute;
|
||||
right: 1rem;
|
||||
height: 30px;
|
||||
width: 128px;
|
||||
color: var(--white-500);
|
||||
background-color: var(--button-secondary-background);
|
||||
border-radius: 10px;
|
||||
transition: background-color 0.15s ease;
|
||||
}
|
||||
|
||||
.vc-command-palette-key-recorder-button:hover {
|
||||
background-color: var(--button-secondary-background-hover);
|
||||
}
|
||||
144
src/equicordplugins/commandPalette/index.tsx
Normal file
144
src/equicordplugins/commandPalette/index.tsx
Normal file
|
|
@ -0,0 +1,144 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2024 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import { classNameFactory } from "@api/Styles";
|
||||
import { Devs } from "@utils/constants";
|
||||
import { closeAllModals } from "@utils/modal";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import { SettingsRouter, useState } from "@webpack/common";
|
||||
|
||||
import { registerAction } from "./commands";
|
||||
import { openCommandPalette } from "./components/CommandPalette";
|
||||
|
||||
const cl = classNameFactory("vc-command-palette-");
|
||||
let isRecordingGlobal: boolean = false;
|
||||
|
||||
export const settings = definePluginSettings({
|
||||
hotkey: {
|
||||
description: "The hotkey to open the command palette.",
|
||||
type: OptionType.COMPONENT,
|
||||
default: ["Control", "Shift", "P"],
|
||||
component: () => {
|
||||
const [isRecording, setIsRecording] = useState(false);
|
||||
|
||||
const recordKeybind = (setIsRecording: (value: boolean) => void) => {
|
||||
const keys: Set<string> = new Set();
|
||||
const keyLists: string[][] = [];
|
||||
|
||||
setIsRecording(true);
|
||||
isRecordingGlobal = true;
|
||||
|
||||
const updateKeys = () => {
|
||||
if (keys.size === 0 || !document.querySelector(`.${cl("key-recorder-button")}`)) {
|
||||
const longestArray = keyLists.reduce((a, b) => a.length > b.length ? a : b);
|
||||
if (longestArray.length > 0) {
|
||||
settings.store.hotkey = longestArray.map(key => key.toLowerCase());
|
||||
}
|
||||
setIsRecording(false);
|
||||
isRecordingGlobal = false;
|
||||
document.removeEventListener("keydown", keydownListener);
|
||||
document.removeEventListener("keyup", keyupListener);
|
||||
}
|
||||
keyLists.push(Array.from(keys));
|
||||
};
|
||||
|
||||
const keydownListener = (e: KeyboardEvent) => {
|
||||
const { key } = e;
|
||||
if (!keys.has(key)) {
|
||||
keys.add(key);
|
||||
}
|
||||
updateKeys();
|
||||
};
|
||||
|
||||
const keyupListener = (e: KeyboardEvent) => {
|
||||
keys.delete(e.key);
|
||||
updateKeys();
|
||||
};
|
||||
|
||||
document.addEventListener("keydown", keydownListener);
|
||||
document.addEventListener("keyup", keyupListener);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={cl("key-recorder-container")} onClick={() => recordKeybind(setIsRecording)}>
|
||||
<div className={`${cl("key-recorder")} ${isRecording ? cl("recording") : ""}`}>
|
||||
{settings.store.hotkey.map(word => word.charAt(0).toUpperCase() + word.slice(1)).join(" + ")}
|
||||
<button className={`${cl("key-recorder-button")} ${isRecording ? cl("recording-button") : ""}`} disabled={isRecording}>
|
||||
{isRecording ? "Recording..." : "Record keybind"}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
},
|
||||
allowMouseControl: {
|
||||
description: "Allow the mouse to control the command palette.",
|
||||
type: OptionType.BOOLEAN,
|
||||
default: true
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
export default definePlugin({
|
||||
name: "CommandPalette",
|
||||
description: "Allows you to navigate the UI with a keyboard.",
|
||||
authors: [Devs.Ethan],
|
||||
settings,
|
||||
|
||||
start() {
|
||||
document.addEventListener("keydown", this.event);
|
||||
|
||||
if (IS_DEV) {
|
||||
registerAction({
|
||||
id: "openDevSettings",
|
||||
label: "Open Dev tab",
|
||||
callback: () => SettingsRouter.open("SuncordPatchHelper"),
|
||||
registrar: "Suncord"
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
stop() {
|
||||
document.removeEventListener("keydown", this.event);
|
||||
},
|
||||
|
||||
|
||||
event(e: KeyboardEvent) {
|
||||
|
||||
enum Modifiers {
|
||||
control = "ctrlKey",
|
||||
shift = "shiftKey",
|
||||
alt = "altKey",
|
||||
meta = "metaKey"
|
||||
}
|
||||
|
||||
const { hotkey } = settings.store;
|
||||
const pressedKey = e.key.toLowerCase();
|
||||
|
||||
if (isRecordingGlobal) return;
|
||||
|
||||
for (let i = 0; i < hotkey.length; i++) {
|
||||
const lowercasedRequiredKey = hotkey[i].toLowerCase();
|
||||
|
||||
if (lowercasedRequiredKey in Modifiers && !e[Modifiers[lowercasedRequiredKey]]) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(lowercasedRequiredKey in Modifiers) && pressedKey !== lowercasedRequiredKey) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
closeAllModals();
|
||||
|
||||
if (document.querySelector(`.${cl("root")}`)) return;
|
||||
|
||||
openCommandPalette();
|
||||
}
|
||||
});
|
||||
114
src/equicordplugins/mediaDownloader.desktop/DependencyModal.tsx
Normal file
114
src/equicordplugins/mediaDownloader.desktop/DependencyModal.tsx
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2024 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { closeModal, ModalCloseButton, ModalContent, ModalHeader, ModalProps, ModalRoot, ModalSize } from "@utils/modal";
|
||||
import { Button, Text, useEffect, useState } from "@webpack/common";
|
||||
|
||||
export function DependencyModal({ props, options: { key, checkytdlp, checkffmpeg } }: {
|
||||
props: ModalProps;
|
||||
options: {
|
||||
key: string;
|
||||
checkytdlp: () => Promise<boolean>;
|
||||
checkffmpeg: () => Promise<boolean>;
|
||||
};
|
||||
}) {
|
||||
const checking = <span>Checking...</span>;
|
||||
const installed = <span style={{ color: "green" }}>Installed!</span>;
|
||||
const notInstalled = (color: string) => <span style={{ color }}>Not installed.</span>;
|
||||
|
||||
const [ytdlpStatus, setYtdlpStatus] = useState(checking);
|
||||
const [ffmpegStatus, setFfmpegStatus] = useState(checking);
|
||||
|
||||
useEffect(() => {
|
||||
checkytdlp().then(v => v ? setYtdlpStatus(installed) : setYtdlpStatus(notInstalled("red")));
|
||||
checkffmpeg().then(v => v ? setFfmpegStatus(installed) : setFfmpegStatus(notInstalled("yellow")));
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<ModalRoot {...props} size={ModalSize.MEDIUM}>
|
||||
<ModalHeader>
|
||||
<Text variant="heading-lg/semibold" style={{ flexGrow: 1 }}>MediaDownloader: Missing dependencies</Text>
|
||||
<ModalCloseButton onClick={() => closeModal(key)} />
|
||||
</ModalHeader>
|
||||
<ModalContent>
|
||||
<div style={{ padding: "16px 0" }}>
|
||||
<Text variant="text-md/normal" >
|
||||
The MediaDownloader plugin requires a working version of yt-dlp to be installed on your system. For extra features such as higher quality videos and gifs, you can also optionally install ffmpeg.
|
||||
<br /><br />
|
||||
If you don't know how to install yt-dlp or ffmpeg, check the installation guides below (if you <i>do</i> know how to install it, make sure it's in your PATH).
|
||||
<br /><br />
|
||||
<strong>Note:</strong> You may need to completely restart Discord after installing yt-dlp or ffmpeg for the plugin to detect them.
|
||||
<br /><br />
|
||||
<ul style={{ listStyleType: "disc", marginLeft: "1rem" }}>
|
||||
<li>
|
||||
<a href="https://github.com/yt-dlp/yt-dlp/wiki/Installation#windows" target="_blank" rel="noreferrer">yt-dlp installation guide</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://phoenixnap.com/kb/ffmpeg-windows" target="_blank" rel="noreferrer">Unofficial ffmpeg installation guide</a>
|
||||
</li>
|
||||
</ul>
|
||||
</Text>
|
||||
<div style={{
|
||||
marginTop: "16px",
|
||||
display: "grid",
|
||||
gridTemplateColumns: "repeat(2, 1fr)",
|
||||
gridTemplateRows: "repeat(2, 1fr)",
|
||||
columnGap: "16px",
|
||||
rowGap: "8px"
|
||||
}}>
|
||||
<div
|
||||
style={{
|
||||
gridArea: "1 / 1 / 2 / 2",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center"
|
||||
}}
|
||||
>
|
||||
<Text variant="text-md/bold">
|
||||
yt-dlp status: {ytdlpStatus}
|
||||
</Text>
|
||||
</div>
|
||||
<Button
|
||||
onClick={async () => {
|
||||
setYtdlpStatus(checking);
|
||||
setYtdlpStatus(await checkytdlp()
|
||||
? installed
|
||||
: notInstalled("red"));
|
||||
}}
|
||||
style={{ gridArea: "1 / 2 / 2 / 3" }}
|
||||
>
|
||||
Check again
|
||||
</Button>
|
||||
<div
|
||||
style={{
|
||||
gridArea: "2 / 1 / 3 / 2",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center"
|
||||
|
||||
}}
|
||||
>
|
||||
<Text variant="text-md/bold">
|
||||
ffmpeg status: {ffmpegStatus}
|
||||
</Text>
|
||||
</div>
|
||||
<Button
|
||||
onClick={async () => {
|
||||
setFfmpegStatus(checking);
|
||||
setFfmpegStatus(await checkffmpeg()
|
||||
? installed
|
||||
: notInstalled("yellow"));
|
||||
}}
|
||||
style={{ gridArea: "2 / 2 / 3 / 3" }}
|
||||
>
|
||||
Check again
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</ModalContent>
|
||||
</ModalRoot>
|
||||
);
|
||||
}
|
||||
333
src/equicordplugins/mediaDownloader.desktop/index.tsx
Normal file
333
src/equicordplugins/mediaDownloader.desktop/index.tsx
Normal file
|
|
@ -0,0 +1,333 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2024 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import "./style.css";
|
||||
|
||||
import { ApplicationCommandInputType, ApplicationCommandOptionType, findOption, sendBotMessage } from "@api/Commands";
|
||||
import * as DataStore from "@api/DataStore";
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import ErrorBoundary from "@components/ErrorBoundary";
|
||||
import { Link } from "@components/Link";
|
||||
import { EquicordDevs } from "@utils/constants";
|
||||
import { Logger } from "@utils/Logger";
|
||||
import { openModal } from "@utils/modal";
|
||||
import definePlugin, { OptionType, PluginNative, ReporterTestable } from "@utils/types";
|
||||
import { Button, DraftType, FluxDispatcher, Forms, UploadHandler, UploadManager, UserStore } from "@webpack/common";
|
||||
import { Channel } from "discord-types/general";
|
||||
|
||||
import { DependencyModal } from "./DependencyModal";
|
||||
|
||||
type ButtonComponent = {
|
||||
customId?: string;
|
||||
disabled?: boolean;
|
||||
emoji?: {
|
||||
animated?: boolean | string;
|
||||
id?: string;
|
||||
name?: string;
|
||||
src?: string;
|
||||
};
|
||||
id: string;
|
||||
label?: string;
|
||||
style: number;
|
||||
type: number;
|
||||
url?: string;
|
||||
};
|
||||
|
||||
const Native = VencordNative.pluginHelpers.MediaDownloader as PluginNative<typeof import("./native")>;
|
||||
const logger = new Logger("MediaDownloader", "#ff0b01");
|
||||
|
||||
const maxFileSize = () => {
|
||||
const premiumType = (UserStore.getCurrentUser().premiumType ?? 0);
|
||||
if (premiumType > 1) return 500000000; // 500MB
|
||||
if (premiumType > 0) return 50000000; // 50MB
|
||||
return 25000000; // 25MB
|
||||
};
|
||||
/** Takes a string and splits it into an array of arguments. */
|
||||
const argParse = (args: string): string[] => args.match(
|
||||
/(?:[^\s"']+|"([^"\\]*(?:\\.[^"\\]*)*)"|'([^'\\]*(?:\\.[^'\\]*)*)'|([^\s]+))/g
|
||||
) ?? [];
|
||||
|
||||
function mimetype(extension: "mp4" | "webm" | "gif" | "mp3" | string) {
|
||||
switch (extension) {
|
||||
case "mp4":
|
||||
return "video/mp4";
|
||||
case "webm":
|
||||
return "video/webm";
|
||||
case "gif":
|
||||
return "image/gif";
|
||||
case "mp3":
|
||||
return "audio/mp3";
|
||||
default:
|
||||
return "application/octet-stream";
|
||||
}
|
||||
}
|
||||
|
||||
const CancelButton = [{
|
||||
components: [{
|
||||
customId: "media-downloader-stop-download", // ! for some reason customId is always undefined, so I'm just saving the id in the emoji animated field :3
|
||||
emoji: {
|
||||
name: "⚪",
|
||||
animated: "media-downloader-stop-download"
|
||||
},
|
||||
label: "Cancel download",
|
||||
id: "0,0",
|
||||
style: 4,
|
||||
type: 2,
|
||||
}], id: "0", type: 1
|
||||
}];
|
||||
async function sendProgress(channelId: string, promise: Promise<{
|
||||
buffer: Buffer;
|
||||
title: string;
|
||||
logs: string;
|
||||
} | {
|
||||
error: string;
|
||||
logs: string;
|
||||
}>) {
|
||||
if (!settings.store.showProgress) {
|
||||
sendBotMessage(channelId, {
|
||||
components: CancelButton
|
||||
});
|
||||
return await promise;
|
||||
}
|
||||
const clydeMessage = sendBotMessage(channelId, {
|
||||
content: "Downloading video...",
|
||||
components: CancelButton
|
||||
});
|
||||
const updateMessage = (stdout: string, append?: string) => {
|
||||
const text = stdout.toString();
|
||||
FluxDispatcher.dispatch({
|
||||
type: "MESSAGE_UPDATE",
|
||||
message: {
|
||||
...clydeMessage,
|
||||
content: `Downloading video...\n\`\`\`\n${text}\n\`\`\`${append || ""}`,
|
||||
components: append ? [] : clydeMessage.components
|
||||
}
|
||||
});
|
||||
};
|
||||
// Hacky way to send info from native to renderer for progress updates
|
||||
const id = setInterval(async () => {
|
||||
const stdout = await Native.getStdout();
|
||||
updateMessage(stdout);
|
||||
}, 500);
|
||||
|
||||
const data = await promise;
|
||||
clearInterval(id);
|
||||
const stdout = await Native.getStdout();
|
||||
updateMessage(stdout, "error" in data ? "Error!" : "Done!");
|
||||
return data;
|
||||
}
|
||||
|
||||
function sendFfmpegWarning(channelId: string) {
|
||||
sendBotMessage(channelId, {
|
||||
content: "FFmpeg not detected. You may experience lower download quality and missing features."
|
||||
});
|
||||
}
|
||||
|
||||
// Mostly taken from viewRaw and betterSessions plugins.
|
||||
async function openDependencyModal() {
|
||||
const key = openModal(props => (
|
||||
<ErrorBoundary>
|
||||
<DependencyModal props={props} options={{
|
||||
key,
|
||||
checkffmpeg: Native.checkffmpeg,
|
||||
checkytdlp: Native.checkytdlp,
|
||||
}} />
|
||||
</ErrorBoundary>
|
||||
));
|
||||
}
|
||||
|
||||
const settings = definePluginSettings({
|
||||
supportedWebsites: {
|
||||
description: "See the link for a list of supported websites.",
|
||||
type: OptionType.COMPONENT,
|
||||
default: "none",
|
||||
component: () => (
|
||||
<>
|
||||
<Forms.FormText>
|
||||
<Link href="https://github.com/yt-dlp/yt-dlp/blob/master/supportedsites.md" className="media-downloader-link">
|
||||
<Button role="link" style={{ width: "100%" }}>
|
||||
Click to see supported websites.
|
||||
</Button>
|
||||
</Link>
|
||||
</Forms.FormText>
|
||||
<Forms.FormDivider />
|
||||
</>
|
||||
)
|
||||
},
|
||||
showProgress: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Send a Clyde message with the download progress.",
|
||||
default: true,
|
||||
},
|
||||
showFfmpegWarning: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Show a warning message if ffmpeg is not installed.",
|
||||
default: true,
|
||||
},
|
||||
defaultGifQuality: {
|
||||
type: OptionType.NUMBER,
|
||||
description: "The quality level to use if no value is specified when downloading gifs. A number between 1 and 5.",
|
||||
default: 3,
|
||||
},
|
||||
ytdlpArgs: {
|
||||
type: OptionType.STRING,
|
||||
description: "Additional arguments to pass to yt-dlp. This may overwrite default plugin arguments such format selection. Note: if modifying the ouptup, ensure the filename starts with `download`.",
|
||||
placeholder: "--format bestvideo+bestaudio",
|
||||
},
|
||||
ffmpegArgs: {
|
||||
type: OptionType.STRING,
|
||||
description: "Additional arguments to pass to ffmpeg. This may overwrite default plugin arguments such as auto-scaling. Note: if modifying the output, ensure the filename starts with `remux`.",
|
||||
placeholder: "-vf scale=1280:720",
|
||||
}
|
||||
}, {
|
||||
defaultGifQuality: {
|
||||
isValid(value) {
|
||||
return value >= 1 && value <= 5;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export default definePlugin({
|
||||
name: "MediaDownloader",
|
||||
description: "Download and send videos with from YouTube, Twitter, Reddit and more.",
|
||||
authors: [EquicordDevs.Colorman],
|
||||
dependencies: ["CommandsAPI"],
|
||||
reporterTestable: ReporterTestable.Patches,
|
||||
settings,
|
||||
commands: [{
|
||||
inputType: ApplicationCommandInputType.BUILT_IN,
|
||||
name: "download",
|
||||
description: "Download and send videos, audio or gifs.",
|
||||
options: [{
|
||||
name: "url",
|
||||
description: "The URL of any video supported by yt-dlp.",
|
||||
required: true,
|
||||
type: ApplicationCommandOptionType.STRING
|
||||
}, {
|
||||
name: "format",
|
||||
description: "Whether to download a video or audio.",
|
||||
type: ApplicationCommandOptionType.STRING,
|
||||
choices: [
|
||||
{ name: "Video", value: "video", label: "Video" },
|
||||
{ name: "Audio", value: "audio", label: "Audio" },
|
||||
{ name: "GIF", value: "gif", label: "GIF" }
|
||||
],
|
||||
required: false,
|
||||
}, {
|
||||
name: "gif_quality",
|
||||
type: ApplicationCommandOptionType.INTEGER,
|
||||
description: "The quality level when using GIF. Try lowering this number if the GIF is too large.",
|
||||
required: false,
|
||||
choices: [
|
||||
{ name: "5", value: "5", label: "5" },
|
||||
{ name: "4", value: "4", label: "4" },
|
||||
{ name: "3", value: "3", label: "3" },
|
||||
{ name: "2", value: "2", label: "2" },
|
||||
{ name: "1", value: "1", label: "1" }
|
||||
]
|
||||
}, {
|
||||
name: "yt-dlp_args",
|
||||
description: "Additional arguments to pass to yt-dlp. These will take precedence over arguments set in the settings. This may overwrite default plugin arguments such format selection. Note: if modifying the output, ensure the filename starts with `download`.",
|
||||
required: false,
|
||||
type: ApplicationCommandOptionType.STRING
|
||||
}, {
|
||||
name: "ffmpeg_args",
|
||||
description: "Additional arguments to pass to ffmpeg. These will take precedence over arguments set in the settings. This may overwrite default plugin arguments such as auto-scaling. Note: if modifying the output, ensure the filename starts with `remux`.",
|
||||
required: false,
|
||||
type: ApplicationCommandOptionType.STRING
|
||||
}],
|
||||
|
||||
execute: async (args, ctx) => {
|
||||
if (!await Native.isYtdlpAvailable()) return openDependencyModal();
|
||||
if (!await Native.isFfmpegAvailable() && settings.store.showFfmpegWarning) sendFfmpegWarning(ctx.channel.id);
|
||||
|
||||
const url = findOption<string>(args, "url", "");
|
||||
const format = findOption<"video" | "audio" | "gif">(args, "format", "video");
|
||||
const gifQuality = parseInt(findOption<string>(args, "gif_quality", settings.store.defaultGifQuality.toString())) as 1 | 2 | 3 | 4 | 5;
|
||||
const ytdlpArgs = findOption<string>(args, "yt-dlp_args", "");
|
||||
const ffmpegArgs = findOption<string>(args, "ffmpeg_args", "");
|
||||
|
||||
return await download(ctx.channel, {
|
||||
url,
|
||||
format,
|
||||
gifQuality,
|
||||
ytdlpArgs,
|
||||
ffmpegArgs
|
||||
});
|
||||
}
|
||||
}],
|
||||
patches: [
|
||||
{
|
||||
find: "missing validator for this component",
|
||||
replacement: {
|
||||
match: /(\i)(\.type\)\{case \i\.\i\.BUTTON):return null;/,
|
||||
replace: "$1$2:return ($self.handleButtonClick($1),null);"
|
||||
}
|
||||
}
|
||||
],
|
||||
handleButtonClick: (buttonComponent: ButtonComponent) => {
|
||||
if (!(buttonComponent.emoji?.animated === "media-downloader-stop-download")) return;
|
||||
Native.interrupt();
|
||||
},
|
||||
start: async () => {
|
||||
await Native.checkytdlp();
|
||||
await Native.checkffmpeg();
|
||||
|
||||
const videoDir = await DataStore.get<string>("media-downloader-video-dir");
|
||||
const newVideoDir = await Native.start(videoDir);
|
||||
await DataStore.set("media-downloader-video-dir", newVideoDir);
|
||||
},
|
||||
stop: async () => {
|
||||
// Clean up the temp files
|
||||
await Native.stop();
|
||||
await DataStore.del("media-downloader-video-dir");
|
||||
}
|
||||
});
|
||||
|
||||
async function download(channel: Channel, {
|
||||
url, format, ytdlpArgs, ffmpegArgs, gifQuality
|
||||
}: {
|
||||
url: string;
|
||||
format: "video" | "audio" | "gif";
|
||||
ytdlpArgs: string;
|
||||
ffmpegArgs: string;
|
||||
gifQuality: 1 | 2 | 3 | 4 | 5;
|
||||
}) {
|
||||
const promise = Native.execute({
|
||||
url,
|
||||
format,
|
||||
gifQuality,
|
||||
ytdlpArgs: [
|
||||
...argParse(settings.store.ytdlpArgs || ""),
|
||||
...argParse(ytdlpArgs)
|
||||
],
|
||||
ffmpegArgs: [
|
||||
...argParse(settings.store.ffmpegArgs || ""),
|
||||
...argParse(ffmpegArgs)
|
||||
],
|
||||
maxFileSize: maxFileSize()
|
||||
});
|
||||
|
||||
const data = await sendProgress(channel.id, promise);
|
||||
|
||||
for (const log of data.logs.trim().split("\n")) logger.info(log);
|
||||
|
||||
if ("error" in data) {
|
||||
// Open the modal if the error is due to missing formats (could be fixed by downloading ffmpeg)
|
||||
if (data.error.includes("--list-formats") && !(await Native.isFfmpegAvailable()))
|
||||
return sendBotMessage(channel.id, { content: "No good streams found. Consider installing ffmpeg to increase the likelihood of a successful stream." }), openDependencyModal();
|
||||
|
||||
return sendBotMessage(channel.id, {
|
||||
content: `Failed to download video: ${data.error.includes("\n") ? "\n```" + data.error + "\n```" : `\`${data.error}\``}`
|
||||
});
|
||||
}
|
||||
|
||||
const { buffer, title } = data;
|
||||
UploadManager.clearAll(channel.id, DraftType.SlashCommand);
|
||||
const file = new File([buffer], title, { type: mimetype(title.split(".")[1]) });
|
||||
// See petpet
|
||||
setTimeout(() => UploadHandler.promptToUpload([file], channel, DraftType.ChannelMessage), 10);
|
||||
}
|
||||
300
src/equicordplugins/mediaDownloader.desktop/native.ts
Normal file
300
src/equicordplugins/mediaDownloader.desktop/native.ts
Normal file
|
|
@ -0,0 +1,300 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2024 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { ChildProcessWithoutNullStreams, execFileSync, spawn } from "child_process";
|
||||
import { IpcMainInvokeEvent } from "electron";
|
||||
import * as fs from "fs";
|
||||
import os from "os";
|
||||
import path from "path";
|
||||
|
||||
type Format = "video" | "audio" | "gif";
|
||||
type DownloadOptions = {
|
||||
url: string;
|
||||
format?: Format;
|
||||
gifQuality?: 1 | 2 | 3 | 4 | 5;
|
||||
ytdlpArgs?: string[];
|
||||
ffmpegArgs?: string[];
|
||||
maxFileSize?: number;
|
||||
};
|
||||
|
||||
let workdir: string | null = null;
|
||||
let stdout_global: string = "";
|
||||
let logs_global: string = "";
|
||||
|
||||
let ytdlpAvailable = false;
|
||||
let ffmpegAvailable = false;
|
||||
|
||||
let ytdlpProcess: ChildProcessWithoutNullStreams | null = null;
|
||||
let ffmpegProcess: ChildProcessWithoutNullStreams | null = null;
|
||||
|
||||
const getdir = () => workdir ?? process.cwd();
|
||||
const p = (file: string) => path.join(getdir(), file);
|
||||
const cleanVideoFiles = () => {
|
||||
if (!workdir) return;
|
||||
fs.readdirSync(workdir)
|
||||
.filter(f => f.startsWith("download.") || f.startsWith("remux."))
|
||||
.forEach(f => fs.unlinkSync(p(f)));
|
||||
};
|
||||
const appendOut = (data: string) => ( // Makes carriage return (\r) work
|
||||
(stdout_global += data), (stdout_global = stdout_global.replace(/^.*\r([^\n])/gm, "$1")));
|
||||
const log = (...data: string[]) => (console.log(`[Plugin:MediaDownloader] ${data.join(" ")}`), logs_global += `[Plugin:MediaDownloader] ${data.join(" ")}\n`);
|
||||
const error = (...data: string[]) => console.error(`[Plugin:MediaDownloader] [ERROR] ${data.join(" ")}`);
|
||||
|
||||
function ytdlp(args: string[]): Promise<string> {
|
||||
log(`Executing yt-dlp with args: ["${args.map(a => a.replace('"', '\\"')).join('", "')}"]`);
|
||||
let errorMsg = "";
|
||||
|
||||
return new Promise<string>((resolve, reject) => {
|
||||
ytdlpProcess = spawn("yt-dlp", args, {
|
||||
cwd: getdir(),
|
||||
});
|
||||
|
||||
ytdlpProcess.stdout.on("data", data => appendOut(data));
|
||||
ytdlpProcess.stderr.on("data", data => {
|
||||
appendOut(data);
|
||||
error(`yt-dlp encountered an error: ${data}`);
|
||||
errorMsg += data;
|
||||
});
|
||||
ytdlpProcess.on("exit", code => {
|
||||
ytdlpProcess = null;
|
||||
code === 0 ? resolve(stdout_global) : reject(new Error(errorMsg || `yt-dlp exited with code ${code}`));
|
||||
});
|
||||
});
|
||||
}
|
||||
function ffmpeg(args: string[]): Promise<string> {
|
||||
log(`Executing ffmpeg with args: ["${args.map(a => a.replace('"', '\\"')).join('", "')}"]`);
|
||||
let errorMsg = "";
|
||||
|
||||
return new Promise<string>((resolve, reject) => {
|
||||
ffmpegProcess = spawn("ffmpeg", args, {
|
||||
cwd: getdir(),
|
||||
});
|
||||
|
||||
ffmpegProcess.stdout.on("data", data => appendOut(data));
|
||||
ffmpegProcess.stderr.on("data", data => {
|
||||
appendOut(data);
|
||||
error(`ffmpeg encountered an error: ${data}`);
|
||||
errorMsg += data;
|
||||
});
|
||||
ffmpegProcess.on("exit", code => {
|
||||
ffmpegProcess = null;
|
||||
code === 0 ? resolve(stdout_global) : reject(new Error(errorMsg || `ffmpeg exited with code ${code}`));
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
export async function start(_: IpcMainInvokeEvent, _workdir: string | undefined) {
|
||||
_workdir ||= fs.mkdtempSync(path.join(os.tmpdir(), "vencord_mediaDownloader_"));
|
||||
if (!fs.existsSync(_workdir)) fs.mkdirSync(_workdir, { recursive: true });
|
||||
workdir = _workdir;
|
||||
log("Using workdir: ", workdir);
|
||||
return workdir;
|
||||
}
|
||||
export async function stop(_: IpcMainInvokeEvent) {
|
||||
if (workdir) {
|
||||
log("Cleaning up workdir");
|
||||
fs.rmSync(workdir, { recursive: true });
|
||||
workdir = null;
|
||||
}
|
||||
}
|
||||
|
||||
async function metadata(options: DownloadOptions) {
|
||||
stdout_global = "";
|
||||
const metadata = JSON.parse(await ytdlp(["-J", options.url, "--no-warnings"]));
|
||||
if (metadata.is_live) throw "Live streams are not supported.";
|
||||
stdout_global = "";
|
||||
return { videoTitle: `${metadata.title || "video"} (${metadata.id})` };
|
||||
}
|
||||
function genFormat({ videoTitle }: { videoTitle: string; }, { maxFileSize, format }: DownloadOptions) {
|
||||
const HAS_LIMIT = !!maxFileSize;
|
||||
const MAX_VIDEO_SIZE = HAS_LIMIT ? maxFileSize * 0.8 : 0;
|
||||
const MAX_AUDIO_SIZE = HAS_LIMIT ? maxFileSize * 0.2 : 0;
|
||||
|
||||
const audio = {
|
||||
noFfmpeg: "ba[ext=mp3]{TOT_SIZE}/wa[ext=mp3]{TOT_SIZE}",
|
||||
ffmpeg: "ba*{TOT_SIZE}/ba{TOT_SIZE}/wa*{TOT_SIZE}/ba*"
|
||||
};
|
||||
const video = {
|
||||
noFfmpeg: "b{TOT_SIZE}{HEIGHT}[ext=webm]/b{TOT_SIZE}{HEIGHT}[ext=mp4]/w{HEIGHT}{TOT_SIZE}",
|
||||
ffmpeg: "b*{VID_SIZE}{HEIGHT}+ba{AUD_SIZE}/b{TOT_SIZE}{HEIGHT}/b*{HEIGHT}+ba",
|
||||
};
|
||||
const gif = {
|
||||
ffmpeg: "bv{TOT_SIZE}/wv{TOT_SIZE}"
|
||||
};
|
||||
|
||||
let format_group: { noFfmpeg?: string; ffmpeg: string; };
|
||||
switch (format) {
|
||||
case "audio":
|
||||
format_group = audio;
|
||||
break;
|
||||
case "gif":
|
||||
format_group = gif;
|
||||
break;
|
||||
case "video":
|
||||
default:
|
||||
format_group = video;
|
||||
break;
|
||||
}
|
||||
|
||||
const format_string = (ffmpegAvailable ? format_group.ffmpeg : format_group.noFfmpeg)
|
||||
?.replaceAll("{TOT_SIZE}", HAS_LIMIT ? `[filesize<${maxFileSize}]` : "")
|
||||
.replaceAll("{VID_SIZE}", HAS_LIMIT ? `[filesize<${MAX_VIDEO_SIZE}]` : "")
|
||||
.replaceAll("{AUD_SIZE}", HAS_LIMIT ? `[filesize<${MAX_AUDIO_SIZE}]` : "")
|
||||
.replaceAll("{HEIGHT}", "[height<=1080]");
|
||||
if (!format_string) throw "Gif format is only supported with ffmpeg.";
|
||||
log("Video formated calculated as ", format_string);
|
||||
log(`Based on: format=${format}, maxFileSize=${maxFileSize}, ffmpegAvailable=${ffmpegAvailable}`);
|
||||
return { format: format_string, videoTitle };
|
||||
}
|
||||
async function download({ format, videoTitle }: { format: string; videoTitle: string; }, { ytdlpArgs, url, format: usrFormat }: DownloadOptions) {
|
||||
cleanVideoFiles();
|
||||
const baseArgs = ["-f", format, "-o", "download.%(ext)s", "--force-overwrites", "-I", "1"];
|
||||
const remuxArgs = ffmpegAvailable
|
||||
? usrFormat === "video"
|
||||
? ["--remux-video", "webm>webm/mp4"]
|
||||
: usrFormat === "audio"
|
||||
? ["--extract-audio", "--audio-format", "mp3"]
|
||||
: []
|
||||
: [];
|
||||
const customArgs = ytdlpArgs?.filter(Boolean) || [];
|
||||
|
||||
await ytdlp([url, ...baseArgs, ...remuxArgs, ...customArgs]);
|
||||
const file = fs.readdirSync(getdir()).find(f => f.startsWith("download."));
|
||||
if (!file) throw "No video file was found!";
|
||||
return { file, videoTitle };
|
||||
}
|
||||
async function remux({ file, videoTitle }: { file: string; videoTitle: string; }, { ffmpegArgs, format, maxFileSize, gifQuality }: DownloadOptions) {
|
||||
const sourceExtension = file.split(".").pop();
|
||||
if (!ffmpegAvailable) return log("Skipping remux, ffmpeg is unavailable."), { file, videoTitle, extension: sourceExtension };
|
||||
|
||||
// We only really need to remux if
|
||||
// 1. The file is too big
|
||||
// 2. The file is in a format not supported by discord
|
||||
// 3. The user provided custom ffmpeg arguments
|
||||
// 4. The target format is gif
|
||||
const acceptableFormats = ["mp3", "mp4", "webm"];
|
||||
const fileSize = fs.statSync(p(file)).size;
|
||||
const customArgs = ffmpegArgs?.filter(Boolean) || [];
|
||||
|
||||
const isFormatAcceptable = acceptableFormats.includes(sourceExtension ?? "");
|
||||
const isFileSizeAcceptable = (!maxFileSize || fileSize <= maxFileSize);
|
||||
const hasCustomArgs = customArgs.length > 0;
|
||||
const isGif = format === "gif";
|
||||
if (isFormatAcceptable && isFileSizeAcceptable && !hasCustomArgs && !isGif)
|
||||
return log("Skipping remux, file type and size are good, and no ffmpeg arguments were specified."), { file, videoTitle, extension: sourceExtension };
|
||||
|
||||
const duration = parseFloat(execFileSync("ffprobe", ["-v", "error", "-show_entries", "format=duration", "-of", "default=noprint_wrappers=1:nokey=1", p(file)]).toString());
|
||||
if (isNaN(duration)) throw "Failed to get video duration.";
|
||||
// ffmpeg tends to go above the target size, so I'm setting it to 7/8
|
||||
const targetBits = maxFileSize ? (maxFileSize * 7) / duration : 9999999;
|
||||
const kilobits = ~~(targetBits / 1024);
|
||||
|
||||
let baseArgs: string[];
|
||||
let ext: string;
|
||||
switch (format) {
|
||||
case "audio":
|
||||
baseArgs = ["-i", p(file), "-b:a", `${kilobits}k`, "-maxrate", `${kilobits}k`, "-bufsize", "1M", "-y"];
|
||||
ext = "mp3";
|
||||
break;
|
||||
case "video":
|
||||
default:
|
||||
// Dynamically resize based on target bitrate
|
||||
const height = kilobits <= 100 ? 480 : kilobits <= 500 ? 720 : 1080;
|
||||
baseArgs = ["-i", p(file), "-b:v", `${~~(kilobits * 0.8)}k`, "-b:a", `${~~(kilobits * 0.2)}k`, "-maxrate", `${kilobits}k`, "-bufsize", "1M", "-y", "-filter:v", `scale=-1:${height}`];
|
||||
ext = "mp4";
|
||||
break;
|
||||
case "gif":
|
||||
let fps: number, width: number, colors: number, bayer_scale: number;
|
||||
// WARNING: these parameters have been arbitrarily chosen, optimization is welcome!
|
||||
switch (gifQuality) {
|
||||
case 1:
|
||||
fps = 5, width = 360, colors = 24, bayer_scale = 5;
|
||||
break;
|
||||
case 2:
|
||||
fps = 10, width = 420, colors = 32, bayer_scale = 5;
|
||||
break;
|
||||
default:
|
||||
case 3:
|
||||
fps = 15, width = 480, colors = 64, bayer_scale = 4;
|
||||
break;
|
||||
case 4:
|
||||
fps = 20, width = 540, colors = 64, bayer_scale = 3;
|
||||
break;
|
||||
case 5:
|
||||
fps = 30, width = 720, colors = 128, bayer_scale = 1;
|
||||
break;
|
||||
}
|
||||
|
||||
baseArgs = ["-i", p(file), "-vf", `fps=${fps},scale=w=${width}:h=-1:flags=lanczos,mpdecimate,split[s0][s1];[s0]palettegen=max_colors=${colors}[p];[s1][p]paletteuse=dither=bayer:bayer_scale=${bayer_scale}`, "-loop", "0", "-bufsize", "1M", "-y"];
|
||||
ext = "gif";
|
||||
break;
|
||||
}
|
||||
|
||||
await ffmpeg([...baseArgs, ...customArgs, `remux.${ext}`]);
|
||||
return { file: `remux.${ext}`, videoTitle, extension: ext };
|
||||
}
|
||||
function upload({ file, videoTitle, extension }: { file: string; videoTitle: string; extension: string | undefined; }) {
|
||||
if (!extension) throw "Invalid extension.";
|
||||
const buffer = fs.readFileSync(p(file));
|
||||
return { buffer, title: `${videoTitle}.${extension}` };
|
||||
}
|
||||
export async function execute(
|
||||
_: IpcMainInvokeEvent,
|
||||
opt: DownloadOptions
|
||||
): Promise<{
|
||||
buffer: Buffer;
|
||||
title: string;
|
||||
logs: string;
|
||||
} | {
|
||||
error: string;
|
||||
logs: string;
|
||||
}> {
|
||||
logs_global = "";
|
||||
try {
|
||||
const videoMetadata = await metadata(opt);
|
||||
const videoFormat = genFormat(videoMetadata, opt);
|
||||
const videoDownload = await download(videoFormat, opt);
|
||||
const videoRemux = await remux(videoDownload, opt);
|
||||
const videoUpload = upload(videoRemux);
|
||||
return { logs: logs_global, ...videoUpload };
|
||||
} catch (e: any) {
|
||||
return { error: e.toString(), logs: logs_global };
|
||||
}
|
||||
}
|
||||
|
||||
export function checkffmpeg(_?: IpcMainInvokeEvent) {
|
||||
try {
|
||||
execFileSync("ffmpeg", ["-version"]);
|
||||
execFileSync("ffprobe", ["-version"]);
|
||||
ffmpegAvailable = true;
|
||||
return true;
|
||||
} catch (e) {
|
||||
ffmpegAvailable = false;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
export async function checkytdlp(_?: IpcMainInvokeEvent) {
|
||||
try {
|
||||
execFileSync("yt-dlp", ["--version"]);
|
||||
ytdlpAvailable = true;
|
||||
return true;
|
||||
} catch (e) {
|
||||
ytdlpAvailable = false;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export async function interrupt(_: IpcMainInvokeEvent) {
|
||||
log("Interrupting...");
|
||||
ytdlpProcess?.kill();
|
||||
ffmpegProcess?.kill();
|
||||
cleanVideoFiles();
|
||||
}
|
||||
|
||||
export const getStdout = () => stdout_global;
|
||||
export const isYtdlpAvailable = () => ytdlpAvailable;
|
||||
export const isFfmpegAvailable = () => ffmpegAvailable;
|
||||
9
src/equicordplugins/mediaDownloader.desktop/style.css
Normal file
9
src/equicordplugins/mediaDownloader.desktop/style.css
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
/* Only way to make on:hover styles */
|
||||
|
||||
.media-downloader-link {
|
||||
text-decoration: none !important;
|
||||
}
|
||||
|
||||
.media-downloader-link:hover {
|
||||
text-decoration: none !important;
|
||||
}
|
||||
|
|
@ -11,47 +11,45 @@ import { User } from "discord-types/general";
|
|||
|
||||
let user: ModifiedUser | undefined;
|
||||
let lastUserId: string | undefined;
|
||||
let uninject: (() => void) | undefined;
|
||||
|
||||
interface ModifiedUser extends User {
|
||||
_realPremiumType?: number;
|
||||
}
|
||||
|
||||
const onChange = () => {
|
||||
const newUser = UserStore.getCurrentUser();
|
||||
if (newUser && newUser.id !== lastUserId) {
|
||||
user = newUser;
|
||||
ready(user);
|
||||
}
|
||||
};
|
||||
|
||||
function ready(user: ModifiedUser) {
|
||||
if (!user) return;
|
||||
if ("_realPremiumType" in user) return;
|
||||
|
||||
user._realPremiumType = user.premiumType ?? 0;
|
||||
user.premiumType = 2;
|
||||
lastUserId = user.id;
|
||||
}
|
||||
|
||||
export default definePlugin({
|
||||
name: "NoNitroUpsell",
|
||||
description: "Removes ALL of Discord's nitro upsells by tricking the client into thinking you have nitro.",
|
||||
authors: [EquicordDevs.thororen],
|
||||
ready(user: ModifiedUser): void {
|
||||
if (!user) return;
|
||||
if ("_realPremiumType" in user) return;
|
||||
|
||||
user._realPremiumType = user.premiumType ?? 0;
|
||||
user.premiumType = 2;
|
||||
lastUserId = user.id;
|
||||
},
|
||||
start(): void {
|
||||
start() {
|
||||
user = UserStore.getCurrentUser();
|
||||
if (user) this.ready(user);
|
||||
|
||||
const onChange = (): void => {
|
||||
const newUser = UserStore.getCurrentUser();
|
||||
if (newUser && newUser.id !== lastUserId) {
|
||||
user = newUser;
|
||||
this.ready(user);
|
||||
}
|
||||
};
|
||||
if (user) ready(user);
|
||||
|
||||
UserStore.addChangeListener(onChange);
|
||||
uninject = () => UserStore.removeChangeListener(onChange);
|
||||
},
|
||||
stop(): void {
|
||||
uninject?.();
|
||||
stop() {
|
||||
const user = UserStore.getCurrentUser();
|
||||
if (!user) return;
|
||||
if (!("_realPremiumType" in user)) return;
|
||||
UserStore?.getCurrentUser()?.premiumType;
|
||||
// @ts-ignore
|
||||
user.premiumType = user._realPremiumType;
|
||||
delete user._realPremiumType;
|
||||
UserStore.removeChangeListener(onChange);
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
|
@ -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} />
|
||||
);
|
||||
};
|
||||
|
|
@ -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} />
|
||||
);
|
||||
};
|
||||
|
||||
|
|
@ -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 >
|
||||
);
|
||||
};
|
||||
|
|
@ -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} />
|
||||
);
|
||||
};
|
||||
|
|
@ -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} />
|
||||
);
|
||||
};
|
||||
|
|
@ -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";
|
||||
23
src/equicordplugins/philsPluginLibrary/components/index.tsx
Normal file
23
src/equicordplugins/philsPluginLibrary/components/index.tsx
Normal 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";
|
||||
|
|
@ -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 >
|
||||
);
|
||||
};
|
||||
|
|
@ -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 >
|
||||
);
|
||||
};
|
||||
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
|
@ -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";
|
||||
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
|
@ -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} />
|
||||
);
|
||||
};
|
||||
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
|
@ -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";
|
||||
|
|
@ -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;
|
||||
19
src/equicordplugins/philsPluginLibrary/constants/index.ts
Normal file
19
src/equicordplugins/philsPluginLibrary/constants/index.ts
Normal 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";
|
||||
|
|
@ -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);
|
||||
|
|
@ -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"));
|
||||
|
|
@ -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";
|
||||
|
|
@ -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);
|
||||
|
|
@ -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);
|
||||
86
src/equicordplugins/philsPluginLibrary/emitter/emitter.ts
Normal file
86
src/equicordplugins/philsPluginLibrary/emitter/emitter.ts
Normal 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)
|
||||
);
|
||||
}
|
||||
}
|
||||
19
src/equicordplugins/philsPluginLibrary/emitter/index.ts
Normal file
19
src/equicordplugins/philsPluginLibrary/emitter/index.ts
Normal 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";
|
||||
142
src/equicordplugins/philsPluginLibrary/icons/index.tsx
Normal file
142
src/equicordplugins/philsPluginLibrary/icons/index.tsx
Normal 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>;
|
||||
50
src/equicordplugins/philsPluginLibrary/index.tsx
Normal file
50
src/equicordplugins/philsPluginLibrary/index.tsx
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* 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 definePlugin from "@utils/types";
|
||||
|
||||
import { replacedUserPanelComponent } from "./patches";
|
||||
|
||||
export default definePlugin({
|
||||
name: "PhilsPluginLibrary",
|
||||
description: "A library for phil's plugins",
|
||||
authors: [Devs.philhk],
|
||||
replacedUserPanelComponent: replacedUserPanelComponent,
|
||||
|
||||
patches: [
|
||||
{
|
||||
find: "Messages.ACCOUNT_A11Y_LABEL",
|
||||
replacement: {
|
||||
match: /(?<=function)( .{0,8}(?={).)(.{0,1000}isFullscreenInContext\(\).+?\)]}\))(})/,
|
||||
replace: "$1return $self.replacedUserPanelComponent(function(){$2}, this, arguments)$3"
|
||||
}
|
||||
}
|
||||
],
|
||||
});
|
||||
|
||||
|
||||
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";
|
||||
19
src/equicordplugins/philsPluginLibrary/patchers/index.ts
Normal file
19
src/equicordplugins/philsPluginLibrary/patchers/index.ts
Normal 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";
|
||||
29
src/equicordplugins/philsPluginLibrary/patchers/patcher.ts
Normal file
29
src/equicordplugins/philsPluginLibrary/patchers/patcher.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
88
src/equicordplugins/philsPluginLibrary/patches/audio.ts
Normal file
88
src/equicordplugins/philsPluginLibrary/patches/audio.ts
Normal 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 };
|
||||
}
|
||||
21
src/equicordplugins/philsPluginLibrary/patches/index.ts
Normal file
21
src/equicordplugins/philsPluginLibrary/patches/index.ts
Normal 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";
|
||||
107
src/equicordplugins/philsPluginLibrary/patches/userPanel.tsx
Normal file
107
src/equicordplugins/philsPluginLibrary/patches/userPanel.tsx
Normal 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));
|
||||
}
|
||||
253
src/equicordplugins/philsPluginLibrary/patches/video.ts
Normal file
253
src/equicordplugins/philsPluginLibrary/patches/video.ts
Normal 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.dev/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,
|
||||
};
|
||||
}
|
||||
20
src/equicordplugins/philsPluginLibrary/store/index.ts
Normal file
20
src/equicordplugins/philsPluginLibrary/store/index.ts
Normal 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";
|
||||
85
src/equicordplugins/philsPluginLibrary/store/profileable.ts
Normal file
85
src/equicordplugins/philsPluginLibrary/store/profileable.ts
Normal 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)
|
||||
});
|
||||
}
|
||||
102
src/equicordplugins/philsPluginLibrary/store/store.ts
Normal file
102
src/equicordplugins/philsPluginLibrary/store/store.ts
Normal 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
|
||||
};
|
||||
}
|
||||
19
src/equicordplugins/philsPluginLibrary/styles/index.ts
Normal file
19
src/equicordplugins/philsPluginLibrary/styles/index.ts
Normal 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";
|
||||
25
src/equicordplugins/philsPluginLibrary/styles/styles.ts
Normal file
25
src/equicordplugins/philsPluginLibrary/styles/styles.ts
Normal 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>;
|
||||
46
src/equicordplugins/philsPluginLibrary/types/common/index.ts
Normal file
46
src/equicordplugins/philsPluginLibrary/types/common/index.ts
Normal 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]>;
|
||||
};
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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";
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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";
|
||||
|
|
@ -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>;
|
||||
|
|
@ -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";
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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];
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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";
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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[];
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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";
|
||||
|
|
@ -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;
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue