Compare commits

...

11 Commits

Author SHA1 Message Date
V
214c101740 Bump to v1.2.9 2023-06-22 22:45:28 +02:00
V
5a0e501829 Fix ValidUser, BetterFolders & MutualGroupDms
Co-authored-by: Juby210 <31005896+Juby210@users.noreply.github.com>
Co-authored-by: Amia <9750071+aamiaa@users.noreply.github.com>
2023-06-22 22:35:59 +02:00
V
92113da7c0 Fix Vencord Web 2023-06-21 21:50:28 +02:00
V
96f30a5359 Update README.md 2023-06-21 21:07:48 +02:00
V
ceb1f15188 utils: export missing members 2023-06-21 21:04:04 +02:00
V
626eb3613e [skip ci] WelcomeStickerPicker: Clean up settings types 2023-06-21 02:05:01 +02:00
V
3020fcc9bb [skip ci] Improve typings for settings.withPrivateSettings 2023-06-21 02:00:59 +02:00
heckeroncrack
bc0de3926c Improve uwuify (#1295)
Co-authored-by: V <vendicated@riseup.net>
2023-06-20 22:11:59 +02:00
Nuckyz
9820b79dfe SHC: show active now voice members; PermViewer: improve styling (#1314) 2023-06-20 22:11:50 +02:00
V
ab811470fc Update README.md 2023-06-18 21:27:52 +02:00
V
e4162e7bd5 PronounDB: Source pronouns from both PronounDB & Discord (#1301)
* PronounDB: Source pronouns from both PronounDB & Discord

* jdsjdsajjasiofigvjodsjigfdjiogegjnegjnersjn
2023-06-17 03:50:59 +02:00
16 changed files with 117 additions and 52 deletions

View File

@ -39,10 +39,9 @@ Or use the [UserScript](https://raw.githubusercontent.com/Vencord/builds/main/Ve
## Vencord Desktop ## Vencord Desktop
> **Warning** > **Warning**
> This is an alternative app. It currently doesn't support screensharing or keybinds. If you just want to install to the normal Discord Desktop app, scroll up > This is an alternative app. It currently doesn't support keybinds and possibly some more features. If you just want to install to the normal Discord Desktop app, scroll up
As an alternative to the Discord Desktop app, Vencord also has its own standalone Desktop app that is snappier and lighter than Discord's official Desktop app
As an alternative to the Discord Desktop app, Vencord also has its own standalone Desktop app that is snappier and lighter than Discord's official Desktop app. It is currently in beta and we have yet to implement some features like screensharing, but you can try the beta nonetheless
[![Download Vencord Desktop](https://img.shields.io/github/v/release/Vencord/Desktop?label=Download%20Vencord%20Desktop&style=for-the-badge)](https://github.com/Vencord/Desktop#vencord-desktop) [![Download Vencord Desktop](https://img.shields.io/github/v/release/Vencord/Desktop?label=Download%20Vencord%20Desktop&style=for-the-badge)](https://github.com/Vencord/Desktop#vencord-desktop)
@ -50,7 +49,7 @@ As an alternative to the Discord Desktop app, Vencord also has its own standalon
## Join our Support/Community Server ## Join our Support/Community Server
[![Vencord Discord Server](https://invidget.switchblade.xyz/D9uwnFnqmd?theme=dark)](https://discord.gg/D9uwnFnqmd) https://discord.gg/D9uwnFnqmd
## Disclaimer ## Disclaimer

View File

@ -1,7 +1,7 @@
{ {
"name": "vencord", "name": "vencord",
"private": "true", "private": "true",
"version": "1.2.8", "version": "1.2.9",
"description": "The cutest Discord client mod", "description": "The cutest Discord client mod",
"homepage": "https://github.com/Vendicated/Vencord#readme", "homepage": "https://github.com/Vendicated/Vencord#readme",
"bugs": { "bugs": {

View File

@ -254,8 +254,12 @@ export function migratePluginSettings(name: string, ...oldNames: string[]) {
} }
} }
export function definePluginSettings<D extends SettingsDefinition, C extends SettingsChecks<D>>(def: D, checks?: C) { export function definePluginSettings<
const definedSettings: DefinedSettings<D> = { Def extends SettingsDefinition,
Checks extends SettingsChecks<Def>,
PrivateSettings extends object = {}
>(def: Def, checks?: Checks) {
const definedSettings: DefinedSettings<Def, Checks, PrivateSettings> = {
get store() { get store() {
if (!definedSettings.pluginName) throw new Error("Cannot access settings before plugin is initialized"); if (!definedSettings.pluginName) throw new Error("Cannot access settings before plugin is initialized");
return Settings.plugins[definedSettings.pluginName] as any; return Settings.plugins[definedSettings.pluginName] as any;
@ -264,11 +268,11 @@ export function definePluginSettings<D extends SettingsDefinition, C extends Set
settings?.map(name => `plugins.${definedSettings.pluginName}.${name}`) as UseSettings<Settings>[] settings?.map(name => `plugins.${definedSettings.pluginName}.${name}`) as UseSettings<Settings>[]
).plugins[definedSettings.pluginName] as any, ).plugins[definedSettings.pluginName] as any,
def, def,
checks: checks ?? {}, checks: checks ?? {} as any,
pluginName: "", pluginName: "",
withPrivateSettings<T>() { withPrivateSettings<T extends object>() {
return this as DefinedSettings<D, C> & { store: T; }; return this as DefinedSettings<Def, Checks, T>;
} }
}; };

View File

@ -36,7 +36,8 @@ function Guilds(props: {
// @ts-expect-error // @ts-expect-error
const res = Vencord.Plugins.plugins.BetterFolders.Guilds(props); const res = Vencord.Plugins.plugins.BetterFolders.Guilds(props);
const scrollerProps = res.props.children?.props?.children?.[1]?.props; // TODO: Make this better
const scrollerProps = res.props.children?.props?.children?.props?.children?.[1]?.props;
if (scrollerProps?.children) { if (scrollerProps?.children) {
const servers = scrollerProps.children.find(c => c?.props?.["aria-label"] === i18n.Messages.SERVERS); const servers = scrollerProps.children.find(c => c?.props?.["aria-label"] === i18n.Messages.SERVERS);
if (servers) scrollerProps.children = servers; if (servers) scrollerProps.children = servers;

View File

@ -78,7 +78,7 @@ export default definePlugin({
</Avatar> </Avatar>
<div className={ProfileListClasses.listRowContent}> <div className={ProfileListClasses.listRowContent}>
<div className={ProfileListClasses.listName}>{getGroupDMName(c)}</div> <div className={ProfileListClasses.listName}>{getGroupDMName(c)}</div>
<div className={GuildLabelClasses.guildNick}>{c.recipients.length} Members</div> <div className={GuildLabelClasses.guildNick}>{c.recipients.length + 1} Members</div>
</div> </div>
</Clickable> </Clickable>
)); ));

View File

@ -23,6 +23,7 @@ import { ModalCloseButton, ModalContent, ModalHeader, ModalProps, ModalRoot, Mod
import { ContextMenu, FluxDispatcher, GuildMemberStore, Menu, PermissionsBits, Text, Tooltip, useEffect, UserStore, useState, useStateFromStores } from "@webpack/common"; import { ContextMenu, FluxDispatcher, GuildMemberStore, Menu, PermissionsBits, Text, Tooltip, useEffect, UserStore, useState, useStateFromStores } from "@webpack/common";
import type { Guild } from "discord-types/general"; import type { Guild } from "discord-types/general";
import { settings } from "..";
import { cl, getPermissionDescription, getPermissionString } from "../utils"; import { cl, getPermissionDescription, getPermissionString } from "../utils";
import { PermissionAllowedIcon, PermissionDefaultIcon, PermissionDeniedIcon } from "./icons"; import { PermissionAllowedIcon, PermissionDefaultIcon, PermissionDeniedIcon } from "./icons";
@ -108,7 +109,7 @@ function RolesAndUsersPermissionsComponent({ permissions, guild, modalProps, hea
<div <div
className={cl("perms-list-item", { "perms-list-item-active": selectedItemIndex === index })} className={cl("perms-list-item", { "perms-list-item-active": selectedItemIndex === index })}
onContextMenu={e => { onContextMenu={e => {
if (permission.type === PermissionType.Role) if ((settings.store as any).unsafeViewAsRole && permission.type === PermissionType.Role)
ContextMenu.open(e, () => ( ContextMenu.open(e, () => (
<RoleContextMenu <RoleContextMenu
guild={guild} guild={guild}

View File

@ -63,6 +63,7 @@
grid-area: list; grid-area: list;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 2px;
border-right: 2px solid var(--background-modifier-active); border-right: 2px solid var(--background-modifier-active);
} }
@ -77,6 +78,15 @@
padding: 8px 5px; padding: 8px 5px;
cursor: pointer; cursor: pointer;
width: 230px; width: 230px;
border-radius: 5px;
}
.vc-permviewer-perms-list-item:hover {
background-color: var(--background-modifier-hover);
}
.vc-permviewer-perms-list-item-active {
background-color: var(--background-modifier-selected);
} }
.vc-permviewer-perms-list-item > div { .vc-permviewer-perms-list-item > div {
@ -85,11 +95,6 @@
overflow: hidden; overflow: hidden;
} }
.vc-permviewer-perms-list-item-active {
background-color: var(--background-modifier-selected);
border-radius: 5px;
}
.vc-permviewer-perms-role-circle { .vc-permviewer-perms-role-circle {
border-radius: 50%; border-radius: 50%;
width: 12px; width: 12px;

View File

@ -52,7 +52,7 @@ export default definePlugin({
find: ".userTagNoNickname", find: ".userTagNoNickname",
replacement: { replacement: {
match: /=(\i)\.pronouns/, match: /=(\i)\.pronouns/,
replace: "=$self.useProfilePronouns($1.user.id)" replace: "=$self.useProfilePronouns($1.user.id,$1.pronouns)"
} }
}, },
// Patch the profile modal username header to use our pronoun hook instead of Discord's pronouns // Patch the profile modal username header to use our pronoun hook instead of Discord's pronouns
@ -60,7 +60,7 @@ export default definePlugin({
find: ".USER_PROFILE_ACTIVITY", find: ".USER_PROFILE_ACTIVITY",
replacement: { replacement: {
match: /\).showPronouns/, match: /\).showPronouns/,
replace: ").showPronouns||true;const vcPronounce=$self.useProfilePronouns(arguments[0].user.id);if(arguments[0].displayProfile)arguments[0].displayProfile.pronouns=vcPronounce" replace: ").showPronouns||true;const vcPronounce=$self.useProfilePronouns(arguments[0].user.id,arguments[0].displayProfile?.pronouns);if(arguments[0].displayProfile&&vcPronounce)arguments[0].displayProfile.pronouns=vcPronounce"
} }
} }
], ],

View File

@ -19,17 +19,26 @@
import { Settings } from "@api/Settings"; import { Settings } from "@api/Settings";
import { VENCORD_USER_AGENT } from "@utils/constants"; import { VENCORD_USER_AGENT } from "@utils/constants";
import { debounce } from "@utils/debounce"; import { debounce } from "@utils/debounce";
import { getCurrentChannel } from "@utils/discord";
import { useAwaiter } from "@utils/react"; import { useAwaiter } from "@utils/react";
import { findStoreLazy } from "@webpack";
import { UserStore } from "@webpack/common"; import { UserStore } from "@webpack/common";
import { settings } from "./settings"; import { settings } from "./settings";
import { PronounCode, PronounMapping, PronounsResponse } from "./types"; import { PronounCode, PronounMapping, PronounsResponse } from "./types";
const UserProfileStore = findStoreLazy("UserProfileStore");
export const enum PronounsFormat { export const enum PronounsFormat {
Lowercase = "LOWERCASE", Lowercase = "LOWERCASE",
Capitalized = "CAPITALIZED" Capitalized = "CAPITALIZED"
} }
export const enum PronounSource {
PreferPDB,
PreferDiscord
}
// A map of cached pronouns so the same request isn't sent twice // A map of cached pronouns so the same request isn't sent twice
const cache: Record<string, PronounCode> = {}; const cache: Record<string, PronounCode> = {};
// A map of ids and callbacks that should be triggered on fetch // A map of ids and callbacks that should be triggered on fetch
@ -46,21 +55,29 @@ const bulkFetch = debounce(async () => {
} }
}); });
export function useFormattedPronouns(id: string): string | null { function getDiscordPronouns(id: string) {
const [result] = useAwaiter(() => fetchPronouns(id), { return (
fallbackValue: getCachedPronouns(id), UserProfileStore.getGuildMemberProfile(id, getCurrentChannel()?.guild_id)?.pronouns
|| UserProfileStore.getUserProfile(id)?.pronouns
);
}
export function useFormattedPronouns(id: string, discordPronouns: string = getDiscordPronouns(id)): string | null {
const [result] = useAwaiter(() => fetchPronouns(id, discordPronouns), {
fallbackValue: getCachedPronouns(id, discordPronouns),
onError: e => console.error("Fetching pronouns failed: ", e) onError: e => console.error("Fetching pronouns failed: ", e)
}); });
// If the result is present and not "unspecified", and there is a mapping for the code, then return the mappings if (result && result !== "unspecified")
if (result && result !== "unspecified" && PronounMapping[result]) return Object.hasOwn(PronounMapping, result)
return formatPronouns(result); ? formatPronouns(result) // PronounDB
: result; // Discord
return null; return null;
} }
export function useProfilePronouns(id: string) { export function useProfilePronouns(id: string, discordPronouns: string) {
const pronouns = useFormattedPronouns(id); const pronouns = useFormattedPronouns(id, discordPronouns);
if (!settings.store.showInProfile) return null; if (!settings.store.showInProfile) return null;
if (!settings.store.showSelf && id === UserStore.getCurrentUser().id) return null; if (!settings.store.showSelf && id === UserStore.getCurrentUser().id) return null;
@ -70,22 +87,28 @@ export function useProfilePronouns(id: string) {
// Gets the cached pronouns, if you're too impatient for a promise! // Gets the cached pronouns, if you're too impatient for a promise!
export function getCachedPronouns(id: string): PronounCode | null { export function getCachedPronouns(id: string, discordPronouns: string): string | null {
return cache[id] ?? null; if (settings.store.pronounSource === PronounSource.PreferDiscord && discordPronouns)
return discordPronouns;
const cached = cache[id];
if (cached && cached !== "unspecified") return cached;
return discordPronouns || cached || null;
} }
// Fetches the pronouns for one id, returning a promise that resolves if it was cached, or once the request is completed // Fetches the pronouns for one id, returning a promise that resolves if it was cached, or once the request is completed
export function fetchPronouns(id: string): Promise<PronounCode> { export function fetchPronouns(id: string, discordPronouns: string): Promise<string> {
return new Promise(res => { return new Promise(res => {
// If cached, return the cached pronouns const cached = getCachedPronouns(id, discordPronouns);
if (id in cache) res(getCachedPronouns(id)!); if (cached) return res(cached);
// If there is already a request added, then just add this callback to it // If there is already a request added, then just add this callback to it
else if (id in requestQueue) requestQueue[id].push(res); if (id in requestQueue) return requestQueue[id].push(res);
// If not already added, then add it and call the debounced function to make sure the request gets executed // If not already added, then add it and call the debounced function to make sure the request gets executed
else {
requestQueue[id] = [res]; requestQueue[id] = [res];
bulkFetch(); bulkFetch();
}
}); });
} }
@ -116,7 +139,7 @@ async function bulkFetchPronouns(ids: string[]): Promise<PronounsResponse> {
} }
} }
export function formatPronouns(pronouns: PronounCode): string { export function formatPronouns(pronouns: string): string {
const { pronounsFormat } = Settings.plugins.PronounDB as { pronounsFormat: PronounsFormat, enabled: boolean; }; const { pronounsFormat } = Settings.plugins.PronounDB as { pronounsFormat: PronounsFormat, enabled: boolean; };
// For capitalized pronouns, just return the mapping (it is by default capitalized) // For capitalized pronouns, just return the mapping (it is by default capitalized)
if (pronounsFormat === PronounsFormat.Capitalized) return PronounMapping[pronouns]; if (pronounsFormat === PronounsFormat.Capitalized) return PronounMapping[pronouns];

View File

@ -19,7 +19,7 @@
import { definePluginSettings } from "@api/Settings"; import { definePluginSettings } from "@api/Settings";
import { OptionType } from "@utils/types"; import { OptionType } from "@utils/types";
import { PronounsFormat } from "./pronoundbUtils"; import { PronounsFormat, PronounSource } from "./pronoundbUtils";
export const settings = definePluginSettings({ export const settings = definePluginSettings({
pronounsFormat: { pronounsFormat: {
@ -37,6 +37,21 @@ export const settings = definePluginSettings({
} }
] ]
}, },
pronounSource: {
type: OptionType.SELECT,
description: "Where to source pronouns from",
options: [
{
label: "Prefer PronounDB, fall back to Discord",
value: PronounSource.PreferPDB,
default: true
},
{
label: "Prefer Discord, fall back to PronounDB (might lead to inconsistency between pronouns in chat and profile)",
value: PronounSource.PreferDiscord
}
]
},
showSelf: { showSelf: {
type: OptionType.BOOLEAN, type: OptionType.BOOLEAN,
description: "Enable or disable showing pronouns for the current user", description: "Enable or disable showing pronouns for the current user",

View File

@ -419,6 +419,14 @@ export default definePlugin({
match: /(?<=getChannels\(\i)(?=\))/, match: /(?<=getChannels\(\i)(?=\))/,
replace: ",true" replace: ",true"
} }
},
{
find: '.displayName="NowPlayingViewStore"',
replacement: {
// Make active now voice states on hiddenl channels
match: /(getVoiceStateForUser.{0,150}?)&&\i\.\i\.canWithPartialContext.{0,20}VIEW_CHANNEL.+?}\)(?=\?)/,
replace: "$1"
}
} }
], ],

View File

@ -65,6 +65,7 @@ const replacements = [
["stupid", "baka"], ["stupid", "baka"],
["what", "nani"], ["what", "nani"],
["meow", "nya~"], ["meow", "nya~"],
["hello", "hewwo"],
]; ];
const settings = definePluginSettings({ const settings = definePluginSettings({

View File

@ -24,7 +24,7 @@ import definePlugin from "@utils/types";
import { findByCodeLazy } from "@webpack"; import { findByCodeLazy } from "@webpack";
import { UserStore, useState } from "@webpack/common"; import { UserStore, useState } from "@webpack/common";
import type { User } from "discord-types/general"; import type { User } from "discord-types/general";
import type { ComponentType } from "react"; import type { ComponentType, ReactNode } from "react";
const fetching = new Set<string>(); const fetching = new Set<string>();
const queue = new Queue(5); const queue = new Queue(5);
@ -36,7 +36,7 @@ interface MentionProps {
channelId?: string; channelId?: string;
content: any; content: any;
}; };
parse: (content: any, props: MentionProps["props"]) => string[]; parse: (content: any, props: MentionProps["props"]) => ReactNode;
props: { props: {
key: string; key: string;
formatInline: boolean; formatInline: boolean;
@ -72,7 +72,7 @@ function MentionWrapper({ data, UserMention, RoleMention, parse, props }: Mentio
> >
<span <span
onMouseEnter={() => { onMouseEnter={() => {
const mention = children?.[0]; const mention = children?.[0]?.props?.children;
if (typeof mention !== "string") return; if (typeof mention !== "string") return;
const id = mention.match(/<@(\d+)>/)?.[1]; const id = mention.match(/<@(\d+)>/)?.[1];

View File

@ -44,7 +44,10 @@ const settings = definePluginSettings({
], ],
description: "Choose the greet mode" description: "Choose the greet mode"
} }
}); }).withPrivateSettings<{
multiGreetChoices?: string[];
unholyMultiGreetEnabled?: boolean;
}>();
const MessageActions = findByPropsLazy("sendGreetMessage"); const MessageActions = findByPropsLazy("sendGreetMessage");
@ -73,7 +76,7 @@ function greet(channel: Channel, message: Message, stickers: string[]) {
function GreetMenu({ stickers, channel, message }: { stickers: Sticker[], message: Message, channel: Channel; }) { function GreetMenu({ stickers, channel, message }: { stickers: Sticker[], message: Message, channel: Channel; }) {
const s = settings.use(["greetMode", "multiGreetChoices"] as any) as { greetMode: GreetMode, multiGreetChoices: string[]; }; const s = settings.use(["greetMode", "multiGreetChoices"]);
const { greetMode, multiGreetChoices = [] } = s; const { greetMode, multiGreetChoices = [] } = s;
return ( return (
@ -112,7 +115,7 @@ function GreetMenu({ stickers, channel, message }: { stickers: Sticker[], messag
))} ))}
</Menu.MenuGroup> </Menu.MenuGroup>
{!(settings.store as any).unholyMultiGreetEnabled ? null : ( {!settings.store.unholyMultiGreetEnabled ? null : (
<> <>
<Menu.MenuSeparator /> <Menu.MenuSeparator />

View File

@ -31,4 +31,5 @@ export * from "./onceDefined";
export * from "./onlyOnce"; export * from "./onlyOnce";
export * from "./patches"; export * from "./patches";
export * from "./Queue"; export * from "./Queue";
export * from "./react";
export * from "./text"; export * from "./text";

View File

@ -260,25 +260,29 @@ type SettingsStore<D extends SettingsDefinition> = {
}; };
/** An instance of defined plugin settings */ /** An instance of defined plugin settings */
export interface DefinedSettings<D extends SettingsDefinition = SettingsDefinition, C extends SettingsChecks<D> = {}> { export interface DefinedSettings<
Def extends SettingsDefinition = SettingsDefinition,
Checks extends SettingsChecks<Def> = {},
PrivateSettings extends object = {}
> {
/** Shorthand for `Vencord.Settings.plugins.PluginName`, but with typings */ /** Shorthand for `Vencord.Settings.plugins.PluginName`, but with typings */
store: SettingsStore<D>; store: SettingsStore<Def> & PrivateSettings;
/** /**
* React hook for getting the settings for this plugin * React hook for getting the settings for this plugin
* @param filter optional filter to avoid rerenders for irrelavent settings * @param filter optional filter to avoid rerenders for irrelavent settings
*/ */
use<F extends Extract<keyof D, string>>(filter?: F[]): Pick<SettingsStore<D>, F>; use<F extends Extract<keyof Def | keyof PrivateSettings, string>>(filter?: F[]): Pick<SettingsStore<Def> & PrivateSettings, F>;
/** Definitions of each setting */ /** Definitions of each setting */
def: D; def: Def;
/** Setting methods with return values that could rely on other settings */ /** Setting methods with return values that could rely on other settings */
checks: C; checks: Checks;
/** /**
* Name of the plugin these settings belong to, * Name of the plugin these settings belong to,
* will be an empty string until plugin is initialized * will be an empty string until plugin is initialized
*/ */
pluginName: string; pluginName: string;
withPrivateSettings<T>(): this & { store: T; }; withPrivateSettings<T extends object>(): DefinedSettings<Def, Checks, T>;
} }
export type PartialExcept<T, R extends keyof T> = Partial<T> & Required<Pick<T, R>>; export type PartialExcept<T, R extends keyof T> = Partial<T> & Required<Pick<T, R>>;