Merge branch 'Vendicated:main' into listenbrainz

This commit is contained in:
ConfiG 2025-02-13 03:05:08 +03:00 committed by GitHub
commit 271c4d3de1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
33 changed files with 411 additions and 126 deletions

View file

@ -1,7 +1,7 @@
{ {
"name": "vencord", "name": "vencord",
"private": "true", "private": "true",
"version": "1.11.3", "version": "1.11.4",
"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

@ -43,20 +43,21 @@ interface DecoratorProps {
export type MemberListDecoratorFactory = (props: DecoratorProps) => JSX.Element | null; export type MemberListDecoratorFactory = (props: DecoratorProps) => JSX.Element | null;
type OnlyIn = "guilds" | "dms"; type OnlyIn = "guilds" | "dms";
export const decorators = new Map<string, { render: MemberListDecoratorFactory, onlyIn?: OnlyIn; }>(); export const decoratorsFactories = new Map<string, { render: MemberListDecoratorFactory, onlyIn?: OnlyIn; }>();
export function addMemberListDecorator(identifier: string, render: MemberListDecoratorFactory, onlyIn?: OnlyIn) { export function addMemberListDecorator(identifier: string, render: MemberListDecoratorFactory, onlyIn?: OnlyIn) {
decorators.set(identifier, { render, onlyIn }); decoratorsFactories.set(identifier, { render, onlyIn });
} }
export function removeMemberListDecorator(identifier: string) { export function removeMemberListDecorator(identifier: string) {
decorators.delete(identifier); decoratorsFactories.delete(identifier);
} }
export function __getDecorators(props: DecoratorProps): (JSX.Element | null)[] { export function __getDecorators(props: DecoratorProps): JSX.Element {
const isInGuild = !!(props.guildId); const isInGuild = !!(props.guildId);
return Array.from(
decorators.entries(), const decorators = Array.from(
decoratorsFactories.entries(),
([key, { render: Decorator, onlyIn }]) => { ([key, { render: Decorator, onlyIn }]) => {
if ((onlyIn === "guilds" && !isInGuild) || (onlyIn === "dms" && isInGuild)) if ((onlyIn === "guilds" && !isInGuild) || (onlyIn === "dms" && isInGuild))
return null; return null;
@ -68,4 +69,10 @@ export function __getDecorators(props: DecoratorProps): (JSX.Element | null)[] {
); );
} }
); );
return (
<div className="vc-member-list-decorators-wrapper">
{decorators}
</div>
);
} }

View file

@ -48,23 +48,29 @@ export interface MessageDecorationProps {
} }
export type MessageDecorationFactory = (props: MessageDecorationProps) => JSX.Element | null; export type MessageDecorationFactory = (props: MessageDecorationProps) => JSX.Element | null;
export const decorations = new Map<string, MessageDecorationFactory>(); export const decorationsFactories = new Map<string, MessageDecorationFactory>();
export function addMessageDecoration(identifier: string, decoration: MessageDecorationFactory) { export function addMessageDecoration(identifier: string, decoration: MessageDecorationFactory) {
decorations.set(identifier, decoration); decorationsFactories.set(identifier, decoration);
} }
export function removeMessageDecoration(identifier: string) { export function removeMessageDecoration(identifier: string) {
decorations.delete(identifier); decorationsFactories.delete(identifier);
} }
export function __addDecorationsToMessage(props: MessageDecorationProps): (JSX.Element | null)[] { export function __addDecorationsToMessage(props: MessageDecorationProps): JSX.Element {
return Array.from( const decorations = Array.from(
decorations.entries(), decorationsFactories.entries(),
([key, Decoration]) => ( ([key, Decoration]) => (
<ErrorBoundary noop message={`Failed to render ${key} Message Decoration`} key={key}> <ErrorBoundary noop message={`Failed to render ${key} Message Decoration`} key={key}>
<Decoration {...props} /> <Decoration {...props} />
</ErrorBoundary> </ErrorBoundary>
) )
); );
return (
<div className="vc-message-decorations-wrapper">
{decorations}
</div>
);
} }

View file

@ -17,16 +17,22 @@
*/ */
import { Button } from "@webpack/common"; import { Button } from "@webpack/common";
import { ButtonProps } from "@webpack/types";
import { Heart } from "./Heart"; import { Heart } from "./Heart";
export default function DonateButton(props: any) { export default function DonateButton({
look = Button.Looks.LINK,
color = Button.Colors.TRANSPARENT,
...props
}: Partial<ButtonProps>) {
return ( return (
<Button <Button
{...props} {...props}
look={Button.Looks.LINK} look={look}
color={Button.Colors.TRANSPARENT} color={color}
onClick={() => VencordNative.native.openExternal("https://github.com/sponsors/Vendicated")} onClick={() => VencordNative.native.openExternal("https://github.com/sponsors/Vendicated")}
innerClassName="vc-donate-button"
> >
<Heart /> <Heart />
Donate Donate

View file

@ -16,14 +16,18 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
export function Heart() { import { classes } from "@utils/misc";
import { SVGProps } from "react";
export function Heart(props: SVGProps<SVGSVGElement>) {
return ( return (
<svg <svg
aria-hidden="true" aria-hidden="true"
height="16"
viewBox="0 0 16 16" viewBox="0 0 16 16"
height="16"
width="16" width="16"
style={{ marginRight: "0.5em", transform: "translateY(2px)" }} {...props}
className={classes("vc-heart-icon", props.className)}
> >
<path <path
fill="#db61a2" fill="#db61a2"

View file

@ -0,0 +1,77 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2023 Vendicated and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import "./specialCard.css";
import { classNameFactory } from "@api/Styles";
import { Card, Clickable, Forms, React } from "@webpack/common";
import type { PropsWithChildren } from "react";
const cl = classNameFactory("vc-special-");
interface StyledCardProps {
title: string;
subtitle?: string;
description: string;
cardImage?: string;
backgroundImage?: string;
backgroundColor?: string;
buttonTitle?: string;
buttonOnClick?: () => void;
}
export function SpecialCard({ title, subtitle, description, cardImage, backgroundImage, backgroundColor, buttonTitle, buttonOnClick: onClick, children }: PropsWithChildren<StyledCardProps>) {
const cardStyle: React.CSSProperties = {
backgroundColor: backgroundColor || "#9c85ef",
backgroundImage: `url(${backgroundImage || ""})`,
};
return (
<Card className={cl("card", "card-special")} style={cardStyle}>
<div className={cl("card-flex")}>
<div className={cl("card-flex-main")}>
<Forms.FormTitle className={cl("title")} tag="h5">{title}</Forms.FormTitle>
<Forms.FormText className={cl("subtitle")}>{subtitle}</Forms.FormText>
<Forms.FormText className={cl("text")}>{description}</Forms.FormText>
{children}
</div>
{cardImage && (
<div className={cl("image-container")}>
<img
role="presentation"
src={cardImage}
alt=""
className={cl("image")}
/>
</div>
)}
</div>
{buttonTitle && (
<>
<Forms.FormDivider className={cl("seperator")} />
<Clickable onClick={onClick} className={cl("hyperlink")}>
<Forms.FormText className={cl("hyperlink-text")}>
{buttonTitle}
</Forms.FormText>
</Clickable>
</>
)}
</Card>
);
}

View file

@ -20,29 +20,38 @@ import { openNotificationLogModal } from "@api/Notifications/notificationLog";
import { useSettings } from "@api/Settings"; import { useSettings } from "@api/Settings";
import { classNameFactory } from "@api/Styles"; import { classNameFactory } from "@api/Styles";
import DonateButton from "@components/DonateButton"; import DonateButton from "@components/DonateButton";
import { openContributorModal } from "@components/PluginSettings/ContributorModal";
import { openPluginModal } from "@components/PluginSettings/PluginModal"; import { openPluginModal } from "@components/PluginSettings/PluginModal";
import { gitRemote } from "@shared/vencordUserAgent"; import { gitRemote } from "@shared/vencordUserAgent";
import { DONOR_ROLE_ID, VENCORD_GUILD_ID } from "@utils/constants";
import { Margins } from "@utils/margins"; import { Margins } from "@utils/margins";
import { identity } from "@utils/misc"; import { identity, isPluginDev } from "@utils/misc";
import { relaunch, showItemInFolder } from "@utils/native"; import { relaunch, showItemInFolder } from "@utils/native";
import { useAwaiter } from "@utils/react"; import { useAwaiter } from "@utils/react";
import { Button, Card, Forms, React, Select, Switch } from "@webpack/common"; import { Button, Forms, GuildMemberStore, React, Select, Switch, UserStore } from "@webpack/common";
import BadgeAPI from "../../plugins/_api/badges";
import { Flex, FolderIcon, GithubIcon, LogIcon, PaintbrushIcon, RestartIcon } from ".."; import { Flex, FolderIcon, GithubIcon, LogIcon, PaintbrushIcon, RestartIcon } from "..";
import { openNotificationSettingsModal } from "./NotificationSettings"; import { openNotificationSettingsModal } from "./NotificationSettings";
import { QuickAction, QuickActionCard } from "./quickActions"; import { QuickAction, QuickActionCard } from "./quickActions";
import { SettingsTab, wrapTab } from "./shared"; import { SettingsTab, wrapTab } from "./shared";
import { SpecialCard } from "./SpecialCard";
const cl = classNameFactory("vc-settings-"); const cl = classNameFactory("vc-settings-");
const DEFAULT_DONATE_IMAGE = "https://cdn.discordapp.com/emojis/1026533090627174460.png"; const DEFAULT_DONATE_IMAGE = "https://cdn.discordapp.com/emojis/1026533090627174460.png";
const SHIGGY_DONATE_IMAGE = "https://media.discordapp.net/stickers/1039992459209490513.png"; const SHIGGY_DONATE_IMAGE = "https://media.discordapp.net/stickers/1039992459209490513.png";
const VENNIE_DONATOR_IMAGE = "https://cdn.discordapp.com/emojis/1238120638020063377.png";
const COZY_CONTRIB_IMAGE = "https://cdn.discordapp.com/emojis/1026533070955872337.png";
const DONOR_BACKGROUND_IMAGE = "https://media.discordapp.net/stickers/1311070116305436712.png?size=2048";
const CONTRIB_BACKGROUND_IMAGE = "https://media.discordapp.net/stickers/1311070166481895484.png?size=2048";
type KeysOfType<Object, Type> = { type KeysOfType<Object, Type> = {
[K in keyof Object]: Object[K] extends Type ? K : never; [K in keyof Object]: Object[K] extends Type ? K : never;
}[keyof Object]; }[keyof Object];
function VencordSettings() { function VencordSettings() {
const [settingsDir, , settingsDirPending] = useAwaiter(VencordNative.settings.getSettingsDir, { const [settingsDir, , settingsDirPending] = useAwaiter(VencordNative.settings.getSettingsDir, {
fallbackValue: "Loading..." fallbackValue: "Loading..."
@ -55,6 +64,8 @@ function VencordSettings() {
const isMac = navigator.platform.toLowerCase().startsWith("mac"); const isMac = navigator.platform.toLowerCase().startsWith("mac");
const needsVibrancySettings = IS_DISCORD_DESKTOP && isMac; const needsVibrancySettings = IS_DISCORD_DESKTOP && isMac;
const user = UserStore.getCurrentUser();
const Switches: Array<false | { const Switches: Array<false | {
key: KeysOfType<typeof settings, boolean>; key: KeysOfType<typeof settings, boolean>;
title: string; title: string;
@ -99,7 +110,44 @@ function VencordSettings() {
return ( return (
<SettingsTab title="Vencord Settings"> <SettingsTab title="Vencord Settings">
<DonateCard image={donateImage} /> {isDonor(user?.id)
? (
<SpecialCard
title="Donations"
subtitle="Thank you for donating!"
description="All Vencord users can see your badge! You can change it at any time by messaging @vending.machine."
cardImage={VENNIE_DONATOR_IMAGE}
backgroundImage={DONOR_BACKGROUND_IMAGE}
backgroundColor="#ED87A9"
>
<DonateButtonComponent />
</SpecialCard>
)
: (
<SpecialCard
title="Support the Project"
description="Please consider supporting the development of Vencord by donating!"
cardImage={donateImage}
backgroundImage={DONOR_BACKGROUND_IMAGE}
backgroundColor="#c3a3ce"
>
<DonateButtonComponent />
</SpecialCard>
)
}
{isPluginDev(user?.id) && (
<SpecialCard
title="Contributions"
subtitle="Thank you for contributing!"
description="Since you've contributed to Vencord you now have a cool new badge!"
cardImage={COZY_CONTRIB_IMAGE}
backgroundImage={CONTRIB_BACKGROUND_IMAGE}
backgroundColor="#EDCC87"
buttonTitle="See what you've contributed to"
buttonOnClick={() => openContributorModal(user)}
/>
)}
<Forms.FormSection title="Quick Actions"> <Forms.FormSection title="Quick Actions">
<QuickActionCard> <QuickActionCard>
<QuickAction <QuickAction
@ -239,31 +287,19 @@ function VencordSettings() {
); );
} }
interface DonateCardProps { function DonateButtonComponent() {
image: string;
}
function DonateCard({ image }: DonateCardProps) {
return ( return (
<Card className={cl("card", "donate")}> <DonateButton
<div> look={Button.Looks.FILLED}
<Forms.FormTitle tag="h5">Support the Project</Forms.FormTitle> color={Button.Colors.WHITE}
<Forms.FormText>Please consider supporting the development of Vencord by donating!</Forms.FormText> style={{ marginTop: "1em" }}
<DonateButton style={{ transform: "translateX(-1em)" }} /> />
</div>
<img
role="presentation"
src={image}
alt=""
height={128}
style={{
imageRendering: image === SHIGGY_DONATE_IMAGE ? "pixelated" : void 0,
marginLeft: "auto",
transform: image === DEFAULT_DONATE_IMAGE ? "rotate(10deg)" : void 0
}}
/>
</Card>
); );
} }
function isDonor(userId: string): boolean {
const donorBadges = BadgeAPI.getDonorBadges(userId);
return GuildMemberStore.getMember(VENCORD_GUILD_ID, userId)?.roles.includes(DONOR_ROLE_ID) || !!donorBadges;
}
export default wrapTab(VencordSettings, "Vencord Settings"); export default wrapTab(VencordSettings, "Vencord Settings");

View file

@ -1,12 +1,17 @@
.vc-settings-quickActions-card { .vc-settings-quickActions-card {
display: grid; display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, max-content)); grid-template-columns: repeat(3, 1fr);
gap: 0.5em; gap: 0.5em;
justify-content: center; padding: 0.5em;
padding: 0.5em 0;
margin-bottom: 1em; margin-bottom: 1em;
} }
@media (width <=1040px) {
.vc-settings-quickActions-card {
grid-template-columns: repeat(2, 1fr);
}
}
.vc-settings-quickActions-pill { .vc-settings-quickActions-pill {
all: unset; all: unset;
background: var(--background-secondary); background: var(--background-secondary);
@ -14,12 +19,16 @@
display: flex; display: flex;
align-items: center; align-items: center;
gap: 0.5em; gap: 0.5em;
padding: 8px 12px; padding: 8px 9px;
border-radius: 9999px; border-radius: 8px;
transition: 0.1s ease-out;
box-sizing: border-box;
} }
.vc-settings-quickActions-pill:hover { .vc-settings-quickActions-pill:hover {
background: var(--background-secondary-alt); background: var(--background-secondary-alt);
transform: translateY(-1px);
box-shadow: var(--elevation-high);
} }
.vc-settings-quickActions-pill:focus-visible { .vc-settings-quickActions-pill:focus-visible {

View file

@ -0,0 +1,92 @@
.vc-donate-button {
overflow: visible !important;
}
.vc-donate-button .vc-heart-icon {
transition: transform 0.3s;
}
.vc-donate-button:hover .vc-heart-icon {
transform: scale(1.1);
z-index: 10;
position: relative;
}
.vc-settings-card {
padding: 1em;
margin-bottom: 1em;
}
.vc-special-card-special {
padding: 1em 1.5em;
margin-bottom: 1em;
background-size: cover;
background-position: center;
}
.vc-special-card-flex {
display: flex;
flex-direction: row;
}
.vc-special-card-flex-main {
width: 100%;
}
.vc-special-title {
color: black;
}
.vc-special-subtitle {
color: black;
font-size: 1.2em;
font-weight: bold;
margin-top: 0.5em;
}
.vc-special-text {
color: black;
font-size: 1em;
margin-top: .75em;
white-space: pre-line;
}
.vc-special-seperator {
margin-top: .75em;
border-top: 1px solid white;
opacity: 0.4;
}
.vc-special-hyperlink {
margin-top: 1em;
cursor: pointer;
.vc-special-hyperlink-text {
color: black;
font-size: 1em;
font-weight: bold;
text-align: center;
transition: text-decoration 0.5s;
cursor: pointer;
}
&:hover .vc-special-hyperlink-text {
text-decoration: underline;
}
}
.vc-special-image-container {
display: flex;
justify-content: center;
align-items: center;
margin-left: 1em;
flex-shrink: 0;
width: 100px;
height: 100px;
border-radius: 50%;
background-color: white;
}
.vc-special-image {
width: 65%;
}

View file

@ -5,3 +5,8 @@
.vc-owner-crown-icon { .vc-owner-crown-icon {
color: var(--text-warning); color: var(--text-warning);
} }
.vc-heart-icon {
margin-right: 0.5em;
translate: 0 2px;
}

View file

@ -19,10 +19,15 @@
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import definePlugin from "@utils/types"; import definePlugin from "@utils/types";
import managedStyle from "./style.css?managed";
export default definePlugin({ export default definePlugin({
name: "MemberListDecoratorsAPI", name: "MemberListDecoratorsAPI",
description: "API to add decorators to member list (both in servers and DMs)", description: "API to add decorators to member list (both in servers and DMs)",
authors: [Devs.TheSun, Devs.Ven], authors: [Devs.TheSun, Devs.Ven],
managedStyle,
patches: [ patches: [
{ {
find: ".lostPermission)", find: ".lostPermission)",
@ -32,7 +37,7 @@ export default definePlugin({
replace: "$&vencordProps=$1," replace: "$&vencordProps=$1,"
}, { }, {
match: /#{intl::GUILD_OWNER}(?=.+?decorators:(\i)\(\)).+?\1=?\(\)=>.+?children:\[/, match: /#{intl::GUILD_OWNER}(?=.+?decorators:(\i)\(\)).+?\1=?\(\)=>.+?children:\[/,
replace: "$&...(typeof vencordProps=='undefined'?[]:Vencord.Api.MemberListDecorators.__getDecorators(vencordProps))," replace: "$&(typeof vencordProps=='undefined'?null:Vencord.Api.MemberListDecorators.__getDecorators(vencordProps)),"
} }
] ]
}, },
@ -40,8 +45,8 @@ export default definePlugin({
find: "PrivateChannel.renderAvatar", find: "PrivateChannel.renderAvatar",
replacement: { replacement: {
match: /decorators:(\i\.isSystemDM\(\))\?(.+?):null/, match: /decorators:(\i\.isSystemDM\(\))\?(.+?):null/,
replace: "decorators:[...Vencord.Api.MemberListDecorators.__getDecorators(arguments[0]), $1?$2:null]" replace: "decorators:[Vencord.Api.MemberListDecorators.__getDecorators(arguments[0]),$1?$2:null]"
} }
} }
], ]
}); });

View file

@ -0,0 +1,11 @@
.vc-member-list-decorators-wrapper {
display: flex;
align-items: center;
justify-content: center;
gap: 0.25em;
}
.vc-member-list-decorators-wrapper:not(:empty) {
/* Margin to match default Discord decorators */
margin-left: 0.25em;
}

View file

@ -19,17 +19,22 @@
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import definePlugin from "@utils/types"; import definePlugin from "@utils/types";
import managedStyle from "./style.css?managed";
export default definePlugin({ export default definePlugin({
name: "MessageDecorationsAPI", name: "MessageDecorationsAPI",
description: "API to add decorations to messages", description: "API to add decorations to messages",
authors: [Devs.TheSun], authors: [Devs.TheSun],
managedStyle,
patches: [ patches: [
{ {
find: '"Message Username"', find: '"Message Username"',
replacement: { replacement: {
match: /#{intl::GUILD_COMMUNICATION_DISABLED_BOTTOM_SHEET_TITLE}.+?}\),\i(?=\])/, match: /#{intl::GUILD_COMMUNICATION_DISABLED_BOTTOM_SHEET_TITLE}.+?}\),\i(?=\])/,
replace: "$&,...Vencord.Api.MessageDecorations.__addDecorationsToMessage(arguments[0])" replace: "$&,Vencord.Api.MessageDecorations.__addDecorationsToMessage(arguments[0])"
} }
} }
], ]
}); });

View file

@ -0,0 +1,18 @@
.vc-message-decorations-wrapper {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 0.25em;
}
.vc-message-decorations-wrapper:not(:empty) {
/* Margin to match default Discord decorators */
margin-left: 0.25em;
/* Align vertically */
position: relative;
vertical-align: top;
top: 0.1rem;
height: calc(1rem + 4px);
max-height: calc(1rem + 4px)
}

View file

@ -22,7 +22,7 @@ import ErrorBoundary from "@components/ErrorBoundary";
import { Flex } from "@components/Flex"; import { Flex } from "@components/Flex";
import { Link } from "@components/Link"; import { Link } from "@components/Link";
import { openUpdaterModal } from "@components/VencordSettings/UpdaterTab"; import { openUpdaterModal } from "@components/VencordSettings/UpdaterTab";
import { Devs, SUPPORT_CHANNEL_ID } from "@utils/constants"; import { CONTRIB_ROLE_ID, Devs, DONOR_ROLE_ID, KNOWN_ISSUES_CHANNEL_ID, REGULAR_ROLE_ID, SUPPORT_CHANNEL_ID, VENBOT_USER_ID, VENCORD_GUILD_ID } from "@utils/constants";
import { sendMessage } from "@utils/discord"; import { sendMessage } from "@utils/discord";
import { Logger } from "@utils/Logger"; import { Logger } from "@utils/Logger";
import { Margins } from "@utils/margins"; import { Margins } from "@utils/margins";
@ -40,9 +40,6 @@ import plugins, { PluginMeta } from "~plugins";
import SettingsPlugin from "./settings"; import SettingsPlugin from "./settings";
const VENCORD_GUILD_ID = "1015060230222131221";
const VENBOT_USER_ID = "1017176847865352332";
const KNOWN_ISSUES_CHANNEL_ID = "1222936386626129920";
const CodeBlockRe = /```js\n(.+?)```/s; const CodeBlockRe = /```js\n(.+?)```/s;
const AllowedChannelIds = [ const AllowedChannelIds = [
@ -52,9 +49,9 @@ const AllowedChannelIds = [
]; ];
const TrustedRolesIds = [ const TrustedRolesIds = [
"1026534353167208489", // contributor CONTRIB_ROLE_ID, // contributor
"1026504932959977532", // regular REGULAR_ROLE_ID, // regular
"1042507929485586532", // donor DONOR_ROLE_ID, // donor
]; ];
const AsyncFunction = async function () { }.constructor; const AsyncFunction = async function () { }.constructor;

View file

@ -23,7 +23,7 @@ import definePlugin, { ReporterTestable } from "@utils/types";
import { findByCodeLazy } from "@webpack"; import { findByCodeLazy } from "@webpack";
import { ApplicationAssetUtils, FluxDispatcher, Forms, Toasts } from "@webpack/common"; import { ApplicationAssetUtils, FluxDispatcher, Forms, Toasts } from "@webpack/common";
const fetchApplicationsRPC = findByCodeLazy("APPLICATION_RPC(", "Client ID"); const fetchApplicationsRPC = findByCodeLazy('"Invalid Origin"', ".application");
async function lookupAsset(applicationId: string, key: string): Promise<string> { async function lookupAsset(applicationId: string, key: string): Promise<string> {
return (await ApplicationAssetUtils.fetchAssetIds(applicationId, [key]))[0]; return (await ApplicationAssetUtils.fetchAssetIds(applicationId, [key]))[0];

View file

@ -185,8 +185,8 @@ export default definePlugin({
{ {
// Decide if we should render the expanded folder background if we are rendering the Better Folders sidebar // Decide if we should render the expanded folder background if we are rendering the Better Folders sidebar
predicate: () => settings.store.showFolderIcon !== FolderIconDisplay.Always, predicate: () => settings.store.showFolderIcon !== FolderIconDisplay.Always,
match: /(?<=\.isExpanded\),children:\[)/, match: /\.isExpanded\),.{0,30}children:\[/,
replace: "$self.shouldShowFolderIconAndBackground(!!arguments[0]?.isBetterFolders,arguments[0]?.betterFoldersExpandedIds)&&" replace: "$&$self.shouldShowFolderIconAndBackground(!!arguments[0]?.isBetterFolders,arguments[0]?.betterFoldersExpandedIds)&&"
}, },
{ {
// Decide if we should render the expanded folder icon if we are rendering the Better Folders sidebar // Decide if we should render the expanded folder icon if we are rendering the Better Folders sidebar

View file

@ -33,11 +33,11 @@ function getEmojiMarkdown(target: Target, copyUnicode: boolean): string {
: `:${emojiName}:`; : `:${emojiName}:`;
} }
const extension = target?.firstChild.src.match( const url = new URL(target.firstChild.src);
/https:\/\/cdn\.discordapp\.com\/emojis\/\d+\.(\w+)/ const hasParam = url.searchParams.get("animated") === "true";
)?.[1]; const isGif = url.pathname.endsWith(".gif");
return `<${extension === "gif" ? "a" : ""}:${emojiName.replace(/~\d+$/, "")}:${emojiId}>`; return `<${(hasParam || isGif) ? "a" : ""}:${emojiName.replace(/~\d+$/, "")}:${emojiId}>`;
} }
const settings = definePluginSettings({ const settings = definePluginSettings({
@ -55,7 +55,7 @@ export default definePlugin({
settings, settings,
contextMenus: { contextMenus: {
"expression-picker"(children, { target }: { target: Target }) { "expression-picker"(children, { target }: { target: Target; }) {
if (target.dataset.type !== "emoji") return; if (target.dataset.type !== "emoji") return;
children.push( children.push(

View file

@ -17,14 +17,13 @@
*/ */
import { definePluginSettings } from "@api/Settings"; import { definePluginSettings } from "@api/Settings";
import { disableStyle, enableStyle } from "@api/Styles";
import { getUserSettingLazy } from "@api/UserSettings"; import { getUserSettingLazy } from "@api/UserSettings";
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";
import { findComponentByCodeLazy } from "@webpack"; import { findComponentByCodeLazy } from "@webpack";
import style from "./style.css?managed"; import managedStyle from "./style.css?managed";
const Button = findComponentByCodeLazy(".NONE,disabled:", ".PANEL_BUTTON"); const Button = findComponentByCodeLazy(".NONE,disabled:", ".PANEL_BUTTON");
@ -90,6 +89,8 @@ export default definePlugin({
dependencies: ["UserSettingsAPI"], dependencies: ["UserSettingsAPI"],
settings, settings,
managedStyle,
patches: [ patches: [
{ {
find: "#{intl::ACCOUNT_SPEAKING_WHILE_MUTED}", find: "#{intl::ACCOUNT_SPEAKING_WHILE_MUTED}",
@ -102,11 +103,4 @@ export default definePlugin({
GameActivityToggleButton: ErrorBoundary.wrap(GameActivityToggleButton, { noop: true }), GameActivityToggleButton: ErrorBoundary.wrap(GameActivityToggleButton, { noop: true }),
start() {
enableStyle(style);
},
stop() {
disableStyle(style);
}
}); });

View file

@ -18,7 +18,6 @@
import { NavContextMenuPatchCallback } from "@api/ContextMenu"; import { NavContextMenuPatchCallback } from "@api/ContextMenu";
import { definePluginSettings } from "@api/Settings"; import { definePluginSettings } from "@api/Settings";
import { disableStyle, enableStyle } from "@api/Styles";
import { makeRange } from "@components/PluginSettings/components"; import { makeRange } from "@components/PluginSettings/components";
import { debounce } from "@shared/debounce"; import { debounce } from "@shared/debounce";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
@ -29,7 +28,7 @@ import type { Root } from "react-dom/client";
import { Magnifier, MagnifierProps } from "./components/Magnifier"; import { Magnifier, MagnifierProps } from "./components/Magnifier";
import { ELEMENT_ID } from "./constants"; import { ELEMENT_ID } from "./constants";
import styles from "./styles.css?managed"; import managedStyle from "./styles.css?managed";
export const settings = definePluginSettings({ export const settings = definePluginSettings({
saveZoomValues: { saveZoomValues: {
@ -160,6 +159,8 @@ export default definePlugin({
authors: [Devs.Aria], authors: [Devs.Aria],
tags: ["ImageUtilities"], tags: ["ImageUtilities"],
managedStyle,
patches: [ patches: [
{ {
find: ".contain,SCALE_DOWN:", find: ".contain,SCALE_DOWN:",
@ -252,14 +253,12 @@ export default definePlugin({
}, },
start() { start() {
enableStyle(styles);
this.element = document.createElement("div"); this.element = document.createElement("div");
this.element.classList.add("MagnifierContainer"); this.element.classList.add("MagnifierContainer");
document.body.appendChild(this.element); document.body.appendChild(this.element);
}, },
stop() { stop() {
disableStyle(styles);
// so componenetWillUnMount gets called if Magnifier component is still alive // so componenetWillUnMount gets called if Magnifier component is still alive
this.root && this.root.unmount(); this.root && this.root.unmount();
this.element?.remove(); this.element?.remove();

View file

@ -26,6 +26,7 @@ import { addMessageDecoration, removeMessageDecoration } from "@api/MessageDecor
import { addMessageClickListener, addMessagePreEditListener, addMessagePreSendListener, removeMessageClickListener, removeMessagePreEditListener, removeMessagePreSendListener } from "@api/MessageEvents"; import { addMessageClickListener, addMessagePreEditListener, addMessagePreSendListener, removeMessageClickListener, removeMessagePreEditListener, removeMessagePreSendListener } from "@api/MessageEvents";
import { addMessagePopoverButton, removeMessagePopoverButton } from "@api/MessagePopover"; import { addMessagePopoverButton, removeMessagePopoverButton } from "@api/MessagePopover";
import { Settings, SettingsStore } from "@api/Settings"; import { Settings, SettingsStore } from "@api/Settings";
import { disableStyle, enableStyle } from "@api/Styles";
import { Logger } from "@utils/Logger"; import { Logger } from "@utils/Logger";
import { canonicalizeFind } from "@utils/patches"; import { canonicalizeFind } from "@utils/patches";
import { Patch, Plugin, PluginDef, ReporterTestable, StartAt } from "@utils/types"; import { Patch, Plugin, PluginDef, ReporterTestable, StartAt } from "@utils/types";
@ -254,7 +255,7 @@ export function subscribeAllPluginsFluxEvents(fluxDispatcher: typeof FluxDispatc
export const startPlugin = traceFunction("startPlugin", function startPlugin(p: Plugin) { export const startPlugin = traceFunction("startPlugin", function startPlugin(p: Plugin) {
const { const {
name, commands, contextMenus, userProfileBadge, name, commands, contextMenus, managedStyle, userProfileBadge,
onBeforeMessageEdit, onBeforeMessageSend, onMessageClick, onBeforeMessageEdit, onBeforeMessageSend, onMessageClick,
renderChatBarButton, renderMemberListDecorator, renderMessageAccessory, renderMessageDecoration, renderMessagePopoverButton renderChatBarButton, renderMemberListDecorator, renderMessageAccessory, renderMessageDecoration, renderMessagePopoverButton
} = p; } = p;
@ -298,6 +299,8 @@ export const startPlugin = traceFunction("startPlugin", function startPlugin(p:
} }
} }
if (managedStyle) enableStyle(managedStyle);
if (userProfileBadge) addProfileBadge(userProfileBadge); if (userProfileBadge) addProfileBadge(userProfileBadge);
if (onBeforeMessageEdit) addMessagePreEditListener(onBeforeMessageEdit); if (onBeforeMessageEdit) addMessagePreEditListener(onBeforeMessageEdit);
@ -315,7 +318,7 @@ export const startPlugin = traceFunction("startPlugin", function startPlugin(p:
export const stopPlugin = traceFunction("stopPlugin", function stopPlugin(p: Plugin) { export const stopPlugin = traceFunction("stopPlugin", function stopPlugin(p: Plugin) {
const { const {
name, commands, contextMenus, userProfileBadge, name, commands, contextMenus, managedStyle, userProfileBadge,
onBeforeMessageEdit, onBeforeMessageSend, onMessageClick, onBeforeMessageEdit, onBeforeMessageSend, onMessageClick,
renderChatBarButton, renderMemberListDecorator, renderMessageAccessory, renderMessageDecoration, renderMessagePopoverButton renderChatBarButton, renderMemberListDecorator, renderMessageAccessory, renderMessageDecoration, renderMessagePopoverButton
} = p; } = p;
@ -357,6 +360,8 @@ export const stopPlugin = traceFunction("stopPlugin", function stopPlugin(p: Plu
} }
} }
if (managedStyle) disableStyle(managedStyle);
if (userProfileBadge) removeProfileBadge(userProfileBadge); if (userProfileBadge) removeProfileBadge(userProfileBadge);
if (onBeforeMessageEdit) removeMessagePreEditListener(onBeforeMessageEdit); if (onBeforeMessageEdit) removeMessagePreEditListener(onBeforeMessageEdit);

View file

@ -133,7 +133,7 @@ function getBadges({ userId }: BadgeUserArgs): ProfileBadge[] {
})); }));
} }
const PlatformIndicator = ({ user, wantMargin = true, wantTopMargin = false, small = false }: { user: User; wantMargin?: boolean; wantTopMargin?: boolean; small?: boolean; }) => { const PlatformIndicator = ({ user, small = false }: { user: User; small?: boolean; }) => {
if (!user || user.bot) return null; if (!user || user.bot) return null;
ensureOwnStatus(user); ensureOwnStatus(user);
@ -155,11 +155,7 @@ const PlatformIndicator = ({ user, wantMargin = true, wantTopMargin = false, sma
return ( return (
<span <span
className="vc-platform-indicator" className="vc-platform-indicator"
style={{ style={{ gap: "2px" }}
marginLeft: wantMargin ? 4 : 0,
top: wantTopMargin ? 2 : 0,
gap: 2
}}
> >
{icons} {icons}
</span> </span>
@ -190,7 +186,7 @@ const indicatorLocations = {
description: "Inside messages", description: "Inside messages",
onEnable: () => addMessageDecoration("platform-indicator", props => onEnable: () => addMessageDecoration("platform-indicator", props =>
<ErrorBoundary noop> <ErrorBoundary noop>
<PlatformIndicator user={props.message?.author} wantTopMargin={true} /> <PlatformIndicator user={props.message?.author} />
</ErrorBoundary> </ErrorBoundary>
), ),
onDisable: () => removeMessageDecoration("platform-indicator") onDisable: () => removeMessageDecoration("platform-indicator")

View file

@ -124,11 +124,11 @@ export default definePlugin({
}, },
// Voice Users // Voice Users
{ {
find: "renderPrioritySpeaker(){", find: ".usernameSpeaking]:",
replacement: [ replacement: [
{ {
match: /renderName\(\){.+?usernameSpeaking\]:.+?(?=children)/, match: /\.usernameSpeaking\]:.+?,(?=children)(?<=guildId:(\i),.+?user:(\i).+?)/,
replace: "$&style:$self.getColorStyle(this?.props?.user?.id,this?.props?.guildId)," replace: "$&style:$self.getColorStyle($2.id,$1),"
} }
], ],
predicate: () => settings.store.voiceUsers predicate: () => settings.store.voiceUsers

View file

@ -130,15 +130,13 @@ function VoiceChannelTooltip({ channel, isLocked }: VoiceChannelTooltipProps) {
interface VoiceChannelIndicatorProps { interface VoiceChannelIndicatorProps {
userId: string; userId: string;
isMessageIndicator?: boolean;
isProfile?: boolean;
isActionButton?: boolean; isActionButton?: boolean;
shouldHighlight?: boolean; shouldHighlight?: boolean;
} }
const clickTimers = {} as Record<string, any>; const clickTimers = {} as Record<string, any>;
export const VoiceChannelIndicator = ErrorBoundary.wrap(({ userId, isMessageIndicator, isProfile, isActionButton, shouldHighlight }: VoiceChannelIndicatorProps) => { export const VoiceChannelIndicator = ErrorBoundary.wrap(({ userId, isActionButton, shouldHighlight }: VoiceChannelIndicatorProps) => {
const channelId = useStateFromStores([VoiceStateStore], () => VoiceStateStore.getVoiceStateForUser(userId)?.channelId as string | undefined); const channelId = useStateFromStores([VoiceStateStore], () => VoiceStateStore.getVoiceStateForUser(userId)?.channelId as string | undefined);
const channel = channelId == null ? undefined : ChannelStore.getChannel(channelId); const channel = channelId == null ? undefined : ChannelStore.getChannel(channelId);
@ -182,7 +180,7 @@ export const VoiceChannelIndicator = ErrorBoundary.wrap(({ userId, isMessageIndi
{props => { {props => {
const iconProps: IconProps = { const iconProps: IconProps = {
...props, ...props,
className: classes(isMessageIndicator && cl("message-indicator"), (!isProfile && !isActionButton) && cl("speaker-margin"), isActionButton && ActionButtonClasses.actionButton, shouldHighlight && ActionButtonClasses.highlight), className: classes(isActionButton && ActionButtonClasses.actionButton, shouldHighlight && ActionButtonClasses.highlight),
size: isActionButton ? 20 : undefined, size: isActionButton ? 20 : undefined,
onClick onClick
}; };

View file

@ -60,7 +60,7 @@ export default definePlugin({
find: "#{intl::USER_PROFILE_LOAD_ERROR}", find: "#{intl::USER_PROFILE_LOAD_ERROR}",
replacement: { replacement: {
match: /(\.fetchError.+?\?)null/, match: /(\.fetchError.+?\?)null/,
replace: (_, rest) => `${rest}$self.VoiceChannelIndicator({userId:arguments[0]?.userId,isProfile:true})` replace: (_, rest) => `${rest}$self.VoiceChannelIndicator({userId:arguments[0]?.userId})`
}, },
predicate: () => settings.store.showInUserProfileModal predicate: () => settings.store.showInUserProfileModal
}, },
@ -99,7 +99,7 @@ export default definePlugin({
addMemberListDecorator("UserVoiceShow", ({ user }) => user == null ? null : <VoiceChannelIndicator userId={user.id} />); addMemberListDecorator("UserVoiceShow", ({ user }) => user == null ? null : <VoiceChannelIndicator userId={user.id} />);
} }
if (settings.store.showInMessages) { if (settings.store.showInMessages) {
addMessageDecoration("UserVoiceShow", ({ message }) => message?.author == null ? null : <VoiceChannelIndicator userId={message.author.id} isMessageIndicator />); addMessageDecoration("UserVoiceShow", ({ message }) => message?.author == null ? null : <VoiceChannelIndicator userId={message.author.id} />);
} }
}, },

View file

@ -13,16 +13,6 @@
color: var(--interactive-hover); color: var(--interactive-hover);
} }
.vc-uvs-speaker-margin {
margin-left: 4px;
}
.vc-uvs-message-indicator {
display: inline-flex;
top: 2.5px;
position: relative;
}
.vc-uvs-tooltip-container { .vc-uvs-tooltip-container {
max-width: 300px; max-width: 300px;
} }

View file

@ -18,7 +18,13 @@
export const WEBPACK_CHUNK = "webpackChunkdiscord_app"; export const WEBPACK_CHUNK = "webpackChunkdiscord_app";
export const REACT_GLOBAL = "Vencord.Webpack.Common.React"; export const REACT_GLOBAL = "Vencord.Webpack.Common.React";
export const VENBOT_USER_ID = "1017176847865352332";
export const VENCORD_GUILD_ID = "1015060230222131221";
export const DONOR_ROLE_ID = "1042507929485586532";
export const CONTRIB_ROLE_ID = "1026534353167208489";
export const REGULAR_ROLE_ID = "1026504932959977532";
export const SUPPORT_CHANNEL_ID = "1026515880080842772"; export const SUPPORT_CHANNEL_ID = "1026515880080842772";
export const KNOWN_ISSUES_CHANNEL_ID = "1222936386626129920";
export interface Dev { export interface Dev {
name: string; name: string;
@ -583,6 +589,10 @@ export const Devs = /* #__PURE__*/ Object.freeze({
name: "jamesbt365", name: "jamesbt365",
id: 158567567487795200n, id: 158567567487795200n,
}, },
samsam: {
name: "samsam",
id: 836452332387565589n,
},
} satisfies Record<string, Dev>); } satisfies Record<string, Dev>);
// iife so #__PURE__ works correctly // iife so #__PURE__ works correctly

View file

@ -60,7 +60,7 @@ export async function downloadSettingsBackup() {
} }
} }
const toast = (type: number, message: string) => const toast = (type: string, message: string) =>
Toasts.show({ Toasts.show({
type, type,
message, message,

View file

@ -150,6 +150,11 @@ export interface PluginDef {
tags?: string[]; tags?: string[];
/**
* Managed style to automatically enable and disable when the plugin is enabled or disabled
*/
managedStyle?: string;
userProfileBadge?: ProfileBadge; userProfileBadge?: ProfileBadge;
onMessageClick?: MessageClickListener; onMessageClick?: MessageClickListener;

View file

@ -57,7 +57,7 @@ export const Heading = waitForComponent<t.Heading>("Heading", filters.componentB
export const Select = waitForComponent<t.Select>("Select", filters.componentByCode('.selectPositionTop]:"top"===', '"Escape"===')); export const Select = waitForComponent<t.Select>("Select", filters.componentByCode('.selectPositionTop]:"top"===', '"Escape"==='));
export const SearchableSelect = waitForComponent<t.SearchableSelect>("SearchableSelect", filters.componentByCode('.selectPositionTop]:"top"===', ".multi]:")); export const SearchableSelect = waitForComponent<t.SearchableSelect>("SearchableSelect", filters.componentByCode('.selectPositionTop]:"top"===', ".multi]:"));
export const Slider = waitForComponent<t.Slider>("Slider", filters.componentByCode('"markDash".concat(')); export const Slider = waitForComponent<t.Slider>("Slider", filters.componentByCode('"markDash".concat('));
export const Popout = waitForComponent<t.Popout>("Popout", filters.componentByCode("ref:this.ref,preload:")); export const Popout = waitForComponent<t.Popout>("Popout", filters.componentByCode("ref:this.ref,", "renderPopout:this.renderPopout,"));
export const Dialog = waitForComponent<t.Dialog>("Dialog", filters.componentByCode('role:"dialog",tabIndex:-1')); export const Dialog = waitForComponent<t.Dialog>("Dialog", filters.componentByCode('role:"dialog",tabIndex:-1'));
export const TabBar = waitForComponent("TabBar", filters.componentByCode("ref:this.tabBarRef,className:")); export const TabBar = waitForComponent("TabBar", filters.componentByCode("ref:this.tabBarRef,className:"));
export const Paginator = waitForComponent<t.Paginator>("Paginator", filters.componentByCode('rel:"prev",children:')); export const Paginator = waitForComponent<t.Paginator>("Paginator", filters.componentByCode('rel:"prev",children:'));

View file

@ -152,7 +152,7 @@ export type ComboboxPopout = ComponentType<PropsWithChildren<{
}>>; }>>;
export type Button = ComponentType<PropsWithChildren<Omit<HTMLProps<HTMLButtonElement>, "size"> & { export interface ButtonProps extends PropsWithChildren<Omit<HTMLProps<HTMLButtonElement>, "size">> {
/** Button.Looks.FILLED */ /** Button.Looks.FILLED */
look?: string; look?: string;
/** Button.Colors.BRAND */ /** Button.Colors.BRAND */
@ -172,7 +172,9 @@ export type Button = ComponentType<PropsWithChildren<Omit<HTMLProps<HTMLButtonEl
submittingStartedLabel?: string; submittingStartedLabel?: string;
submittingFinishedLabel?: string; submittingFinishedLabel?: string;
}>> & { }
export type Button = ComponentType<ButtonProps> & {
BorderColors: Record<"BLACK" | "BRAND" | "BRAND_NEW" | "GREEN" | "LINK" | "PRIMARY" | "RED" | "TRANSPARENT" | "WHITE" | "YELLOW", string>; BorderColors: Record<"BLACK" | "BRAND" | "BRAND_NEW" | "GREEN" | "LINK" | "PRIMARY" | "RED" | "TRANSPARENT" | "WHITE" | "YELLOW", string>;
Colors: Record<"BRAND" | "RED" | "GREEN" | "YELLOW" | "PRIMARY" | "LINK" | "WHITE" | "BLACK" | "TRANSPARENT" | "BRAND_NEW" | "CUSTOM", string>; Colors: Record<"BRAND" | "RED" | "GREEN" | "YELLOW" | "PRIMARY" | "LINK" | "WHITE" | "BLACK" | "TRANSPARENT" | "BRAND_NEW" | "CUSTOM", string>;
Hovers: Record<"DEFAULT" | "BRAND" | "RED" | "GREEN" | "YELLOW" | "PRIMARY" | "LINK" | "WHITE" | "BLACK" | "TRANSPARENT", string>; Hovers: Record<"DEFAULT" | "BRAND" | "RED" | "GREEN" | "YELLOW" | "PRIMARY" | "LINK" | "WHITE" | "BLACK" | "TRANSPARENT", string>;

View file

@ -71,10 +71,15 @@ export let Alerts: t.Alerts;
waitFor(["show", "close"], m => Alerts = m); waitFor(["show", "close"], m => Alerts = m);
const ToastType = { const ToastType = {
MESSAGE: 0, MESSAGE: "message",
SUCCESS: 1, SUCCESS: "success",
FAILURE: 2, FAILURE: "failure",
CUSTOM: 3 CUSTOM: "custom",
CLIP: "clip",
LINK: "link",
FORWARD: "forward",
BOOKMARK: "bookmark",
CLOCK: "clock"
}; };
const ToastPosition = { const ToastPosition = {
TOP: 0, TOP: 0,
@ -87,7 +92,7 @@ export interface ToastData {
/** /**
* Toasts.Type * Toasts.Type
*/ */
type: number, type: string,
options?: ToastOptions; options?: ToastOptions;
} }
@ -110,7 +115,7 @@ export const Toasts = {
...{} as { ...{} as {
show(data: ToastData): void; show(data: ToastData): void;
pop(): void; pop(): void;
create(message: string, type: number, options?: ToastOptions): ToastData; create(message: string, type: string, options?: ToastOptions): ToastData;
} }
}; };

View file

@ -69,12 +69,12 @@ export const filters = {
m.constructor?.displayName === name, m.constructor?.displayName === name,
componentByCode: (...code: CodeFilter): FilterFn => { componentByCode: (...code: CodeFilter): FilterFn => {
const filter = filters.byCode(...code); const byCodeFilter = filters.byCode(...code);
return m => { const filter = m => {
let inner = m; let inner = m;
while (inner != null) { while (inner != null) {
if (filter(inner)) return true; if (byCodeFilter(inner)) return true;
else if (!inner.$$typeof) return false; else if (!inner.$$typeof) return false;
else if (inner.type) inner = inner.type; // memos else if (inner.type) inner = inner.type; // memos
else if (inner.render) inner = inner.render; // forwardRefs else if (inner.render) inner = inner.render; // forwardRefs
@ -83,6 +83,9 @@ export const filters = {
return false; return false;
}; };
filter.$$vencordProps = [...code];
return filter;
} }
}; };