Compare commits
11 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
214c101740 | ||
|
5a0e501829 | ||
|
92113da7c0 | ||
|
96f30a5359 | ||
|
ceb1f15188 | ||
|
626eb3613e | ||
|
3020fcc9bb | ||
|
bc0de3926c | ||
|
9820b79dfe | ||
|
ab811470fc | ||
|
e4162e7bd5 |
@ -39,10 +39,9 @@ Or use the [UserScript](https://raw.githubusercontent.com/Vencord/builds/main/Ve
|
||||
## Vencord Desktop
|
||||
|
||||
> **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. It is currently in beta and we have yet to implement some features like screensharing, but you can try the beta nonetheless
|
||||
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
|
||||
|
||||
[![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
|
||||
|
||||
[![Vencord Discord Server](https://invidget.switchblade.xyz/D9uwnFnqmd?theme=dark)](https://discord.gg/D9uwnFnqmd)
|
||||
https://discord.gg/D9uwnFnqmd
|
||||
|
||||
## Disclaimer
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "vencord",
|
||||
"private": "true",
|
||||
"version": "1.2.8",
|
||||
"version": "1.2.9",
|
||||
"description": "The cutest Discord client mod",
|
||||
"homepage": "https://github.com/Vendicated/Vencord#readme",
|
||||
"bugs": {
|
||||
|
@ -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) {
|
||||
const definedSettings: DefinedSettings<D> = {
|
||||
export function definePluginSettings<
|
||||
Def extends SettingsDefinition,
|
||||
Checks extends SettingsChecks<Def>,
|
||||
PrivateSettings extends object = {}
|
||||
>(def: Def, checks?: Checks) {
|
||||
const definedSettings: DefinedSettings<Def, Checks, PrivateSettings> = {
|
||||
get store() {
|
||||
if (!definedSettings.pluginName) throw new Error("Cannot access settings before plugin is initialized");
|
||||
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>[]
|
||||
).plugins[definedSettings.pluginName] as any,
|
||||
def,
|
||||
checks: checks ?? {},
|
||||
checks: checks ?? {} as any,
|
||||
pluginName: "",
|
||||
|
||||
withPrivateSettings<T>() {
|
||||
return this as DefinedSettings<D, C> & { store: T; };
|
||||
withPrivateSettings<T extends object>() {
|
||||
return this as DefinedSettings<Def, Checks, T>;
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -36,7 +36,8 @@ function Guilds(props: {
|
||||
// @ts-expect-error
|
||||
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) {
|
||||
const servers = scrollerProps.children.find(c => c?.props?.["aria-label"] === i18n.Messages.SERVERS);
|
||||
if (servers) scrollerProps.children = servers;
|
||||
|
@ -78,7 +78,7 @@ export default definePlugin({
|
||||
</Avatar>
|
||||
<div className={ProfileListClasses.listRowContent}>
|
||||
<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>
|
||||
</Clickable>
|
||||
));
|
||||
|
@ -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 type { Guild } from "discord-types/general";
|
||||
|
||||
import { settings } from "..";
|
||||
import { cl, getPermissionDescription, getPermissionString } from "../utils";
|
||||
import { PermissionAllowedIcon, PermissionDefaultIcon, PermissionDeniedIcon } from "./icons";
|
||||
|
||||
@ -108,7 +109,7 @@ function RolesAndUsersPermissionsComponent({ permissions, guild, modalProps, hea
|
||||
<div
|
||||
className={cl("perms-list-item", { "perms-list-item-active": selectedItemIndex === index })}
|
||||
onContextMenu={e => {
|
||||
if (permission.type === PermissionType.Role)
|
||||
if ((settings.store as any).unsafeViewAsRole && permission.type === PermissionType.Role)
|
||||
ContextMenu.open(e, () => (
|
||||
<RoleContextMenu
|
||||
guild={guild}
|
||||
|
@ -63,6 +63,7 @@
|
||||
grid-area: list;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
border-right: 2px solid var(--background-modifier-active);
|
||||
}
|
||||
|
||||
@ -77,6 +78,15 @@
|
||||
padding: 8px 5px;
|
||||
cursor: pointer;
|
||||
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 {
|
||||
@ -85,11 +95,6 @@
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.vc-permviewer-perms-list-item-active {
|
||||
background-color: var(--background-modifier-selected);
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.vc-permviewer-perms-role-circle {
|
||||
border-radius: 50%;
|
||||
width: 12px;
|
||||
|
@ -52,7 +52,7 @@ export default definePlugin({
|
||||
find: ".userTagNoNickname",
|
||||
replacement: {
|
||||
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
|
||||
@ -60,7 +60,7 @@ export default definePlugin({
|
||||
find: ".USER_PROFILE_ACTIVITY",
|
||||
replacement: {
|
||||
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"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
@ -19,17 +19,26 @@
|
||||
import { Settings } from "@api/Settings";
|
||||
import { VENCORD_USER_AGENT } from "@utils/constants";
|
||||
import { debounce } from "@utils/debounce";
|
||||
import { getCurrentChannel } from "@utils/discord";
|
||||
import { useAwaiter } from "@utils/react";
|
||||
import { findStoreLazy } from "@webpack";
|
||||
import { UserStore } from "@webpack/common";
|
||||
|
||||
import { settings } from "./settings";
|
||||
import { PronounCode, PronounMapping, PronounsResponse } from "./types";
|
||||
|
||||
const UserProfileStore = findStoreLazy("UserProfileStore");
|
||||
|
||||
export const enum PronounsFormat {
|
||||
Lowercase = "LOWERCASE",
|
||||
Capitalized = "CAPITALIZED"
|
||||
}
|
||||
|
||||
export const enum PronounSource {
|
||||
PreferPDB,
|
||||
PreferDiscord
|
||||
}
|
||||
|
||||
// A map of cached pronouns so the same request isn't sent twice
|
||||
const cache: Record<string, PronounCode> = {};
|
||||
// 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 {
|
||||
const [result] = useAwaiter(() => fetchPronouns(id), {
|
||||
fallbackValue: getCachedPronouns(id),
|
||||
function getDiscordPronouns(id: string) {
|
||||
return (
|
||||
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)
|
||||
});
|
||||
|
||||
// If the result is present and not "unspecified", and there is a mapping for the code, then return the mappings
|
||||
if (result && result !== "unspecified" && PronounMapping[result])
|
||||
return formatPronouns(result);
|
||||
if (result && result !== "unspecified")
|
||||
return Object.hasOwn(PronounMapping, result)
|
||||
? formatPronouns(result) // PronounDB
|
||||
: result; // Discord
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
export function useProfilePronouns(id: string) {
|
||||
const pronouns = useFormattedPronouns(id);
|
||||
export function useProfilePronouns(id: string, discordPronouns: string) {
|
||||
const pronouns = useFormattedPronouns(id, discordPronouns);
|
||||
|
||||
if (!settings.store.showInProfile) 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!
|
||||
export function getCachedPronouns(id: string): PronounCode | null {
|
||||
return cache[id] ?? null;
|
||||
export function getCachedPronouns(id: string, discordPronouns: string): string | 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
|
||||
export function fetchPronouns(id: string): Promise<PronounCode> {
|
||||
export function fetchPronouns(id: string, discordPronouns: string): Promise<string> {
|
||||
return new Promise(res => {
|
||||
// If cached, return the cached pronouns
|
||||
if (id in cache) res(getCachedPronouns(id)!);
|
||||
const cached = getCachedPronouns(id, discordPronouns);
|
||||
if (cached) return res(cached);
|
||||
|
||||
// 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
|
||||
else {
|
||||
requestQueue[id] = [res];
|
||||
bulkFetch();
|
||||
}
|
||||
requestQueue[id] = [res];
|
||||
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; };
|
||||
// For capitalized pronouns, just return the mapping (it is by default capitalized)
|
||||
if (pronounsFormat === PronounsFormat.Capitalized) return PronounMapping[pronouns];
|
||||
|
@ -19,7 +19,7 @@
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import { OptionType } from "@utils/types";
|
||||
|
||||
import { PronounsFormat } from "./pronoundbUtils";
|
||||
import { PronounsFormat, PronounSource } from "./pronoundbUtils";
|
||||
|
||||
export const settings = definePluginSettings({
|
||||
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: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Enable or disable showing pronouns for the current user",
|
||||
|
@ -419,6 +419,14 @@ export default definePlugin({
|
||||
match: /(?<=getChannels\(\i)(?=\))/,
|
||||
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"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
|
@ -65,6 +65,7 @@ const replacements = [
|
||||
["stupid", "baka"],
|
||||
["what", "nani"],
|
||||
["meow", "nya~"],
|
||||
["hello", "hewwo"],
|
||||
];
|
||||
|
||||
const settings = definePluginSettings({
|
||||
|
@ -24,7 +24,7 @@ import definePlugin from "@utils/types";
|
||||
import { findByCodeLazy } from "@webpack";
|
||||
import { UserStore, useState } from "@webpack/common";
|
||||
import type { User } from "discord-types/general";
|
||||
import type { ComponentType } from "react";
|
||||
import type { ComponentType, ReactNode } from "react";
|
||||
|
||||
const fetching = new Set<string>();
|
||||
const queue = new Queue(5);
|
||||
@ -36,7 +36,7 @@ interface MentionProps {
|
||||
channelId?: string;
|
||||
content: any;
|
||||
};
|
||||
parse: (content: any, props: MentionProps["props"]) => string[];
|
||||
parse: (content: any, props: MentionProps["props"]) => ReactNode;
|
||||
props: {
|
||||
key: string;
|
||||
formatInline: boolean;
|
||||
@ -72,7 +72,7 @@ function MentionWrapper({ data, UserMention, RoleMention, parse, props }: Mentio
|
||||
>
|
||||
<span
|
||||
onMouseEnter={() => {
|
||||
const mention = children?.[0];
|
||||
const mention = children?.[0]?.props?.children;
|
||||
if (typeof mention !== "string") return;
|
||||
|
||||
const id = mention.match(/<@(\d+)>/)?.[1];
|
||||
|
@ -44,7 +44,10 @@ const settings = definePluginSettings({
|
||||
],
|
||||
description: "Choose the greet mode"
|
||||
}
|
||||
});
|
||||
}).withPrivateSettings<{
|
||||
multiGreetChoices?: string[];
|
||||
unholyMultiGreetEnabled?: boolean;
|
||||
}>();
|
||||
|
||||
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; }) {
|
||||
const s = settings.use(["greetMode", "multiGreetChoices"] as any) as { greetMode: GreetMode, multiGreetChoices: string[]; };
|
||||
const s = settings.use(["greetMode", "multiGreetChoices"]);
|
||||
const { greetMode, multiGreetChoices = [] } = s;
|
||||
|
||||
return (
|
||||
@ -112,7 +115,7 @@ function GreetMenu({ stickers, channel, message }: { stickers: Sticker[], messag
|
||||
))}
|
||||
</Menu.MenuGroup>
|
||||
|
||||
{!(settings.store as any).unholyMultiGreetEnabled ? null : (
|
||||
{!settings.store.unholyMultiGreetEnabled ? null : (
|
||||
<>
|
||||
<Menu.MenuSeparator />
|
||||
|
||||
|
@ -31,4 +31,5 @@ export * from "./onceDefined";
|
||||
export * from "./onlyOnce";
|
||||
export * from "./patches";
|
||||
export * from "./Queue";
|
||||
export * from "./react";
|
||||
export * from "./text";
|
||||
|
@ -260,25 +260,29 @@ type SettingsStore<D extends SettingsDefinition> = {
|
||||
};
|
||||
|
||||
/** 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 */
|
||||
store: SettingsStore<D>;
|
||||
store: SettingsStore<Def> & PrivateSettings;
|
||||
/**
|
||||
* React hook for getting the settings for this plugin
|
||||
* @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 */
|
||||
def: D;
|
||||
def: Def;
|
||||
/** Setting methods with return values that could rely on other settings */
|
||||
checks: C;
|
||||
checks: Checks;
|
||||
/**
|
||||
* Name of the plugin these settings belong to,
|
||||
* will be an empty string until plugin is initialized
|
||||
*/
|
||||
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>>;
|
||||
|
Reference in New Issue
Block a user