Make all plugins use new settings API

This commit is contained in:
Nuckyz 2024-06-12 23:56:02 -03:00
parent aa4c6e5496
commit 74bd89b721
No known key found for this signature in database
GPG key ID: 440BF8296E1C4AD9
20 changed files with 349 additions and 353 deletions

View file

@ -250,7 +250,7 @@ function ThemesTab() {
Edit QuickCSS Edit QuickCSS
</Button> </Button>
{Vencord.Settings.plugins.ClientTheme.enabled && ( {Vencord.Plugins.isPluginEnabled("ClientTheme") && (
<Button <Button
onClick={() => openModal(modalProps => ( onClick={() => openModal(modalProps => (
<PluginModal <PluginModal

View file

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { Settings } from "@api/Settings"; import { definePluginSettings } from "@api/Settings";
import BackupAndRestoreTab from "@components/VencordSettings/BackupAndRestoreTab"; import BackupAndRestoreTab from "@components/VencordSettings/BackupAndRestoreTab";
import CloudTab from "@components/VencordSettings/CloudTab"; import CloudTab from "@components/VencordSettings/CloudTab";
import PatchHelperTab from "@components/VencordSettings/PatchHelperTab"; import PatchHelperTab from "@components/VencordSettings/PatchHelperTab";
@ -33,11 +33,27 @@ import gitHash from "~git-hash";
type SectionType = "HEADER" | "DIVIDER" | "CUSTOM"; type SectionType = "HEADER" | "DIVIDER" | "CUSTOM";
type SectionTypes = Record<SectionType, SectionType>; type SectionTypes = Record<SectionType, SectionType>;
const settings = definePluginSettings({
settingsLocation: {
type: OptionType.SELECT,
description: "Where to put the Vencord settings section",
options: [
{ label: "At the very top", value: "top" },
{ label: "Above the Nitro section", value: "aboveNitro", default: true },
{ label: "Below the Nitro section", value: "belowNitro" },
{ label: "Above Activity Settings", value: "aboveActivity" },
{ label: "Below Activity Settings", value: "belowActivity" },
{ label: "At the very bottom", value: "bottom" },
]
}
});
export default definePlugin({ export default definePlugin({
name: "Settings", name: "Settings",
description: "Adds Settings UI and debug info", description: "Adds Settings UI and debug info",
authors: [Devs.Ven, Devs.Megu], authors: [Devs.Ven, Devs.Megu],
required: true, required: true,
settings,
patches: [ patches: [
{ {
@ -63,7 +79,7 @@ export default definePlugin({
noWarn: true, noWarn: true,
replacement: { replacement: {
get match() { get match() {
switch (Settings.plugins.Settings.settingsLocation) { switch (settings.store.settingsLocation) {
case "top": return /\{section:(\i\.\i)\.HEADER,\s*label:(\i)\.\i\.Messages\.USER_SETTINGS/; case "top": return /\{section:(\i\.\i)\.HEADER,\s*label:(\i)\.\i\.Messages\.USER_SETTINGS/;
case "aboveNitro": return /\{section:(\i\.\i)\.HEADER,\s*label:(\i)\.\i\.Messages\.BILLING_SETTINGS/; case "aboveNitro": return /\{section:(\i\.\i)\.HEADER,\s*label:(\i)\.\i\.Messages\.BILLING_SETTINGS/;
case "belowNitro": return /\{section:(\i\.\i)\.HEADER,\s*label:(\i)\.\i\.Messages\.APP_SETTINGS/; case "belowNitro": return /\{section:(\i\.\i)\.HEADER,\s*label:(\i)\.\i\.Messages\.APP_SETTINGS/;
@ -158,12 +174,12 @@ export default definePlugin({
].filter(Boolean); ].filter(Boolean);
}, },
isRightSpot({ header, settings }: { header?: string; settings?: string[]; }) { isRightSpot({ header, settingsChilds }: { header?: string; settingsChilds?: string[]; }) {
const firstChild = settings?.[0]; const firstChild = settingsChilds?.[0];
// lowest two elements... sanity backup // lowest two elements... sanity backup
if (firstChild === "LOGOUT" || firstChild === "SOCIAL_LINKS") return true; if (firstChild === "LOGOUT" || firstChild === "SOCIAL_LINKS") return true;
const { settingsLocation } = Settings.plugins.Settings; const { settingsLocation } = settings.store;
if (settingsLocation === "bottom") return firstChild === "LOGOUT"; if (settingsLocation === "bottom") return firstChild === "LOGOUT";
if (settingsLocation === "belowActivity") return firstChild === "CHANGELOG"; if (settingsLocation === "belowActivity") return firstChild === "CHANGELOG";
@ -203,21 +219,6 @@ export default definePlugin({
}; };
}, },
options: {
settingsLocation: {
type: OptionType.SELECT,
description: "Where to put the Vencord settings section",
options: [
{ label: "At the very top", value: "top" },
{ label: "Above the Nitro section", value: "aboveNitro", default: true },
{ label: "Below the Nitro section", value: "belowNitro" },
{ label: "Above Activity Settings", value: "aboveActivity" },
{ label: "Below Activity Settings", value: "belowActivity" },
{ label: "At the very bottom", value: "bottom" },
]
},
},
get electronVersion() { get electronVersion() {
return VencordNative.native.getVersions().electron || window.armcord?.electron || null; return VencordNative.native.getVersions().electron || window.armcord?.electron || null;
}, },

View file

@ -16,6 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { definePluginSettings } from "@api/Settings";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
@ -28,16 +29,17 @@ export default definePlugin({
find: "BAN_CONFIRM_TITLE.", find: "BAN_CONFIRM_TITLE.",
replacement: { replacement: {
match: /src:\i\("\d+"\)/g, match: /src:\i\("\d+"\)/g,
replace: "src: Vencord.Settings.plugins.BANger.source" replace: "src: $self.settings.store.source"
} }
} }
], ],
options: {
settings: definePluginSettings({
source: { source: {
description: "Source to replace ban GIF with (Video or Gif)", description: "Source to replace ban GIF with (Video or Gif)",
type: OptionType.STRING, type: OptionType.STRING,
default: "https://i.imgur.com/wp5q52C.mp4", default: "https://i.imgur.com/wp5q52C.mp4",
restartNeeded: true, restartNeeded: true,
} }
} })
}); });

View file

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { Settings } from "@api/Settings"; import { definePluginSettings } from "@api/Settings";
import ErrorBoundary from "@components/ErrorBoundary"; import ErrorBoundary from "@components/ErrorBoundary";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import { canonicalizeMatch } from "@utils/patches"; import { canonicalizeMatch } from "@utils/patches";
@ -25,10 +25,26 @@ import { findByProps } from "@webpack";
const UserPopoutSectionCssClasses = findByProps("section", "lastSection"); const UserPopoutSectionCssClasses = findByProps("section", "lastSection");
const settings = definePluginSettings({
hide: {
type: OptionType.BOOLEAN,
description: "Hide notes",
default: false,
restartNeeded: true
},
noSpellCheck: {
type: OptionType.BOOLEAN,
description: "Disable spellcheck in notes",
disabled: () => settings.store.hide,
default: false
}
});
export default definePlugin({ export default definePlugin({
name: "BetterNotesBox", name: "BetterNotesBox",
description: "Hide notes or disable spellcheck (Configure in settings!!)", description: "Hide notes or disable spellcheck (Configure in settings!!)",
authors: [Devs.Ven], authors: [Devs.Ven],
settings,
patches: [ patches: [
{ {
@ -36,7 +52,7 @@ export default definePlugin({
all: true, all: true,
// Some modules match the find but the replacement is returned untouched // Some modules match the find but the replacement is returned untouched
noWarn: true, noWarn: true,
predicate: () => Vencord.Settings.plugins.BetterNotesBox.hide, predicate: () => settings.store.hide,
replacement: { replacement: {
match: /hideNote:.+?(?=([,}].*?\)))/g, match: /hideNote:.+?(?=([,}].*?\)))/g,
replace: (m, rest) => { replace: (m, rest) => {
@ -54,7 +70,7 @@ export default definePlugin({
find: "Messages.NOTE_PLACEHOLDER", find: "Messages.NOTE_PLACEHOLDER",
replacement: { replacement: {
match: /\.NOTE_PLACEHOLDER,/, match: /\.NOTE_PLACEHOLDER,/,
replace: "$&spellCheck:!Vencord.Settings.plugins.BetterNotesBox.noSpellCheck," replace: "$&spellCheck:!$self.settings.store.noSpellCheck,"
} }
}, },
{ {
@ -66,21 +82,6 @@ export default definePlugin({
} }
], ],
options: {
hide: {
type: OptionType.BOOLEAN,
description: "Hide notes",
default: false,
restartNeeded: true
},
noSpellCheck: {
type: OptionType.BOOLEAN,
description: "Disable spellcheck in notes",
disabled: () => Settings.plugins.BetterNotesBox.hide,
default: false
}
},
patchPadding: ErrorBoundary.wrap(({ lastSection }) => { patchPadding: ErrorBoundary.wrap(({ lastSection }) => {
if (!lastSection) return null; if (!lastSection) return null;
return ( return (

View file

@ -16,16 +16,32 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { Settings } from "@api/Settings"; import { definePluginSettings } from "@api/Settings";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
import { Clipboard, Toasts } from "@webpack/common"; import { Clipboard, Toasts } from "@webpack/common";
const settings = definePluginSettings({
bothStyles: {
type: OptionType.BOOLEAN,
description: "Show both role dot and coloured names",
restartNeeded: true,
default: false,
},
copyRoleColorInProfilePopout: {
type: OptionType.BOOLEAN,
description: "Allow click on role dot in profile popout to copy role color",
restartNeeded: true,
default: false
}
});
export default definePlugin({ export default definePlugin({
name: "BetterRoleDot", name: "BetterRoleDot",
authors: [Devs.Ven, Devs.AutumnVN], authors: [Devs.Ven, Devs.AutumnVN],
description: description:
"Copy role colour on RoleDot (accessibility setting) click. Also allows using both RoleDot and coloured names simultaneously", "Copy role colour on RoleDot (accessibility setting) click. Also allows using both RoleDot and coloured names simultaneously",
settings,
patches: [ patches: [
{ {
@ -39,7 +55,7 @@ export default definePlugin({
find: '"dot"===', find: '"dot"===',
all: true, all: true,
noWarn: true, noWarn: true,
predicate: () => Settings.plugins.BetterRoleDot.bothStyles, predicate: () => settings.store.bothStyles,
replacement: { replacement: {
match: /"(?:username|dot)"===\i(?!\.\i)/g, match: /"(?:username|dot)"===\i(?!\.\i)/g,
replace: "true", replace: "true",
@ -49,7 +65,7 @@ export default definePlugin({
{ {
find: ".ADD_ROLE_A11Y_LABEL", find: ".ADD_ROLE_A11Y_LABEL",
all: true, all: true,
predicate: () => Settings.plugins.BetterRoleDot.copyRoleColorInProfilePopout && !Settings.plugins.BetterRoleDot.bothStyles, predicate: () => settings.store.copyRoleColorInProfilePopout && !settings.store.bothStyles,
noWarn: true, noWarn: true,
replacement: { replacement: {
match: /"dot"===\i/, match: /"dot"===\i/,
@ -59,7 +75,7 @@ export default definePlugin({
{ {
find: ".roleVerifiedIcon", find: ".roleVerifiedIcon",
all: true, all: true,
predicate: () => Settings.plugins.BetterRoleDot.copyRoleColorInProfilePopout && !Settings.plugins.BetterRoleDot.bothStyles, predicate: () => settings.store.copyRoleColorInProfilePopout && !settings.store.bothStyles,
noWarn: true, noWarn: true,
replacement: { replacement: {
match: /"dot"===\i/, match: /"dot"===\i/,
@ -68,21 +84,6 @@ export default definePlugin({
} }
], ],
options: {
bothStyles: {
type: OptionType.BOOLEAN,
description: "Show both role dot and coloured names",
restartNeeded: true,
default: false,
},
copyRoleColorInProfilePopout: {
type: OptionType.BOOLEAN,
description: "Allow click on role dot in profile popout to copy role color",
restartNeeded: true,
default: false
}
},
copyToClipBoard(color: string) { copyToClipBoard(color: string) {
Clipboard.copy(color); Clipboard.copy(color);
Toasts.show({ Toasts.show({

View file

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { Settings } from "@api/Settings"; import { definePluginSettings } from "@api/Settings";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
@ -26,7 +26,7 @@ function setCss() {
style.textContent = ` style.textContent = `
.vc-nsfw-img [class^=imageWrapper] img, .vc-nsfw-img [class^=imageWrapper] img,
.vc-nsfw-img [class^=wrapperPaused] video { .vc-nsfw-img [class^=wrapperPaused] video {
filter: blur(${Settings.plugins.BlurNSFW.blurAmount}px); filter: blur(${settings.store.blurAmount}px);
transition: filter 0.2s; transition: filter 0.2s;
} }
.vc-nsfw-img [class^=imageWrapper]:hover img, .vc-nsfw-img [class^=imageWrapper]:hover img,
@ -36,10 +36,20 @@ function setCss() {
`; `;
} }
const settings = definePluginSettings({
blurAmount: {
type: OptionType.NUMBER,
description: "Blur Amount",
default: 10,
onChange: setCss
}
});
export default definePlugin({ export default definePlugin({
name: "BlurNSFW", name: "BlurNSFW",
description: "Blur attachments in NSFW channels until hovered", description: "Blur attachments in NSFW channels until hovered",
authors: [Devs.Ven], authors: [Devs.Ven],
settings,
patches: [ patches: [
{ {
@ -51,15 +61,6 @@ export default definePlugin({
} }
], ],
options: {
blurAmount: {
type: OptionType.NUMBER,
description: "Blur Amount",
default: 10,
onChange: setCss
}
},
start() { start() {
style = document.createElement("style"); style = document.createElement("style");
style.id = "VcBlurNsfw"; style.id = "VcBlurNsfw";

View file

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { Settings } from "@api/Settings"; import { definePluginSettings } from "@api/Settings";
import ErrorBoundary from "@components/ErrorBoundary"; import ErrorBoundary from "@components/ErrorBoundary";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import { useTimer } from "@utils/react"; import { useTimer } from "@utils/react";
@ -25,7 +25,7 @@ import { React } from "@webpack/common";
function formatDuration(ms: number) { function formatDuration(ms: number) {
// here be dragons (moment fucking sucks) // here be dragons (moment fucking sucks)
const human = Settings.plugins.CallTimer.format === "human"; const human = settings.store.format === "human";
const format = (n: number) => human ? n : n.toString().padStart(2, "0"); const format = (n: number) => human ? n : n.toString().padStart(2, "0");
const unit = (s: string) => human ? s : ""; const unit = (s: string) => human ? s : "";
@ -46,15 +46,7 @@ function formatDuration(ms: number) {
return res; return res;
} }
export default definePlugin({ const settings = definePluginSettings({
name: "CallTimer",
description: "Adds a timer to vcs",
authors: [Devs.Ven],
startTime: 0,
interval: void 0 as NodeJS.Timeout | undefined,
options: {
format: { format: {
type: OptionType.SELECT, type: OptionType.SELECT,
description: "The timer format. This can be any valid moment.js format", description: "The timer format. This can be any valid moment.js format",
@ -70,7 +62,16 @@ export default definePlugin({
} }
] ]
} }
}, });
export default definePlugin({
name: "CallTimer",
description: "Adds a timer to vcs",
authors: [Devs.Ven],
settings,
startTime: 0,
interval: void 0 as NodeJS.Timeout | undefined,
patches: [{ patches: [{
find: "renderConnectionStatus(){", find: "renderConnectionStatus(){",

View file

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { definePluginSettings, Settings } from "@api/Settings"; import { definePluginSettings } from "@api/Settings";
import { ErrorCard } from "@components/ErrorCard"; import { ErrorCard } from "@components/ErrorCard";
import { Link } from "@components/Link"; import { Link } from "@components/Link";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
@ -258,7 +258,7 @@ const settings = definePluginSettings({
function onChange() { function onChange() {
setRpc(true); setRpc(true);
if (Settings.plugins.CustomRPC.enabled) setRpc(); if (Vencord.Plugins.isPluginEnabled("CustomRPC")) setRpc();
} }
function isStreamLinkDisabled() { function isStreamLinkDisabled() {

View file

@ -21,20 +21,25 @@ import { Devs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
import { findByProps, findStore } from "@webpack"; import { findByProps, findStore } from "@webpack";
import { ChannelStore, FluxDispatcher, GuildStore, RelationshipStore, SnowflakeUtils, UserStore } from "@webpack/common"; import { ChannelStore, FluxDispatcher, GuildStore, RelationshipStore, SnowflakeUtils, UserStore } from "@webpack/common";
import { Settings } from "Vencord";
const UserAffinitiesStore = findStore("UserAffinitiesStore"); const UserAffinitiesStore = findStore("UserAffinitiesStore");
const { FriendsSections } = findByProps("FriendsSections"); const { FriendsSections } = findByProps("FriendsSections");
interface UserAffinity { const settings = definePluginSettings({
user_id: string; sortByAffinity: {
affinity: number; type: OptionType.BOOLEAN,
default: true,
description: "Whether to sort implicit relationships by their affinity to you.",
restartNeeded: true
} }
});
export default definePlugin({ export default definePlugin({
name: "ImplicitRelationships", name: "ImplicitRelationships",
description: "Shows your implicit relationships in the Friends tab.", description: "Shows your implicit relationships in the Friends tab.",
authors: [Devs.Dolfies], authors: [Devs.Dolfies],
settings,
patches: [ patches: [
// Counts header // Counts header
{ {
@ -81,7 +86,7 @@ export default definePlugin({
{ {
find: "getRelationshipCounts(){", find: "getRelationshipCounts(){",
replacement: { replacement: {
predicate: () => Settings.plugins.ImplicitRelationships.sortByAffinity, predicate: () => settings.store.sortByAffinity,
match: /\}\)\.sortBy\((.+?)\)\.value\(\)/, match: /\}\)\.sortBy\((.+?)\)\.value\(\)/,
replace: "}).sortBy(row => $self.wrapSort(($1), row)).value()" replace: "}).sortBy(row => $self.wrapSort(($1), row)).value()"
} }
@ -110,16 +115,6 @@ export default definePlugin({
}, },
} }
], ],
settings: definePluginSettings(
{
sortByAffinity: {
type: OptionType.BOOLEAN,
default: true,
description: "Whether to sort implicit relationships by their affinity to you.",
restartNeeded: true
},
}
),
wrapSort(comparator: Function, row: any) { wrapSort(comparator: Function, row: any) {
return row.type === 5 return row.type === 5

View file

@ -20,7 +20,7 @@ import "./messageLogger.css";
import { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/ContextMenu"; import { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/ContextMenu";
import { updateMessage } from "@api/MessageUpdater"; import { updateMessage } from "@api/MessageUpdater";
import { Settings } from "@api/Settings"; import { definePluginSettings } from "@api/Settings";
import { disableStyle, enableStyle } from "@api/Styles"; import { disableStyle, enableStyle } from "@api/Styles";
import ErrorBoundary from "@components/ErrorBoundary"; import ErrorBoundary from "@components/ErrorBoundary";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
@ -41,7 +41,7 @@ interface MLMessage extends Message {
const styles = findByProps("edited", "communicationDisabled", "isSystemMessage"); const styles = findByProps("edited", "communicationDisabled", "isSystemMessage");
function addDeleteStyle() { function addDeleteStyle() {
if (Settings.plugins.MessageLogger.deleteStyle === "text") { if (settings.store.deleteStyle === "text") {
enableStyle(textStyle); enableStyle(textStyle);
disableStyle(overlayStyle); disableStyle(overlayStyle);
} else { } else {
@ -125,57 +125,7 @@ const patchChannelContextMenu: NavContextMenuPatchCallback = (children, { channe
); );
}; };
export default definePlugin({ const settings = definePluginSettings({
name: "MessageLogger",
description: "Temporarily logs deleted and edited messages.",
authors: [Devs.rushii, Devs.Ven, Devs.AutumnVN, Devs.Nickyux],
dependencies: ["MessageUpdaterAPI"],
contextMenus: {
"message": patchMessageContextMenu,
"channel-context": patchChannelContextMenu,
"user-context": patchChannelContextMenu,
"gdm-context": patchChannelContextMenu
},
start() {
addDeleteStyle();
},
renderEdits: ErrorBoundary.wrap(({ message: { id: messageId, channel_id: channelId } }: { message: Message; }) => {
const message = useStateFromStores(
[MessageStore],
() => MessageStore.getMessage(channelId, messageId) as MLMessage,
null,
(oldMsg, newMsg) => oldMsg?.editHistory === newMsg?.editHistory
);
return (
<>
{message.editHistory?.map(edit => (
<div className="messagelogger-edited">
{Parser.parse(edit.content)}
<Timestamp
timestamp={edit.timestamp}
isEdited={true}
isInline={false}
>
<span className={styles.edited}>{" "}({i18n.Messages.MESSAGE_EDITED})</span>
</Timestamp>
</div>
))}
</>
);
}, { noop: true }),
makeEdit(newMessage: any, oldMessage: any): any {
return {
timestamp: new Date(newMessage.edited_timestamp),
content: oldMessage.content
};
},
options: {
deleteStyle: { deleteStyle: {
type: OptionType.SELECT, type: OptionType.SELECT,
description: "The style of deleted messages", description: "The style of deleted messages",
@ -221,6 +171,57 @@ export default definePlugin({
description: "Comma-separated list of guild IDs to ignore", description: "Comma-separated list of guild IDs to ignore",
default: "" default: ""
}, },
});
export default definePlugin({
name: "MessageLogger",
description: "Temporarily logs deleted and edited messages.",
authors: [Devs.rushii, Devs.Ven, Devs.AutumnVN, Devs.Nickyux],
dependencies: ["MessageUpdaterAPI"],
settings,
contextMenus: {
"message": patchMessageContextMenu,
"channel-context": patchChannelContextMenu,
"user-context": patchChannelContextMenu,
"gdm-context": patchChannelContextMenu
},
start() {
addDeleteStyle();
},
renderEdits: ErrorBoundary.wrap(({ message: { id: messageId, channel_id: channelId } }: { message: Message; }) => {
const message = useStateFromStores(
[MessageStore],
() => MessageStore.getMessage(channelId, messageId) as MLMessage,
null,
(oldMsg, newMsg) => oldMsg?.editHistory === newMsg?.editHistory
);
return (
<>
{message.editHistory?.map(edit => (
<div className="messagelogger-edited">
{Parser.parse(edit.content)}
<Timestamp
timestamp={edit.timestamp}
isEdited={true}
isInline={false}
>
<span className={styles.edited}>{" "}({i18n.Messages.MESSAGE_EDITED})</span>
</Timestamp>
</div>
))}
</>
);
}, { noop: true }),
makeEdit(newMessage: any, oldMessage: any): any {
return {
timestamp: new Date(newMessage.edited_timestamp),
content: oldMessage.content
};
}, },
handleDelete(cache: any, data: { ids: string[], id: string; mlDeleted?: boolean; }, isBulk: boolean) { handleDelete(cache: any, data: { ids: string[], id: string; mlDeleted?: boolean; }, isBulk: boolean) {
@ -257,7 +258,7 @@ export default definePlugin({
}, },
shouldIgnore(message: any, isEdit = false) { shouldIgnore(message: any, isEdit = false) {
const { ignoreBots, ignoreSelf, ignoreUsers, ignoreChannels, ignoreGuilds, logEdits, logDeletes } = Settings.plugins.MessageLogger; const { ignoreBots, ignoreSelf, ignoreUsers, ignoreChannels, ignoreGuilds, logEdits, logDeletes } = settings.store;
const myId = UserStore.getCurrentUser().id; const myId = UserStore.getCurrentUser().id;
return ignoreBots && message.author?.bot || return ignoreBots && message.author?.bot ||

View file

@ -18,7 +18,7 @@
import { ApplicationCommandInputType, ApplicationCommandOptionType, findOption, registerCommand, sendBotMessage, unregisterCommand } from "@api/Commands"; import { ApplicationCommandInputType, ApplicationCommandOptionType, findOption, registerCommand, sendBotMessage, unregisterCommand } from "@api/Commands";
import * as DataStore from "@api/DataStore"; import * as DataStore from "@api/DataStore";
import { Settings } from "@api/Settings"; import { definePluginSettings } from "@api/Settings";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
@ -60,7 +60,7 @@ function createTagCommand(tag: Tag) {
return { content: `/${tag.name}` }; return { content: `/${tag.name}` };
} }
if (Settings.plugins.MessageTags.clyde) sendBotMessage(ctx.channel.id, { if (settings.store.clyde) sendBotMessage(ctx.channel.id, {
content: `${EMOTE} The tag **${tag.name}** has been sent!` content: `${EMOTE} The tag **${tag.name}** has been sent!`
}); });
return { content: tag.message.replaceAll("\\n", "\n") }; return { content: tag.message.replaceAll("\\n", "\n") };
@ -69,19 +69,21 @@ function createTagCommand(tag: Tag) {
}, "CustomTags"); }, "CustomTags");
} }
const settings = definePluginSettings({
export default definePlugin({
name: "MessageTags",
description: "Allows you to save messages and to use them with a simple command.",
authors: [Devs.Luna],
options: {
clyde: { clyde: {
name: "Clyde message on send", name: "Clyde message on send",
description: "If enabled, clyde will send you an ephemeral message when a tag was used.", description: "If enabled, clyde will send you an ephemeral message when a tag was used.",
type: OptionType.BOOLEAN, type: OptionType.BOOLEAN,
default: true default: true
} }
}, });
export default definePlugin({
name: "MessageTags",
description: "Allows you to save messages and to use them with a simple command.",
authors: [Devs.Luna],
settings,
dependencies: ["CommandsAPI"], dependencies: ["CommandsAPI"],
async start() { async start() {

View file

@ -16,17 +16,28 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { Settings } from "@api/Settings"; import { definePluginSettings } from "@api/Settings";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
import { findByProps } from "@webpack"; import { findByProps } from "@webpack";
const RelationshipStore = findByProps("getRelationships", "isBlocked"); const RelationshipStore = findByProps("getRelationships", "isBlocked");
const settings = definePluginSettings({
ignoreBlockedMessages: {
description: "Completely ignores (recent) incoming messages from blocked users (locally).",
type: OptionType.BOOLEAN,
default: false,
restartNeeded: true,
},
});
export default definePlugin({ export default definePlugin({
name: "NoBlockedMessages", name: "NoBlockedMessages",
description: "Hides all blocked messages from chat completely.", description: "Hides all blocked messages from chat completely.",
authors: [Devs.rushii, Devs.Samu], authors: [Devs.rushii, Devs.Samu],
settings,
patches: [ patches: [
{ {
find: "Messages.BLOCKED_MESSAGES_HIDE", find: "Messages.BLOCKED_MESSAGES_HIDE",
@ -42,7 +53,7 @@ export default definePlugin({
'"displayName","ReadStateStore")' '"displayName","ReadStateStore")'
].map(find => ({ ].map(find => ({
find, find,
predicate: () => Settings.plugins.NoBlockedMessages.ignoreBlockedMessages === true, predicate: () => settings.store.ignoreBlockedMessages === true,
replacement: [ replacement: [
{ {
match: /(?<=MESSAGE_CREATE:function\((\i)\){)/, match: /(?<=MESSAGE_CREATE:function\((\i)\){)/,
@ -51,14 +62,6 @@ export default definePlugin({
] ]
})) }))
], ],
options: {
ignoreBlockedMessages: {
description: "Completely ignores (recent) incoming messages from blocked users (locally).",
type: OptionType.BOOLEAN,
default: false,
restartNeeded: true,
},
},
isBlocked: message => isBlocked: message =>
RelationshipStore.isBlocked(message.author.id) RelationshipStore.isBlocked(message.author.id)
}); });

View file

@ -19,7 +19,7 @@
import { addBadge, BadgePosition, ProfileBadge, removeBadge } from "@api/Badges"; import { addBadge, BadgePosition, ProfileBadge, removeBadge } from "@api/Badges";
import { addDecorator, removeDecorator } from "@api/MemberListDecorators"; import { addDecorator, removeDecorator } from "@api/MemberListDecorators";
import { addDecoration, removeDecoration } from "@api/MessageDecorations"; import { addDecoration, removeDecoration } from "@api/MessageDecorations";
import { Settings } from "@api/Settings"; import { definePluginSettings } from "@api/Settings";
import ErrorBoundary from "@components/ErrorBoundary"; import ErrorBoundary from "@components/ErrorBoundary";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
@ -162,27 +162,34 @@ const indicatorLocations = {
} }
}; };
const settings = definePluginSettings({
...Object.fromEntries(
Object.entries(indicatorLocations).map(([key, value]) => {
return [key, {
type: OptionType.BOOLEAN,
description: `Show indicators ${value.description.toLowerCase()}`,
// onChange doesn't give any way to know which setting was changed, so restart required
restartNeeded: true,
default: true
}];
})
) as Record<"list" | "badges" | "messages", { type: OptionType.BOOLEAN; description: string; restartNeeded: boolean; default: boolean; }>,
colorMobileIndicator: {
type: OptionType.BOOLEAN,
description: "Whether to make the mobile indicator match the color of the user status.",
default: true,
restartNeeded: true
}
});
export default definePlugin({ export default definePlugin({
name: "PlatformIndicators", name: "PlatformIndicators",
description: "Adds platform indicators (Desktop, Mobile, Web...) to users", description: "Adds platform indicators (Desktop, Mobile, Web...) to users",
authors: [Devs.kemo, Devs.TheSun, Devs.Nuckyz, Devs.Ven], authors: [Devs.kemo, Devs.TheSun, Devs.Nuckyz, Devs.Ven],
dependencies: ["MessageDecorationsAPI", "MemberListDecoratorsAPI"], dependencies: ["MessageDecorationsAPI", "MemberListDecoratorsAPI"],
settings,
start() { start() {
const settings = Settings.plugins.PlatformIndicators;
const { displayMode } = settings;
// transfer settings from the old ones, which had a select menu instead of booleans
if (displayMode) {
if (displayMode !== "both") settings[displayMode] = true;
else {
settings.list = true;
settings.badges = true;
}
settings.messages = true;
delete settings.displayMode;
}
Object.entries(indicatorLocations).forEach(([key, value]) => { Object.entries(indicatorLocations).forEach(([key, value]) => {
if (settings[key]) value.onEnable(); if (settings[key]) value.onEnable();
}); });
@ -197,7 +204,7 @@ export default definePlugin({
patches: [ patches: [
{ {
find: ".Masks.STATUS_ONLINE_MOBILE", find: ".Masks.STATUS_ONLINE_MOBILE",
predicate: () => Settings.plugins.PlatformIndicators.colorMobileIndicator, predicate: () => settings.store.colorMobileIndicator,
replacement: [ replacement: [
{ {
// Return the STATUS_ONLINE_MOBILE mask if the user is on mobile, no matter the status // Return the STATUS_ONLINE_MOBILE mask if the user is on mobile, no matter the status
@ -213,7 +220,7 @@ export default definePlugin({
}, },
{ {
find: ".AVATAR_STATUS_MOBILE_16;", find: ".AVATAR_STATUS_MOBILE_16;",
predicate: () => Settings.plugins.PlatformIndicators.colorMobileIndicator, predicate: () => settings.store.colorMobileIndicator,
replacement: [ replacement: [
{ {
// Return the AVATAR_STATUS_MOBILE size mask if the user is on mobile, no matter the status // Return the AVATAR_STATUS_MOBILE size mask if the user is on mobile, no matter the status
@ -234,32 +241,12 @@ export default definePlugin({
}, },
{ {
find: "}isMobileOnline(", find: "}isMobileOnline(",
predicate: () => Settings.plugins.PlatformIndicators.colorMobileIndicator, predicate: () => settings.store.colorMobileIndicator,
replacement: { replacement: {
// Make isMobileOnline return true no matter what is the user status // Make isMobileOnline return true no matter what is the user status
match: /(?<=\i\[\i\.\i\.MOBILE\])===\i\.\i\.ONLINE/, match: /(?<=\i\[\i\.\i\.MOBILE\])===\i\.\i\.ONLINE/,
replace: "!= null" replace: "!= null"
} }
} }
], ]
options: {
...Object.fromEntries(
Object.entries(indicatorLocations).map(([key, value]) => {
return [key, {
type: OptionType.BOOLEAN,
description: `Show indicators ${value.description.toLowerCase()}`,
// onChange doesn't give any way to know which setting was changed, so restart required
restartNeeded: true,
default: true
}];
})
),
colorMobileIndicator: {
type: OptionType.BOOLEAN,
description: "Whether to make the mobile indicator match the color of the user status.",
default: true,
restartNeeded: true
}
}
}); });

View file

@ -16,7 +16,6 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { Settings } from "@api/Settings";
import { debounce } from "@shared/debounce"; import { debounce } from "@shared/debounce";
import { VENCORD_USER_AGENT } from "@shared/vencordUserAgent"; import { VENCORD_USER_AGENT } from "@shared/vencordUserAgent";
import { getCurrentChannel } from "@utils/discord"; import { getCurrentChannel } from "@utils/discord";
@ -147,11 +146,11 @@ async function bulkFetchPronouns(ids: string[]): Promise<PronounsResponse> {
} }
} }
export function extractPronouns(pronounSet?: { [locale: string]: PronounCode[] }): string { export function extractPronouns(pronounSet?: { [locale: string]: PronounCode[]; }): string {
if (!pronounSet || !pronounSet.en) return PronounMapping.unspecified; if (!pronounSet || !pronounSet.en) return PronounMapping.unspecified;
// PronounDB returns an empty set instead of {sets: {en: ["unspecified"]}}. // PronounDB returns an empty set instead of {sets: {en: ["unspecified"]}}.
const pronouns = pronounSet.en; const pronouns = pronounSet.en;
const { pronounsFormat } = Settings.plugins.PronounDB as { pronounsFormat: PronounsFormat, enabled: boolean; }; const { pronounsFormat } = settings.store;
if (pronouns.length === 1) { if (pronouns.length === 1) {
// For capitalized pronouns or special codes (any, ask, avoid), we always return the normal (capitalized) string // For capitalized pronouns or special codes (any, ask, avoid), we always return the normal (capitalized) string

View file

@ -17,7 +17,7 @@
*/ */
import { addServerListElement, removeServerListElement, ServerListRenderPosition } from "@api/ServerList"; import { addServerListElement, removeServerListElement, ServerListRenderPosition } from "@api/ServerList";
import { Settings } from "@api/Settings"; import { definePluginSettings } from "@api/Settings";
import ErrorBoundary from "@components/ErrorBoundary"; import ErrorBoundary from "@components/ErrorBoundary";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import { useForceUpdater } from "@utils/react"; import { useForceUpdater } from "@utils/react";
@ -89,13 +89,7 @@ function handleGuildUpdate() {
forceUpdateGuildCount?.(); forceUpdateGuildCount?.();
} }
export default definePlugin({ const settings = definePluginSettings({
name: "ServerListIndicators",
description: "Add online friend count or server count in the server list",
authors: [Devs.dzshn],
dependencies: ["ServerListAPI"],
options: {
mode: { mode: {
description: "mode", description: "mode",
type: OptionType.SELECT, type: OptionType.SELECT,
@ -105,10 +99,17 @@ export default definePlugin({
{ label: "Both server and online friend counts", value: IndicatorType.BOTH }, { label: "Both server and online friend counts", value: IndicatorType.BOTH },
] ]
} }
}, });
export default definePlugin({
name: "ServerListIndicators",
description: "Add online friend count or server count in the server list",
authors: [Devs.dzshn],
dependencies: ["ServerListAPI"],
settings,
renderIndicator: () => { renderIndicator: () => {
const { mode } = Settings.plugins.ServerListIndicators; const { mode } = settings.store;
return <ErrorBoundary noop> return <ErrorBoundary noop>
<div style={{ marginBottom: "4px" }}> <div style={{ marginBottom: "4px" }}>
{!!(mode & IndicatorType.FRIEND) && <FriendsIndicator />} {!!(mode & IndicatorType.FRIEND) && <FriendsIndicator />}

View file

@ -16,7 +16,6 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { Settings } from "@api/Settings";
import ErrorBoundary from "@components/ErrorBoundary"; import ErrorBoundary from "@components/ErrorBoundary";
import { formatDuration } from "@utils/text"; import { formatDuration } from "@utils/text";
import { findByProps, findComponentByCode } from "@webpack"; import { findByProps, findComponentByCode } from "@webpack";
@ -157,7 +156,7 @@ function HiddenChannelLockScreen({ channel }: { channel: ExtendedChannel; }) {
}); });
} }
if (Settings.plugins.PermissionsViewer.enabled) { if (Vencord.Plugins.isPluginEnabled("PermissionsViewer")) {
setPermissions(sortPermissionOverwrites(Object.values(permissionOverwrites).map(overwrite => ({ setPermissions(sortPermissionOverwrites(Object.values(permissionOverwrites).map(overwrite => ({
type: overwrite.type as PermissionType, type: overwrite.type as PermissionType,
id: overwrite.id, id: overwrite.id,
@ -274,7 +273,7 @@ function HiddenChannelLockScreen({ channel }: { channel: ExtendedChannel; }) {
} }
<div className="shc-lock-screen-allowed-users-and-roles-container"> <div className="shc-lock-screen-allowed-users-and-roles-container">
<div className="shc-lock-screen-allowed-users-and-roles-container-title"> <div className="shc-lock-screen-allowed-users-and-roles-container-title">
{Settings.plugins.PermissionsViewer.enabled && ( {Vencord.Plugins.isPluginEnabled("PermissionsViewer") && (
<Tooltip text="Permission Details"> <Tooltip text="Permission Details">
{({ onMouseLeave, onMouseEnter }) => ( {({ onMouseLeave, onMouseEnter }) => (
<button <button

View file

@ -16,10 +16,11 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { Settings } from "@api/Settings";
import { findByProps, webpackDependantLazy } from "@webpack"; import { findByProps, webpackDependantLazy } from "@webpack";
import { Flux, FluxDispatcher } from "@webpack/common"; import { Flux, FluxDispatcher } from "@webpack/common";
import { settings } from ".";
export interface Track { export interface Track {
id: string; id: string;
name: string; name: string;
@ -88,7 +89,7 @@ export const SpotifyStore = webpackDependantLazy(() => {
public isSettingPosition = false; public isSettingPosition = false;
public openExternal(path: string) { public openExternal(path: string) {
const url = Settings.plugins.SpotifyControls.useSpotifyUris || Vencord.Plugins.isPluginEnabled("OpenInApp") const url = settings.store.useSpotifyUris || Vencord.Plugins.isPluginEnabled("OpenInApp")
? "spotify:" + path.replaceAll("/", (_, idx) => idx === 0 ? "" : ":") ? "spotify:" + path.replaceAll("/", (_, idx) => idx === 0 ? "" : ":")
: "https://open.spotify.com" + path; : "https://open.spotify.com" + path;

View file

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { Settings } from "@api/Settings"; import { definePluginSettings } from "@api/Settings";
import { disableStyle, enableStyle } from "@api/Styles"; import { disableStyle, enableStyle } from "@api/Styles";
import ErrorBoundary from "@components/ErrorBoundary"; import ErrorBoundary from "@components/ErrorBoundary";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
@ -29,11 +29,7 @@ function toggleHoverControls(value: boolean) {
(value ? enableStyle : disableStyle)(hoverOnlyStyle); (value ? enableStyle : disableStyle)(hoverOnlyStyle);
} }
export default definePlugin({ export const settings = definePluginSettings({
name: "SpotifyControls",
description: "Adds a Spotify player above the account panel",
authors: [Devs.Ven, Devs.afn, Devs.KraXen72, Devs.Av32000],
options: {
hoverControls: { hoverControls: {
description: "Show controls on hover", description: "Show controls on hover",
type: OptionType.BOOLEAN, type: OptionType.BOOLEAN,
@ -45,7 +41,14 @@ export default definePlugin({
description: "Open Spotify URIs instead of Spotify URLs. Will only work if you have Spotify installed and might not work on all platforms", description: "Open Spotify URIs instead of Spotify URLs. Will only work if you have Spotify installed and might not work on all platforms",
default: false default: false
} }
}, });
export default definePlugin({
name: "SpotifyControls",
description: "Adds a Spotify player above the account panel",
authors: [Devs.Ven, Devs.afn, Devs.KraXen72, Devs.Av32000],
settings,
patches: [ patches: [
{ {
find: "showTaglessAccountPanel:", find: "showTaglessAccountPanel:",
@ -87,7 +90,7 @@ export default definePlugin({
} }
], ],
start: () => toggleHoverControls(Settings.plugins.SpotifyControls.hoverControls), start: () => toggleHoverControls(settings.store.hoverControls),
PanelWrapper({ VencordOriginal, ...props }) { PanelWrapper({ VencordOriginal, ...props }) {
return ( return (

View file

@ -18,7 +18,7 @@
import "./style.css"; import "./style.css";
import { definePluginSettings, Settings } from "@api/Settings"; import { definePluginSettings } from "@api/Settings";
import ErrorBoundary from "@components/ErrorBoundary"; import ErrorBoundary from "@components/ErrorBoundary";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
@ -87,7 +87,7 @@ function TypingIndicator({ channelId }: { channelId: string; }) {
break; break;
} }
default: { default: {
tooltipText = Settings.plugins.TypingTweaks.enabled tooltipText = Vencord.Plugins.isPluginEnabled("TypingTweaks")
? buildSeveralUsers({ a: getDisplayName(guildId, typingUsersArray[0]), b: getDisplayName(guildId, typingUsersArray[1]), count: typingUsersArray.length - 2 }) ? buildSeveralUsers({ a: getDisplayName(guildId, typingUsersArray[0]), b: getDisplayName(guildId, typingUsersArray[1]), count: typingUsersArray.length - 2 })
: i18n.Messages.SEVERAL_USERS_TYPING; : i18n.Messages.SEVERAL_USERS_TYPING;
break; break;

View file

@ -16,13 +16,14 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { Settings } from "@api/Settings"; import { definePluginSettings } from "@api/Settings";
import { ErrorCard } from "@components/ErrorCard"; import { ErrorCard } from "@components/ErrorCard";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import { proxyLazy } from "@utils/lazy";
import { Logger } from "@utils/Logger"; import { Logger } from "@utils/Logger";
import { Margins } from "@utils/margins"; import { Margins } from "@utils/margins";
import { wordsToTitle } from "@utils/text"; import { wordsToTitle } from "@utils/text";
import definePlugin, { OptionType, PluginOptionsItem, ReporterTestable } from "@utils/types"; import definePlugin, { OptionType, ReporterTestable } from "@utils/types";
import { findByProps } from "@webpack"; import { findByProps } from "@webpack";
import { Button, ChannelStore, Forms, GuildMemberStore, SelectedChannelStore, SelectedGuildStore, useMemo, UserStore } from "@webpack/common"; import { Button, ChannelStore, Forms, GuildMemberStore, SelectedChannelStore, SelectedGuildStore, useMemo, UserStore } from "@webpack/common";
@ -42,25 +43,25 @@ const VoiceStateStore = findByProps("getVoiceStatesForChannel", "getCurrentClien
// Filtering out events is not as simple as just dropping duplicates, as otherwise mute, unmute, mute would // Filtering out events is not as simple as just dropping duplicates, as otherwise mute, unmute, mute would
// not say the second mute, which would lead you to believe they're unmuted // not say the second mute, which would lead you to believe they're unmuted
function speak(text: string, settings: any = Settings.plugins.VcNarrator) { function speak(text: string, mergedSettings: typeof settings.store = settings.store) {
if (!text) return; if (!text) return;
const speech = new SpeechSynthesisUtterance(text); const speech = new SpeechSynthesisUtterance(text);
let voice = speechSynthesis.getVoices().find(v => v.voiceURI === settings.voice); let voice = speechSynthesis.getVoices().find(v => v.voiceURI === mergedSettings.voice);
if (!voice) { if (!voice) {
new Logger("VcNarrator").error(`Voice "${settings.voice}" not found. Resetting to default.`); new Logger("VcNarrator").error(`Voice "${mergedSettings.voice}" not found. Resetting to default.`);
voice = speechSynthesis.getVoices().find(v => v.default); voice = speechSynthesis.getVoices().find(v => v.default);
settings.voice = voice?.voiceURI; mergedSettings.voice = voice?.voiceURI as string;
if (!voice) return; // This should never happen if (!voice) return; // This should never happen
} }
speech.voice = voice!; speech.voice = voice;
speech.volume = settings.volume; speech.volume = mergedSettings.volume;
speech.rate = settings.rate; speech.rate = mergedSettings.rate;
speechSynthesis.speak(speech); speechSynthesis.speak(speech);
} }
function clean(str: string) { function clean(str: string) {
const replacer = Settings.plugins.VcNarrator.latinOnly const replacer = settings.store.latinOnly
? /[^\p{Script=Latin}\p{Number}\p{Punctuation}\s]/gu ? /[^\p{Script=Latin}\p{Number}\p{Punctuation}\s]/gu
: /[^\p{Letter}\p{Number}\p{Punctuation}\s]/gu; : /[^\p{Letter}\p{Number}\p{Punctuation}\s]/gu;
@ -143,92 +144,23 @@ function updateStatuses(type: string, { deaf, mute, selfDeaf, selfMute, userId,
} }
*/ */
function playSample(tempSettings: any, type: string) { function playSample(tempSettings: typeof settings.store, type: string) {
const settings = Object.assign({}, Settings.plugins.VcNarrator, tempSettings); const mergedSettings = Object.assign({}, settings.store, tempSettings);
const currentUser = UserStore.getCurrentUser(); const currentUser = UserStore.getCurrentUser();
const myGuildId = SelectedGuildStore.getGuildId(); const myGuildId = SelectedGuildStore.getGuildId();
speak(formatText(settings[type + "Message"], currentUser.username, "general", (currentUser as any).globalName ?? currentUser.username, GuildMemberStore.getNick(myGuildId, currentUser.id) ?? currentUser.username), settings); speak(formatText(mergedSettings[type + "Message"], currentUser.username, "general", (currentUser as any).globalName ?? currentUser.username, GuildMemberStore.getNick(myGuildId, currentUser.id) ?? currentUser.username), mergedSettings);
} }
export default definePlugin({ const settings = definePluginSettings({
name: "VcNarrator",
description: "Announces when users join, leave, or move voice channels via narrator",
authors: [Devs.Ven],
reporterTestable: ReporterTestable.None,
flux: {
VOICE_STATE_UPDATES({ voiceStates }: { voiceStates: VoiceState[]; }) {
const myGuildId = SelectedGuildStore.getGuildId();
const myChanId = SelectedChannelStore.getVoiceChannelId();
const myId = UserStore.getCurrentUser().id;
if (ChannelStore.getChannel(myChanId!)?.type === 13 /* Stage Channel */) return;
for (const state of voiceStates) {
const { userId, channelId, oldChannelId } = state;
const isMe = userId === myId;
if (!isMe) {
if (!myChanId) continue;
if (channelId !== myChanId && oldChannelId !== myChanId) continue;
}
const [type, id] = getTypeAndChannelId(state, isMe);
if (!type) continue;
const template = Settings.plugins.VcNarrator[type + "Message"];
const user = isMe && !Settings.plugins.VcNarrator.sayOwnName ? "" : UserStore.getUser(userId).username;
const displayName = user && ((UserStore.getUser(userId) as any).globalName ?? user);
const nickname = user && (GuildMemberStore.getNick(myGuildId, userId) ?? user);
const channel = ChannelStore.getChannel(id).name;
speak(formatText(template, user, channel, displayName, nickname));
// updateStatuses(type, state, isMe);
}
},
AUDIO_TOGGLE_SELF_MUTE() {
const chanId = SelectedChannelStore.getVoiceChannelId()!;
const s = VoiceStateStore.getVoiceStateForChannel(chanId) as VoiceState;
if (!s) return;
const event = s.mute || s.selfMute ? "unmute" : "mute";
speak(formatText(Settings.plugins.VcNarrator[event + "Message"], "", ChannelStore.getChannel(chanId).name, "", ""));
},
AUDIO_TOGGLE_SELF_DEAF() {
const chanId = SelectedChannelStore.getVoiceChannelId()!;
const s = VoiceStateStore.getVoiceStateForChannel(chanId) as VoiceState;
if (!s) return;
const event = s.deaf || s.selfDeaf ? "undeafen" : "deafen";
speak(formatText(Settings.plugins.VcNarrator[event + "Message"], "", ChannelStore.getChannel(chanId).name, "", ""));
}
},
start() {
if (typeof speechSynthesis === "undefined" || speechSynthesis.getVoices().length === 0) {
new Logger("VcNarrator").warn(
"SpeechSynthesis not supported or no Narrator voices found. Thus, this plugin will not work. Check my Settings for more info"
);
return;
}
},
optionsCache: null as Record<string, PluginOptionsItem> | null,
get options() {
return this.optionsCache ??= {
voice: { voice: {
type: OptionType.SELECT, type: OptionType.SELECT,
description: "Narrator Voice", description: "Narrator Voice",
options: window.speechSynthesis?.getVoices().map(v => ({ options: proxyLazy(() => window.speechSynthesis?.getVoices().map(v => ({
label: v.name, label: v.name,
value: v.voiceURI, value: v.voiceURI,
default: v.default default: v.default
})) ?? [] })) ?? [])
}, },
volume: { volume: {
type: OptionType.SLIDER, type: OptionType.SLIDER,
@ -289,7 +221,73 @@ export default definePlugin({
description: "Undeafen Message (only self for now)", description: "Undeafen Message (only self for now)",
default: "{{USER}} undeafened" default: "{{USER}} undeafened"
} }
}; });
export default definePlugin({
name: "VcNarrator",
description: "Announces when users join, leave, or move voice channels via narrator",
authors: [Devs.Ven],
reporterTestable: ReporterTestable.None,
settings,
flux: {
VOICE_STATE_UPDATES({ voiceStates }: { voiceStates: VoiceState[]; }) {
const myGuildId = SelectedGuildStore.getGuildId();
const myChanId = SelectedChannelStore.getVoiceChannelId();
const myId = UserStore.getCurrentUser().id;
if (ChannelStore.getChannel(myChanId!)?.type === 13 /* Stage Channel */) return;
for (const state of voiceStates) {
const { userId, channelId, oldChannelId } = state;
const isMe = userId === myId;
if (!isMe) {
if (!myChanId) continue;
if (channelId !== myChanId && oldChannelId !== myChanId) continue;
}
const [type, id] = getTypeAndChannelId(state, isMe);
if (!type) continue;
const template = settings.store[type + "Message"];
const user = isMe && !settings.store.sayOwnName ? "" : UserStore.getUser(userId).username;
const displayName = user && ((UserStore.getUser(userId) as any).globalName ?? user);
const nickname = user && (GuildMemberStore.getNick(myGuildId, userId) ?? user);
const channel = ChannelStore.getChannel(id).name;
speak(formatText(template, user, channel, displayName, nickname));
// updateStatuses(type, state, isMe);
}
},
AUDIO_TOGGLE_SELF_MUTE() {
const chanId = SelectedChannelStore.getVoiceChannelId()!;
const s = VoiceStateStore.getVoiceStateForChannel(chanId) as VoiceState;
if (!s) return;
const event = s.mute || s.selfMute ? "unmute" : "mute";
speak(formatText(settings.store[event + "Message"], "", ChannelStore.getChannel(chanId).name, "", ""));
},
AUDIO_TOGGLE_SELF_DEAF() {
const chanId = SelectedChannelStore.getVoiceChannelId()!;
const s = VoiceStateStore.getVoiceStateForChannel(chanId) as VoiceState;
if (!s) return;
const event = s.deaf || s.selfDeaf ? "undeafen" : "deafen";
speak(formatText(settings.store[event + "Message"], "", ChannelStore.getChannel(chanId).name, "", ""));
}
},
start() {
if (typeof speechSynthesis === "undefined" || speechSynthesis.getVoices().length === 0) {
new Logger("VcNarrator").warn(
"SpeechSynthesis not supported or no Narrator voices found. Thus, this plugin will not work. Check my Settings for more info"
);
return;
}
}, },
settingsAboutComponent({ tempSettings: s }) { settingsAboutComponent({ tempSettings: s }) {
@ -299,7 +297,7 @@ export default definePlugin({
}, []); }, []);
const types = useMemo( const types = useMemo(
() => Object.keys(Vencord.Plugins.plugins.VcNarrator.options!).filter(k => k.endsWith("Message")).map(k => k.slice(0, -7)), () => Object.keys(settings.def).filter(k => k.endsWith("Message")).map(k => k.slice(0, -7)),
[], [],
); );
@ -335,7 +333,7 @@ export default definePlugin({
className={"vc-narrator-buttons"} className={"vc-narrator-buttons"}
> >
{types.map(t => ( {types.map(t => (
<Button key={t} onClick={() => playSample(s, t)}> <Button key={t} onClick={() => playSample(s as typeof settings.store, t)}>
{wordsToTitle([t])} {wordsToTitle([t])}
</Button> </Button>
))} ))}