From f80f3cdd12453a840c2aace4164db34128f1ee6c Mon Sep 17 00:00:00 2001 From: thororen1234 <78185467+thororen1234@users.noreply.github.com> Date: Fri, 21 Mar 2025 23:46:24 -0400 Subject: [PATCH] Revert "Drop BetterActivities" This reverts commit d3d4f3b789a971ef8b556bf3119b827305232e84. --- README.md | 3 +- .../betterActivities/components/Caret.tsx | 13 + .../components/SpotifyIcon.tsx | 11 + .../components/TwitchIcon.tsx | 11 + .../betterActivities/index.tsx | 317 ++++++++++++++++++ .../betterActivities/styles.css | 99 ++++++ src/equicordplugins/betterActivities/types.ts | 82 +++++ 7 files changed, 535 insertions(+), 1 deletion(-) create mode 100644 src/equicordplugins/betterActivities/components/Caret.tsx create mode 100644 src/equicordplugins/betterActivities/components/SpotifyIcon.tsx create mode 100644 src/equicordplugins/betterActivities/components/TwitchIcon.tsx create mode 100644 src/equicordplugins/betterActivities/index.tsx create mode 100644 src/equicordplugins/betterActivities/styles.css create mode 100644 src/equicordplugins/betterActivities/types.ts diff --git a/README.md b/README.md index 3d83f51b..7c8a3cf2 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ You can join our [discord server](https://discord.gg/5Xh2W87egW) for commits, ch ### Extra included plugins
-163 additional plugins +164 additional plugins ### All Platforms - AllCallTimers by MaxHerbold & D3SOX @@ -20,6 +20,7 @@ You can join our [discord server](https://discord.gg/5Xh2W87egW) for commits, ch - Anammox by Kyuuhachi - AtSomeone by Joona - BannersEverywhere by ImLvna & AutumnVN +- BetterActivities by D3SOX, Arjix, AutumnVN - BetterAudioPlayer by Creations - BetterBanReasons by Inbestigator - BetterBlockedUsers by TheArmagan diff --git a/src/equicordplugins/betterActivities/components/Caret.tsx b/src/equicordplugins/betterActivities/components/Caret.tsx new file mode 100644 index 00000000..b948d435 --- /dev/null +++ b/src/equicordplugins/betterActivities/components/Caret.tsx @@ -0,0 +1,13 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2024 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +export function Caret({ disabled, direction }: { disabled: boolean; direction: "left" | "right"; }) { + return ( + + + + ); +} diff --git a/src/equicordplugins/betterActivities/components/SpotifyIcon.tsx b/src/equicordplugins/betterActivities/components/SpotifyIcon.tsx new file mode 100644 index 00000000..9210169e --- /dev/null +++ b/src/equicordplugins/betterActivities/components/SpotifyIcon.tsx @@ -0,0 +1,11 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2024 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import type { SVGProps } from "react"; + +export function SpotifyIcon(props: SVGProps) { + return (); +} diff --git a/src/equicordplugins/betterActivities/components/TwitchIcon.tsx b/src/equicordplugins/betterActivities/components/TwitchIcon.tsx new file mode 100644 index 00000000..f0246c16 --- /dev/null +++ b/src/equicordplugins/betterActivities/components/TwitchIcon.tsx @@ -0,0 +1,11 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2024 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import type { SVGProps } from "react"; + +export function TwitchIcon(props: SVGProps) { + return (); +} diff --git a/src/equicordplugins/betterActivities/index.tsx b/src/equicordplugins/betterActivities/index.tsx new file mode 100644 index 00000000..8b40ba00 --- /dev/null +++ b/src/equicordplugins/betterActivities/index.tsx @@ -0,0 +1,317 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2024 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import "./styles.css"; + +import { definePluginSettings, migratePluginSettings } from "@api/Settings"; +import { classNameFactory } from "@api/Styles"; +import ErrorBoundary from "@components/ErrorBoundary"; +import { Devs } from "@utils/constants"; +import definePlugin, { OptionType } from "@utils/types"; +import { findByPropsLazy, findComponentByCodeLazy, findStoreLazy } from "@webpack"; +import { React, Tooltip, UserStore } from "@webpack/common"; +import { User } from "discord-types/general"; +import { JSX } from "react"; + +import { SpotifyIcon } from "./components/SpotifyIcon"; +import { TwitchIcon } from "./components/TwitchIcon"; +import { Activity, ActivityListIcon, Application, ApplicationIcon, IconCSSProperties } from "./types"; + +const settings = definePluginSettings({ + memberList: { + type: OptionType.BOOLEAN, + description: "Show activity icons in the member list", + default: true, + restartNeeded: true, + }, + iconSize: { + type: OptionType.SLIDER, + description: "Size of the activity icons", + markers: [10, 15, 20], + default: 15, + stickToMarkers: false, + }, + specialFirst: { + type: OptionType.BOOLEAN, + description: "Show special activities first (Currently Spotify and Twitch)", + default: true, + restartNeeded: false, + }, + renderGifs: { + type: OptionType.BOOLEAN, + description: "Allow rendering GIFs", + default: true, + restartNeeded: false, + }, + showAppDescriptions: { + type: OptionType.BOOLEAN, + description: "Show application descriptions in the activity tooltip", + default: true, + restartNeeded: false, + }, + divider: { + type: OptionType.COMPONENT, + description: "", + component: () => ( +
+ ), + }, + allActivitiesStyle: { + type: OptionType.SELECT, + description: "Style for showing all activities", + options: [ + { + default: true, + label: "Carousel", + value: "carousel", + }, + { + label: "List", + value: "list", + }, + ] + } +}); + +const cl = classNameFactory("vc-bactivities-"); + +const ApplicationStore: { + getApplication: (id: string) => Application | null; +} = findStoreLazy("ApplicationStore"); + +const { fetchApplication }: { + fetchApplication: (id: string) => Promise; +} = findByPropsLazy("fetchApplication"); + +const ActivityView = findComponentByCodeLazy<{ + activity: Activity | null; + user: User; + application?: Application; + currentUser: User; +}>('location:"UserProfileActivityCard",'); + +// if discord one day decides to change their icon this needs to be updated +const DefaultActivityIcon = findComponentByCodeLazy("M6,7 L2,7 L2,6 L6,6 L6,7 Z M8,5 L2,5 L2,4 L8,4 L8,5 Z M8,3 L2,3 L2,2 L8,2 L8,3 Z M8.88888889,0 L1.11111111,0 C0.494444444,0 0,0.494444444 0,1.11111111 L0,8.88888889 C0,9.50253861 0.497461389,10 1.11111111,10 L8.88888889,10 C9.50253861,10 10,9.50253861 10,8.88888889 L10,1.11111111 C10,0.494444444 9.5,0 8.88888889,0 Z"); + +const fetchedApplications = new Map(); + +const xboxUrl = "https://discord.com/assets/9a15d086141be29d9fcd.png"; // TODO: replace with "renderXboxImage"? + +const ActivityTooltip = ({ activity, application, user }: Readonly<{ activity: Activity, application?: Application, user: User; }>) => { + const currentUser = UserStore.getCurrentUser(); + if (!currentUser) return null; + return ( + +
+ +
+
+ ); +}; + +function getApplicationIcons(activities: Activity[], preferSmall = false) { + const applicationIcons: ApplicationIcon[] = []; + const applications = activities.filter(activity => activity.application_id || activity.platform); + + for (const activity of applications) { + const { assets, application_id, platform } = activity; + if (!application_id && !platform) { + continue; + } + + if (assets) { + const addImage = (image: string, alt: string) => { + if (image.startsWith("mp:")) { + const discordMediaLink = `https://media.discordapp.net/${image.replace(/mp:/, "")}`; + if (settings.store.renderGifs || !discordMediaLink.endsWith(".gif")) { + applicationIcons.push({ + image: { src: discordMediaLink, alt }, + activity + }); + } + } else { + const src = `https://cdn.discordapp.com/app-assets/${application_id}/${image}.png`; + applicationIcons.push({ + image: { src, alt }, + activity + }); + } + }; + + const smallImage = assets.small_image; + const smallText = assets.small_text ?? "Small Text"; + const largeImage = assets.large_image; + const largeText = assets.large_text ?? "Large Text"; + if (preferSmall) { + if (smallImage) { + addImage(smallImage, smallText); + } else if (largeImage) { + addImage(largeImage, largeText); + } + } else { + if (largeImage) { + addImage(largeImage, largeText); + } else if (smallImage) { + addImage(smallImage, smallText); + } + } + } else if (application_id) { + let application = ApplicationStore.getApplication(application_id); + if (!application) { + if (fetchedApplications.has(application_id)) { + application = fetchedApplications.get(application_id) as Application | null; + } else { + fetchedApplications.set(application_id, null); + fetchApplication(application_id).then(app => { + fetchedApplications.set(application_id, app); + }).catch(console.error); + } + } + + if (application) { + if (application.icon) { + const src = `https://cdn.discordapp.com/app-icons/${application.id}/${application.icon}.png`; + applicationIcons.push({ + image: { src, alt: application.name }, + activity, + application + }); + } else if (platform === "xbox") { + applicationIcons.push({ + image: { src: xboxUrl, alt: "Xbox" }, + activity, + application + }); + } + } else if (platform === "xbox") { + applicationIcons.push({ + image: { src: xboxUrl, alt: "Xbox" }, + activity + }); + } + } else if (platform === "xbox") { + applicationIcons.push({ + image: { src: xboxUrl, alt: "Xbox" }, + activity + }); + } + } + + return applicationIcons; +} + +migratePluginSettings("BetterActivities", "MemberListActivities"); + +export default definePlugin({ + name: "BetterActivities", + description: "Shows activity icons in the member list and allows showing all activities", + authors: [Devs.D3SOX, Devs.Arjix, Devs.AutumnVN], + tags: ["activity"], + + settings, + + patchActivityList: ({ activities, user }: { activities: Activity[], user: User; }): JSX.Element | null => { + const icons: ActivityListIcon[] = []; + + const applicationIcons = getApplicationIcons(activities); + if (applicationIcons.length) { + const compareImageSource = (a: ApplicationIcon, b: ApplicationIcon) => { + return a.image.src === b.image.src; + }; + const uniqueIcons = applicationIcons.filter((element, index, array) => { + return array.findIndex(el => compareImageSource(el, element)) === index; + }); + for (const appIcon of uniqueIcons) { + icons.push({ + iconElement: , + tooltip: + }); + } + } + + const addActivityIcon = (activityName: string, IconComponent: React.ComponentType) => { + const activityIndex = activities.findIndex(({ name }) => name === activityName); + if (activityIndex !== -1) { + const activity = activities[activityIndex]; + const iconObject: ActivityListIcon = { + iconElement: , + tooltip: + }; + + if (settings.store.specialFirst) { + icons.unshift(iconObject); + } else { + icons.splice(activityIndex, 0, iconObject); + } + } + }; + addActivityIcon("Twitch", TwitchIcon); + addActivityIcon("Spotify", SpotifyIcon); + + if (icons.length) { + const iconStyle: IconCSSProperties = { + "--icon-size": `${settings.store.iconSize}px`, + }; + + return +
+ {icons.map(({ iconElement, tooltip }, i) => ( +
+ {tooltip ? + {({ onMouseEnter, onMouseLeave }) => ( +
+ {iconElement} +
+ )} +
: iconElement} +
+ ))} +
+
; + } else { + // Show default icon when there are no custom icons + // We need to filter out custom statuses + const shouldShow = activities.filter(a => a.type !== 4).length !== icons.length; + if (shouldShow) { + return ; + } + } + + return null; + }, + + patches: [ + { + find: "activity_status_cleanup", + replacement: { + match: /activityStatusCleanupEnabled:!0/, + replace: "activityStatusCleanupEnabled:!1", + } + }, + { + // Patch activity icons + find: '"activity-status-web"', + replacement: { + match: /(?<=hasQuest:\i\}=(\i).*?)\(null==\i?\?void 0:\i.some\(\i\.\i\)\)/, + replace: "$self.patchActivityList($1),false" + }, + predicate: () => settings.store.memberList, + } + ], +}); diff --git a/src/equicordplugins/betterActivities/styles.css b/src/equicordplugins/betterActivities/styles.css new file mode 100644 index 00000000..32c80f55 --- /dev/null +++ b/src/equicordplugins/betterActivities/styles.css @@ -0,0 +1,99 @@ +.vc-bactivities-row { + display: flex; + flex-wrap: nowrap; + align-items: center; + margin-left: 5px; + text-align: center; + gap: 3px; +} + +.vc-bactivities-icon { + height: var(--icon-size); + width: var(--icon-size); +} + +.vc-bactivities-icon img { + width: var(--icon-size); + height: var(--icon-size); + object-fit: cover; + border-radius: 50%; +} + +[class*="tooltip_"]:has(.vc-bactivities-activity-tooltip) { + max-width: 280px; +} + +.vc-bactivities-activity-tooltip { + margin: -5px; +} + +.vc-bactivities-caret-left, +.vc-bactivities-caret-right { + color: #ddd; +} + +.vc-bactivities-caret-left { + transform: rotate(90deg); +} + +.vc-bactivities-caret-right { + transform: rotate(-90deg); +} + +.vc-bactivities-controls { + display: flex; + align-items: center; + justify-content: space-between; + padding: 5px; + background: var(--background-secondary-alt); + border-radius: 3px; + flex: 1 0; + margin-top: 10px; +} + +.vc-bactivities-controls [class^="vc-activities-caret-"] { + display: inline-flex; + align-items: center; + justify-content: center; + cursor: pointer; + border-radius: 3px; + background-color: #ffffff4d; +} + +.vc-bactivities-controls [class^="vc-activities-caret-"].disabled { + cursor: not-allowed; + opacity: 0.3; +} + +.vc-bactivities-controls [class^="vc-activities-caret-"]:hover:not(.disabled) { + background: var(--background-modifier-accent); +} + +.vc-bactivities-controls .carousel { + display: flex; + align-items: center; +} + +.vc-bactivities-controls .carousel .dot { + margin: 0 4px; + width: 10px; + cursor: pointer; + height: 10px; + border-radius: 100px; + background: var(--interactive-muted); + transition: background 0.3s; + opacity: 0.6; +} + +.vc-bactivities-controls .carousel .dot:hover:not(.selected) { + opacity: 1; +} + +.vc-bactivities-controls .carousel .dot.selected { + opacity: 1; + background: var(--dot-color, var(--brand-500)); +} + +.vc-bactivities-controls-tooltip { + --background-floating: var(--background-secondary); +} diff --git a/src/equicordplugins/betterActivities/types.ts b/src/equicordplugins/betterActivities/types.ts new file mode 100644 index 00000000..8f24dae5 --- /dev/null +++ b/src/equicordplugins/betterActivities/types.ts @@ -0,0 +1,82 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2024 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { CSSProperties, ImgHTMLAttributes, JSX } from "react"; + +export interface Timestamp { + start?: number; + end?: number; +} + +export interface Activity { + created_at: number; + id: string; + name: string; + type: number; + emoji?: { + animated: boolean; + id: string; + name: string; + }; + state?: string; + flags?: number; + sync_id?: string; + details?: string; + application_id?: string; + assets?: { + large_text?: string; + large_image?: string; + small_text?: string; + small_image?: string; + }; + timestamps?: Timestamp; + platform?: string; +} + +export interface Application { + id: string; + name: string; + icon: string; + description: string; + summary: string; + type: number; + hook: boolean; + guild_id: string; + executables: Executable[]; + verify_key: string; + publishers: Developer[]; + developers: Developer[]; + flags: number; +} + +export interface Developer { + id: string; + name: string; +} + +export interface Executable { + os: string; + name: string; + is_launcher: boolean; +} + +export interface ApplicationIcon { + image: ImgHTMLAttributes & { + src: string; + alt: string; + }; + activity: Activity; + application?: Application; +} + +export interface ActivityListIcon { + iconElement: JSX.Element; + tooltip?: JSX.Element | string; +} + +export interface IconCSSProperties extends CSSProperties { + "--icon-size": string; +}