aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/api/Badges.ts17
-rw-r--r--src/api/MemberListDecorators.ts65
-rw-r--r--src/api/MessageDecorations.ts63
-rw-r--r--src/api/index.ts12
-rw-r--r--src/plugins/apiBadges.tsx19
-rw-r--r--src/plugins/apiMemberListDecorators.ts42
-rw-r--r--src/plugins/apiMessageDecorations.ts35
-rw-r--r--src/plugins/platformIndicators.tsx145
8 files changed, 322 insertions, 76 deletions
diff --git a/src/api/Badges.ts b/src/api/Badges.ts
index 55e9b3a..3607f37 100644
--- a/src/api/Badges.ts
+++ b/src/api/Badges.ts
@@ -17,7 +17,7 @@
*/
import { User } from "discord-types/general";
-import { HTMLProps } from "react";
+import { ComponentType, HTMLProps } from "react";
import Plugins from "~plugins";
@@ -27,20 +27,21 @@ export enum BadgePosition {
}
export interface ProfileBadge {
- /** The tooltip to show on hover */
- tooltip: string;
+ /** The tooltip to show on hover. Required for image badges */
+ tooltip?: string;
+ /** Custom component for the badge (tooltip not included) */
+ component?: ComponentType<ProfileBadge & BadgeUserArgs>;
/** The custom image to use */
image?: string;
/** Action to perform when you click the badge */
onClick?(): void;
/** Should the user display this badge? */
shouldShow?(userInfo: BadgeUserArgs): boolean;
- /** Optional props (e.g. style) for the badge */
+ /** Optional props (e.g. style) for the badge, ignored for component badges */
props?: HTMLProps<HTMLImageElement>;
/** Insert at start or end? */
position?: BadgePosition;
-
- /** The badge name to display. Discord uses this, but we don't. */
+ /** The badge name to display, Discord uses this. Required for component badges */
key?: string;
}
@@ -70,8 +71,8 @@ export function inject(badgeArray: ProfileBadge[], args: BadgeUserArgs) {
for (const badge of Badges) {
if (!badge.shouldShow || badge.shouldShow(args)) {
badge.position === BadgePosition.START
- ? badgeArray.unshift(badge)
- : badgeArray.push(badge);
+ ? badgeArray.unshift({ ...badge, ...args })
+ : badgeArray.push({ ...badge, ...args });
}
}
(Plugins.BadgeAPI as any).addDonorBadge(badgeArray, args.user.id);
diff --git a/src/api/MemberListDecorators.ts b/src/api/MemberListDecorators.ts
new file mode 100644
index 0000000..fade2a7
--- /dev/null
+++ b/src/api/MemberListDecorators.ts
@@ -0,0 +1,65 @@
+/*
+ * Vencord, a modification for Discord's desktop app
+ * Copyright (c) 2022 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 { Channel, User } from "discord-types/general/index.js";
+
+interface DecoratorProps {
+ activities: any[];
+ canUseAvatarDecorations: boolean;
+ channel: Channel;
+ /**
+ * Only for DM members
+ */
+ channelName?: string;
+ /**
+ * Only for server members
+ */
+ currentUser?: User;
+ guildId?: string;
+ isMobile: boolean;
+ isOwner?: boolean;
+ isTyping: boolean;
+ selected: boolean;
+ status: string;
+ user: User;
+ [key: string]: any;
+}
+export type Decorator = (props: DecoratorProps) => JSX.Element | null;
+type OnlyIn = "guilds" | "dms";
+
+export const decorators = new Map<string, { decorator: Decorator, onlyIn?: OnlyIn; }>();
+
+export function addDecorator(identifier: string, decorator: Decorator, onlyIn?: OnlyIn) {
+ decorators.set(identifier, { decorator, onlyIn });
+}
+
+export function removeDecorator(identifier: string) {
+ decorators.delete(identifier);
+}
+
+export function __addDecoratorsToList(props: DecoratorProps): (JSX.Element | null)[] {
+ const isInGuild = !!(props.guildId);
+ return [...decorators.values()].map(decoratorObj => {
+ const { decorator, onlyIn } = decoratorObj;
+ // this can most likely be done cleaner
+ if (!onlyIn || (onlyIn === "guilds" && isInGuild) || (onlyIn === "dms" && !isInGuild)) {
+ return decorator(props);
+ }
+ return null;
+ });
+}
diff --git a/src/api/MessageDecorations.ts b/src/api/MessageDecorations.ts
new file mode 100644
index 0000000..d212b15
--- /dev/null
+++ b/src/api/MessageDecorations.ts
@@ -0,0 +1,63 @@
+/*
+ * Vencord, a modification for Discord's desktop app
+ * Copyright (c) 2022 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 { Channel, Message } from "discord-types/general/index.js";
+
+interface DecorationProps {
+ author: {
+ /**
+ * Will be username if the user has no nickname
+ */
+ nick: string;
+ iconRoleId: string;
+ guildMemberAvatar: string;
+ colorRoleName: string;
+ colorString: string;
+ };
+ channel: Channel;
+ compact: boolean;
+ decorations: {
+ /**
+ * Element for the [BOT] tag if there is one
+ */
+ 0: JSX.Element | null;
+ /**
+ * Other decorations (including ones added with this api)
+ */
+ 1: JSX.Element[];
+ };
+ message: Message;
+ [key: string]: any;
+}
+export type Decoration = (props: DecorationProps) => JSX.Element | null;
+
+export const decorations = new Map<string, Decoration>();
+
+export function addDecoration(identifier: string, decoration: Decoration) {
+ decorations.set(identifier, decoration);
+}
+
+export function removeDecoration(identifier: string) {
+ decorations.delete(identifier);
+}
+
+export function __addDecorationsToMessage(props: DecorationProps): (JSX.Element | null)[] {
+ return [...decorations.values()].map(decoration => {
+ return decoration(props);
+ });
+}
diff --git a/src/api/index.ts b/src/api/index.ts
index b74da6e..7e981e2 100644
--- a/src/api/index.ts
+++ b/src/api/index.ts
@@ -19,7 +19,9 @@
import * as $Badges from "./Badges";
import * as $Commands from "./Commands";
import * as $DataStore from "./DataStore";
+import * as $MemberListDecorators from "./MemberListDecorators";
import * as $MessageAccessories from "./MessageAccessories";
+import * as $MessageDecorations from "./MessageDecorations";
import * as $MessageEventsAPI from "./MessageEvents";
import * as $MessagePopover from "./MessagePopover";
import * as $Notices from "./Notices";
@@ -72,5 +74,13 @@ const Badges = $Badges;
* An API allowing you to add custom elements to the server list
*/
const ServerList = $ServerList;
+/**
+ * An API allowing you to add components as message accessories
+ */
+const MessageDecorations = $MessageDecorations;
+/**
+ * An API allowing you to add components to member list users, in both DM's and servers
+ */
+const MemberListDecorators = $MemberListDecorators;
-export { Badges, Commands, DataStore, MessageAccessories, MessageEvents, MessagePopover, Notices, ServerList };
+export { Badges, Commands, DataStore, MemberListDecorators, MessageAccessories, MessageDecorations, MessageEvents, MessagePopover, Notices, ServerList };
diff --git a/src/plugins/apiBadges.tsx b/src/plugins/apiBadges.tsx
index 77ea46e..72c19f3 100644
--- a/src/plugins/apiBadges.tsx
+++ b/src/plugins/apiBadges.tsx
@@ -66,11 +66,20 @@ export default definePlugin({
/* Patch the badge list component on user profiles */
{
find: "Messages.PROFILE_USER_BADGES,role:",
- replacement: {
- match: /src:(\w{1,3})\[(\w{1,3})\.key\],/,
- // <img src={badge.image ?? imageMap[badge.key]} {...badge.props} />
- replace: (_, imageMap, badge) => `src: ${badge}.image ?? ${imageMap}[${badge}.key], ...${badge}.props,`
- }
+ replacement: [
+ {
+ match: /src:(\w{1,3})\[(\w{1,3})\.key\],/,
+ // <img src={badge.image ?? imageMap[badge.key]} {...badge.props} />
+ replace: (_, imageMap, badge) => `src: ${badge}.image ?? ${imageMap}[${badge}.key], ...${badge}.props,`
+ },
+ {
+ match: /spacing:(\d{1,2}),children:(.{1,40}(.{1,2})\.jsx.+(.{1,2})\.onClick.+\)})},/,
+ // if the badge provides it's own component, render that instead of an image
+ // the badge also includes info about the user that has it (type BadgeUserArgs), which is why it's passed as props
+ replace: (_, s, origBadgeComponent, React, badge) =>
+ `spacing:${s},children:${badge}.component ? () => (0,${React}.jsx)(${badge}.component, { ...${badge} }) : ${origBadgeComponent}},`
+ }
+ ]
}
],
diff --git a/src/plugins/apiMemberListDecorators.ts b/src/plugins/apiMemberListDecorators.ts
new file mode 100644
index 0000000..6b8cffa
--- /dev/null
+++ b/src/plugins/apiMemberListDecorators.ts
@@ -0,0 +1,42 @@
+/*
+ * Vencord, a modification for Discord's desktop app
+ * Copyright (c) 2022 Vendicated and contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+*/
+
+import { Devs } from "@utils/constants";
+import definePlugin from "@utils/types";
+
+export default definePlugin({
+ name: "MemberListDecoratorsAPI",
+ description: "API to add decorators to member list (both in servers and DMs)",
+ authors: [Devs.TheSun],
+ patches: [
+ {
+ find: "lostPermissionTooltipText,",
+ replacement: {
+ match: /Fragment,{children:\[(.{30,80})\]/,
+ replace: "Fragment,{children:Vencord.Api.MemberListDecorators.__addDecoratorsToList(this.props).concat($1)"
+ }
+ },
+ {
+ find: "PrivateChannel.renderAvatar",
+ replacement: {
+ match: /(subText:(.{1,2})\.renderSubtitle\(\).{1,50}decorators):(.{30,100}:null)/,
+ replace: "$1:Vencord.Api.MemberListDecorators.__addDecoratorsToList($2.props).concat($3)"
+ }
+ }
+ ],
+});
diff --git a/src/plugins/apiMessageDecorations.ts b/src/plugins/apiMessageDecorations.ts
new file mode 100644
index 0000000..47f03f3
--- /dev/null
+++ b/src/plugins/apiMessageDecorations.ts
@@ -0,0 +1,35 @@
+/*
+ * Vencord, a modification for Discord's desktop app
+ * Copyright (c) 2022 Vendicated and contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+*/
+
+import { Devs } from "@utils/constants";
+import definePlugin from "@utils/types";
+
+export default definePlugin({
+ name: "MessageDecorationsAPI",
+ description: "API to add decorations to messages",
+ authors: [Devs.TheSun],
+ patches: [
+ {
+ find: ".withMentionPrefix",
+ replacement: {
+ match: /(\(\).roleDot.{10,50}{children:.{1,2})}\)/,
+ replace: "$1.concat(Vencord.Api.MessageDecorations.__addDecorationsToMessage(arguments[0]))})"
+ }
+ }
+ ],
+});
diff --git a/src/plugins/platformIndicators.tsx b/src/plugins/platformIndicators.tsx
index 5cae38f..8ca0677 100644
--- a/src/plugins/platformIndicators.tsx
+++ b/src/plugins/platformIndicators.tsx
@@ -16,6 +16,9 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
+import { addBadge, BadgePosition, ProfileBadge, removeBadge } from "@api/Badges";
+import { addDecorator, removeDecorator } from "@api/MemberListDecorators";
+import { addDecoration, removeDecoration } from "@api/MessageDecorations";
import { Settings } from "@api/settings";
import ErrorBoundary from "@components/ErrorBoundary";
import { Devs } from "@utils/constants";
@@ -59,10 +62,12 @@ const PlatformIcon = ({ platform, status }: { platform: Platform, status: string
return <Icon color={`var(--${getStatusColor(status)}`} tooltip={tooltip} />;
};
+const getStatus = (id: string): Record<Platform, string> => PresenceStore.getState()?.clientStatuses?.[id];
+
const PlatformIndicator = ({ user }: { user: User; }) => {
if (!user || user.bot) return null;
- const status = PresenceStore.getState()?.clientStatuses?.[user.id] as Record<Platform, string>;
+ const status = getStatus(user.id);
if (!status) return null;
const icons = Object.entries(status).map(([platform, status]) => (
@@ -75,79 +80,95 @@ const PlatformIndicator = ({ user }: { user: User; }) => {
if (!icons.length) return null;
- return (
- <div
+ const indicator =
+ <span
className="vc-platform-indicator"
- style={{
- display: "flex", alignItems: "center", marginLeft: "4px", gap: "4px"
- }}
+ style={{ marginLeft: "4px", gap: "4px" }}
>
{icons}
- </div>
- );
+ </span>;
+
+ return indicator;
+};
+
+const badge: ProfileBadge = {
+ component: PlatformIndicator,
+ position: BadgePosition.START,
+ shouldShow: userInfo => !!Object.keys(getStatus(userInfo.user.id) ?? {}).length,
+ key: "indicator"
+};
+
+const indicatorLocations = {
+ list: {
+ description: "In the member list",
+ onEnable: () => addDecorator("platform-indicator", props =>
+ <ErrorBoundary noop>
+ <PlatformIndicator user={props.user} />
+ </ErrorBoundary>
+ ),
+ onDisable: () => removeDecorator("platform-indicator")
+ },
+ badges: {
+ description: "In user profiles, as badges",
+ onEnable: () => addBadge(badge),
+ onDisable: () => removeBadge(badge)
+ },
+ messages: {
+ description: "Inside messages",
+ onEnable: () => addDecoration("platform-indicator", props =>
+ <ErrorBoundary noop>
+ <PlatformIndicator user={
+ props.decorations[1]?.find(i => i.key === "new-member")?.props.message?.author
+ } />
+ </ErrorBoundary>
+ ),
+ onDisable: () => removeDecoration("platform-indicator")
+ }
};
export default definePlugin({
name: "PlatformIndicators",
description: "Adds platform indicators (Desktop, Mobile, Web...) to users",
- authors: [Devs.kemo],
-
- patches: [
- {
- // Server member list decorators
- find: "this.renderPremium()",
- predicate: () => ["both", "list"].includes(Settings.plugins.PlatformIndicators.displayMode),
- replacement: {
- match: /this.renderPremium\(\)[^\]]*?\]/,
- replace: "$&.concat(Vencord.Plugins.plugins.PlatformIndicators.renderPlatformIndicators(this.props))"
- }
- },
- {
- // Dm list decorators
- find: "PrivateChannel.renderAvatar",
- predicate: () => ["both", "list"].includes(Settings.plugins.PlatformIndicators.displayMode),
- replacement: {
- match: /(subText:(.{1,3})\..+?decorators:)(.+?:null)/,
- replace: "$1[$3].concat(Vencord.Plugins.plugins.PlatformIndicators.renderPlatformIndicators($2.props))"
- }
- },
- {
- // User badges
- find: "Messages.PROFILE_USER_BADGES",
- predicate: () => ["both", "badges"].includes(Settings.plugins.PlatformIndicators.displayMode),
- replacement: {
- match: /(Messages\.PROFILE_USER_BADGES,role:"group",children:)(.+?\.key\)\}\)\))/,
- replace: "$1[Vencord.Plugins.plugins.PlatformIndicators.renderPlatformIndicators(e)].concat($2)"
+ authors: [Devs.kemo, Devs.TheSun],
+ dependencies: ["MessageDecorationsAPI", "MemberListDecoratorsAPI"],
+
+ 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;
}
- ],
- renderPlatformIndicators: ({ user }: { user: User; }) => (
- <ErrorBoundary noop>
- <PlatformIndicator user={user} />
- </ErrorBoundary>
- ),
+ Object.entries(indicatorLocations).forEach(([key, value]) => {
+ if (settings[key]) value.onEnable();
+ });
+ },
+
+ stop() {
+ Object.entries(indicatorLocations).forEach(([_, value]) => {
+ value.onDisable();
+ });
+ },
options: {
- displayMode: {
- type: OptionType.SELECT,
- description: "Where to display the platform indicators",
- restartNeeded: true,
- options: [
- {
- label: "Member List & Badges",
- value: "both",
- default: true
- },
- {
- label: "Member List Only",
- value: "list"
- },
- {
- label: "Badges Only",
- value: "badges"
- }
- ]
- },
+ ...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: false
+ }];
+ })
+ )
}
});