Compare commits

...

73 commits

Author SHA1 Message Date
Nico
dff25f96de
Merge 726094aa6f into 8afd79dd50 2024-09-18 11:56:45 +02:00
Vendicated
8afd79dd50
add Icons to webpack commons
Some checks are pending
Sync to Codeberg / codeberg (push) Waiting to run
test / test (push) Waiting to run
2024-09-18 01:36:52 +02:00
Vendicated
65c5897dc3
remove need to depend on CommandsAPI 2024-09-18 01:26:25 +02:00
Nuckyz
6cce8a8bc4
Experiments: Allow clips to be recorded without streaming
Some checks failed
Sync to Codeberg / codeberg (push) Has been cancelled
test / test (push) Has been cancelled
2024-09-17 14:30:23 -03:00
Nuckyz
1848b16536
ReviewDB: Fix in panel profile 2024-09-17 14:30:23 -03:00
Kyuuhachi
c572116b97
BetterSettings: Add submenu for plugins (#2858)
Co-authored-by: Vendicated <vendicated@riseup.net>
2024-09-17 15:40:11 +00:00
Lumap
e26986f66a
AppleMusicRichPresence: fix formatting when listening to radio (#2869)
Co-authored-by: Ryan Cao <70191398+ryanccn@users.noreply.github.com>
Co-authored-by: v <vendicated@riseup.net>
2024-09-17 17:29:46 +02:00
Nico
726094aa6f
chore: thx github web editor.... 2024-08-02 10:16:55 +02:00
Nico
57c8f6915c
fix(betterActivities): avoid conditional hook calls
Prevents crashing when the activities length changes
2024-08-02 10:14:35 +02:00
D3SOX
32476dd1df
fix(betterActivities): avoid passing specific props to all activities
Now only the general props are used and the application is fetched for other activities
2024-07-29 13:19:08 +02:00
D3SOX
811397de3c
chore(betterActivities): remove unused code 2024-07-29 13:17:14 +02:00
D3SOX
4a95c367e3
feat(betterActivities): use ActivityView in tooltip 2024-07-19 06:23:45 +02:00
Nico
a2e089716a
fix(betterActivities): only patch BiteSizePopout type
We don't want to patch the user modal activity tab for example
2024-07-11 22:16:37 +02:00
Nico
5824528add
fix(betterActivities): handle fetchApplication errors 2024-07-10 21:17:58 +02:00
D3SOX
853efa7a90
fix(betterActivities): handle xbox edge case 2024-07-10 21:12:27 +02:00
D3SOX
f80593aef8
fix(betterActivities): patch location including application prop 2024-07-10 21:03:58 +02:00
D3SOX
917ae898c2
fix(betterActivities): correctly pass props 2024-07-10 14:32:48 +02:00
D3SOX
c6560e8446
fix(betterActivities): use newer activity view
Discord removed the old ones now
2024-07-08 22:43:45 +02:00
D3SOX
661686b255
feat(betterActivities): setting for showing descriptions 2024-07-02 16:42:23 +02:00
D3SOX
811a47ba66
fix(betterActivities): correct patch 2024-07-02 00:35:22 +02:00
D3SOX
e1dbd5f753
fix(betterActivities): show all activities for profiles v2 2024-07-02 00:24:21 +02:00
D3SOX
9475478fbe
Merge branch 'refs/heads/dev' into plugin/memberListActivities 2024-07-02 00:21:26 +02:00
Nico
fb3cdbbf62
fix(betterActivities): update icons patch 2024-06-19 06:35:14 +02:00
Nico
c42e490693
fix(betterActivities): brand-experiment is now brand-500 2024-06-01 19:43:40 +02:00
D3SOX
b780ae4a2a
refactor(betterActivities): remove obsolete restartNeeded props 2024-05-17 19:20:49 +02:00
D3SOX
46facce4b7
refactor(betterActivities): ActivityTooltip as function component 2024-05-17 19:17:25 +02:00
D3SOX
265a9755b3
Merge branch 'refs/heads/dev' into plugin/memberListActivities 2024-05-17 18:12:55 +02:00
D3SOX
f78c9ce608
refactor(betterSettings): re-use ActivityViewProps type 2024-05-17 18:09:03 +02:00
D3SOX
3be954f713
docs(betterSettings): improve alt text 2024-05-17 18:06:42 +02:00
D3SOX
8278641c9b
refactor(betterSettings): split index into multiple files 2024-05-17 18:05:00 +02:00
D3SOX
dd5dab5efb
docs(betterActivities): add listview screenshot 2024-05-16 22:04:23 +02:00
D3SOX
3ed2edf967
docs(betterActivities): don't include screenshots in git 2024-05-15 09:04:25 +02:00
D3SOX
d31d1b74e0
chore(betterActivities): add type for icon 2024-05-11 00:04:40 +02:00
D3SOX
8602141fea
feat(betterActivities): setting to not show special ones first
and reorganize/group the settings
2024-05-11 00:03:03 +02:00
Nico
d7d64f420c
fix(betterActivities): set default icon size to 15
idk why I've set this to 20
2024-05-09 17:57:11 +02:00
D3SOX
4f0e9312ec
feat(betterActivities): allow showing all activities below each other 2024-05-07 10:04:53 +02:00
D3SOX
58eb7943db
refactor(betterActivities): add type for ActivityView component 2024-05-06 20:27:16 +02:00
D3SOX
4e430c4252
feat(betterActivities): rename and add show all activities functionality 2024-05-06 19:04:17 +02:00
D3SOX
957639be6c
feat(memberListActivities): show elapsed time in tooltip and support twitch images 2024-04-18 23:33:01 +02:00
Nico
7fee537ea1
chore(memberListActivities): fix typo in comment 2024-04-18 08:21:47 +02:00
D3SOX
c0f97446b8
refactor(memberListActivities): move types to their own file 2024-04-18 08:03:08 +02:00
D3SOX
04cf952fe3
Merge branch 'refs/heads/main' into plugin/memberListActivities 2024-04-18 07:59:57 +02:00
D3SOX
17b9e78272
fix(memberListActivities): add word-break to fix cut off text 2024-04-16 18:22:23 +02:00
D3SOX
14f1569bc2
feat(memberListActivities): show time bar in tooltip 2024-04-16 17:03:34 +02:00
D3SOX
b5b43a7900
feat(memberListActivities): show different asset in tooltip 2024-04-15 00:57:07 +02:00
D3SOX
f6b3df7b5d
chore(memberListActivities): omg linter yes
like who cares
2024-04-15 00:37:01 +02:00
D3SOX
57f548cb69
chore(memberListActivities): linter be happy pls 2024-04-15 00:27:26 +02:00
D3SOX
e87ced9f9d
feat(memberListActivities): better tooltips 2024-04-15 00:24:07 +02:00
D3SOX
0753b51104
Merge branch 'refs/heads/main' into plugin/memberListActivities 2024-04-15 00:16:39 +02:00
D3SOX
96175671b1
refactor(memberListActivities): don't spread activity into img element 2024-04-02 19:56:33 +02:00
D3SOX
99420e2cbe
feat(memberListActivities): add tooltips 2024-04-02 19:46:33 +02:00
D3SOX
3cfefad3f5
Merge branch 'main' into plugin/memberListActivities 2024-04-02 19:43:22 +02:00
D3SOX
77a3b6c96c
feat(memberListActivities): show default icon when no icons where found 2024-03-18 18:03:36 +01:00
D3SOX
896421a9fa
Merge branch 'main' into plugin/memberListActivities 2024-03-06 14:08:41 +01:00
D3SOX
f2b6e6570d
fix(memberListActivities): update patch 2024-03-06 14:08:30 +01:00
D3SOX
d656aa7119
fix(memberListActivities): prefer application icons 2024-02-22 10:57:20 +01:00
D3SOX
12ae29c389
fix(memberListActivities): get rid of duplicate icons 2024-02-21 17:57:03 +01:00
D3SOX
ab1ecd2b7f
feat(memberListActivities): better console icons support 2024-02-19 18:17:10 +01:00
D3SOX
004517ce9b
fix(memberListActivities): prefer large image
I found the large image to be more applicable most of the time as the small image is often only used as an indicator.

Maybe make this a setting?

I also thought about maintaining a list of applications where to use which image but gave up on that idea.
2024-02-16 09:31:33 +01:00
D3SOX
96c1d9cb4b
feat(memberListActivities): option to disable rendering GIFs 2024-02-16 09:15:13 +01:00
D3SOX
11a1c81916
Merge branch 'main' into plugin/memberListActivities 2024-02-16 09:07:43 +01:00
D3SOX
358fa1d1b4
feat(memberListActivities): configurable icon size 2024-02-15 16:09:52 +01:00
D3SOX
d7404417be
fix(memberListActivities): use correct fetchApplication method 2024-02-15 14:44:02 +01:00
D3SOX
ad1d20a4bc
fix(memberListActivities): always replace icon
Update patch to support single app icons (like simple Minecraft RPC)
2024-02-15 14:30:42 +01:00
D3SOX
5c4f743b21
docs(memberListActivities): update screenshot 2024-02-15 14:12:57 +01:00
D3SOX
682f7e65c8
fix(memberListActivities): make icons round 2024-02-15 14:08:48 +01:00
D3SOX
da764f5144
feat(memberListActivities): improve patch 2024-02-15 14:06:25 +01:00
D3SOX
2254da2474
feat(memberListActivities): support application icons 2024-02-15 13:59:50 +01:00
D3SOX
1867427de4
refactor(memberListActivities): simplify media link transformation 2024-02-15 13:16:10 +01:00
D3SOX
94c5e6fdb7
fix(memberListActivities): support twitch and attachment images 2024-02-15 13:13:35 +01:00
D3SOX
c13bb6e47e
fix(memberListActivities): use discord proxy for external media 2024-02-15 13:05:09 +01:00
D3SOX
db6c011396
chore(memberListActivities): remove obsolete settings 2024-02-15 13:01:23 +01:00
D3SOX
bf55ea0763
feat(memberListActivities): add new plugin 2024-02-15 12:56:03 +01:00
31 changed files with 909 additions and 38 deletions

View file

@ -292,10 +292,10 @@ export default function PluginSettings() {
if (!pluginFilter(p)) continue;
const isRequired = p.required || depMap[p.name]?.some(d => settings.plugins[d].enabled);
const isRequired = p.required || p.isDependency || depMap[p.name]?.some(d => settings.plugins[d].enabled);
if (isRequired) {
const tooltipText = p.required
const tooltipText = p.required || !depMap[p.name]
? "This plugin is required for Vencord to function."
: makeDependencyList(depMap[p.name]?.filter(d => settings.plugins[d].enabled));

View file

@ -142,7 +142,7 @@ export default definePlugin({
required: true,
description: "Helps us provide support to you",
authors: [Devs.Ven],
dependencies: ["CommandsAPI", "UserSettingsAPI", "MessageAccessoriesAPI"],
dependencies: ["UserSettingsAPI", "MessageAccessoriesAPI"],
settings,

View file

@ -24,7 +24,7 @@ interface ActivityButton {
}
interface Activity {
state: string;
state?: string;
details?: string;
timestamps?: {
start?: number;
@ -52,8 +52,8 @@ const enum ActivityFlag {
export interface TrackData {
name: string;
album: string;
artist: string;
album?: string;
artist?: string;
appleMusicLink?: string;
songLink?: string;
@ -61,8 +61,8 @@ export interface TrackData {
albumArtwork?: string;
artistArtwork?: string;
playerPosition: number;
duration: number;
playerPosition?: number;
duration?: number;
}
const enum AssetImageType {
@ -155,8 +155,8 @@ const settings = definePluginSettings({
function customFormat(formatStr: string, data: TrackData) {
return formatStr
.replaceAll("{name}", data.name)
.replaceAll("{album}", data.album)
.replaceAll("{artist}", data.artist);
.replaceAll("{album}", data.album ?? "")
.replaceAll("{artist}", data.artist ?? "");
}
function getImageAsset(type: AssetImageType, data: TrackData) {
@ -212,14 +212,16 @@ export default definePlugin({
const assets: ActivityAssets = {};
const isRadio = Number.isNaN(trackData.duration) && (trackData.playerPosition === 0);
if (settings.store.largeImageType !== AssetImageType.Disabled) {
assets.large_image = largeImageAsset;
assets.large_text = customFormat(settings.store.largeTextString, trackData);
if (!isRadio) assets.large_text = customFormat(settings.store.largeTextString, trackData);
}
if (settings.store.smallImageType !== AssetImageType.Disabled) {
assets.small_image = smallImageAsset;
assets.small_text = customFormat(settings.store.smallTextString, trackData);
if (!isRadio) assets.small_text = customFormat(settings.store.smallTextString, trackData);
}
const buttons: ActivityButton[] = [];
@ -243,17 +245,17 @@ export default definePlugin({
name: customFormat(settings.store.nameString, trackData),
details: customFormat(settings.store.detailsString, trackData),
state: customFormat(settings.store.stateString, trackData),
state: isRadio ? undefined : customFormat(settings.store.stateString, trackData),
timestamps: (settings.store.enableTimestamps ? {
timestamps: (trackData.playerPosition && trackData.duration && settings.store.enableTimestamps) ? {
start: Date.now() - (trackData.playerPosition * 1000),
end: Date.now() - (trackData.playerPosition * 1000) + (trackData.duration * 1000),
} : undefined),
} : undefined,
assets,
buttons: buttons.length ? buttons.map(v => v.label) : undefined,
metadata: { button_urls: buttons.map(v => v.url) || undefined, },
buttons: !isRadio && buttons.length ? buttons.map(v => v.label) : undefined,
metadata: !isRadio && buttons.length ? { button_urls: buttons.map(v => v.url) } : undefined,
type: settings.store.activityType,
flags: ActivityFlag.INSTANCE,

View file

@ -0,0 +1,7 @@
# BetterActivities
Shows activity icons in the member list and allows showing all activities
![Shows activity icons next to the status line in the member list](https://github.com/Vendicated/Vencord/assets/24937357/4c034963-4448-483a-ba1d-c04f74e4aa51)
![Shows all activities in the profile via a carousel](https://github.com/Vendicated/Vencord/assets/24937357/11a2f15e-59b0-4072-847a-33444d964674)
![Optionally show all activities as list](https://github.com/Vendicated/Vencord/assets/24937357/277f425f-65e7-4e25-ad98-ff61b4370f08)

View file

@ -0,0 +1,34 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2024 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { classNameFactory } from "@api/Styles";
import ErrorBoundary from "@components/ErrorBoundary";
import { User } from "discord-types/general";
import { ActivityView } from "../index";
import { Activity, Application } from "../types";
interface ActivityTooltipProps {
activity: Activity;
application?: Application;
user: User;
cl: ReturnType<typeof classNameFactory>;
}
export default function ActivityTooltip({ activity, application, user, cl }: Readonly<ActivityTooltipProps>) {
return (
<ErrorBoundary>
<div className={cl("activity-tooltip")}>
<ActivityView
activity={activity}
user={user}
application={application}
type="BiteSizePopout"
/>
</div>
</ErrorBoundary>
);
}

View file

@ -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 (
<svg className={`vc-bactivities-caret-${direction.toLowerCase()} ${disabled && "disabled"}`} width="24" height="24" viewBox="0 0 24 24">
<path fill="currentColor" fillRule="evenodd" clipRule="evenodd" d="M16.59 8.59004L12 13.17L7.41 8.59004L6 10L12 16L18 10L16.59 8.59004Z" />
</svg>
);
}

View file

@ -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<SVGSVGElement>) {
return (<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" {...props}><path fill="#1ed760" d="M128 0C57.308 0 0 57.309 0 128c0 70.696 57.309 128 128 128c70.697 0 128-57.304 128-128C256 57.314 198.697.007 127.998.007zm58.699 184.614c-2.293 3.76-7.215 4.952-10.975 2.644c-30.053-18.357-67.885-22.515-112.44-12.335a7.981 7.981 0 0 1-9.552-6.007a7.968 7.968 0 0 1 6-9.553c48.76-11.14 90.583-6.344 124.323 14.276c3.76 2.308 4.952 7.215 2.644 10.975m15.667-34.853c-2.89 4.695-9.034 6.178-13.726 3.289c-34.406-21.148-86.853-27.273-127.548-14.92c-5.278 1.594-10.852-1.38-12.454-6.649c-1.59-5.278 1.386-10.842 6.655-12.446c46.485-14.106 104.275-7.273 143.787 17.007c4.692 2.89 6.175 9.034 3.286 13.72zm1.345-36.293C162.457 88.964 94.394 86.71 55.007 98.666c-6.325 1.918-13.014-1.653-14.93-7.978c-1.917-6.328 1.65-13.012 7.98-14.935C93.27 62.027 168.434 64.68 215.929 92.876c5.702 3.376 7.566 10.724 4.188 16.405c-3.362 5.69-10.73 7.565-16.4 4.187z"></path></svg>);
}

View file

@ -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<SVGSVGElement>) {
return (<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 268" {...props}><path fill="#5a3e85" d="M17.458 0L0 46.556v186.201h63.983v34.934h34.931l34.898-34.934h52.36L256 162.954V0zm23.259 23.263H232.73v128.029l-40.739 40.741H128L93.113 226.92v-34.886H40.717zm64.008 116.405H128V69.844h-23.275zm63.997 0h23.27V69.844h-23.27z"></path></svg>);
}

View file

@ -0,0 +1,291 @@
/*
* 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 "./styles.css";
import { classNameFactory } from "@api/Styles";
import ErrorBoundary from "@components/ErrorBoundary";
import { Devs } from "@utils/constants";
import definePlugin from "@utils/types";
import { findComponentByCodeLazy } from "@webpack";
import { PresenceStore, React, Tooltip, useEffect, useMemo, useState, useStateFromStores } from "@webpack/common";
import { User } from "discord-types/general";
import ActivityTooltip from "./components/ActivityTooltip";
import { Caret } from "./components/Caret";
import { SpotifyIcon } from "./components/SpotifyIcon";
import { TwitchIcon } from "./components/TwitchIcon";
import settings from "./settings";
import {
Activity,
ActivityListIcon,
ActivityViewProps,
ApplicationIcon,
IconCSSProperties
} from "./types";
import {
getActivityApplication,
getApplicationIcons
} from "./utils";
const cl = classNameFactory("vc-bactivities-");
export const ActivityView = findComponentByCodeLazy<ActivityViewProps>(",onOpenGameProfileModal:");
// 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");
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: <img {...appIcon.image} />,
tooltip: <ActivityTooltip
activity={appIcon.activity}
application={appIcon.application}
user={user}
cl={cl}
/>
});
}
}
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: <IconComponent />,
tooltip: <ActivityTooltip activity={activity} user={user} cl={cl} />
};
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 <ErrorBoundary noop>
<div className={cl("row")}>
{icons.map(({ iconElement, tooltip }, i) => (
<div key={i} className={cl("icon")} style={iconStyle}>
{tooltip ? <Tooltip text={tooltip}>
{({ onMouseEnter, onMouseLeave }) => (
<div
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}>
{iconElement}
</div>
)}
</Tooltip> : iconElement}
</div>
))}
</div>
</ErrorBoundary>;
} 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 <DefaultActivityIcon />;
}
}
return null;
},
showAllActivitiesComponent({ activity, user, ...props }: ActivityViewProps) {
const [currentActivity, setCurrentActivity] = useState<Activity | null>(
activity?.type !== 4 ? activity! : null
);
const activities = useStateFromStores<Activity[]>(
[PresenceStore], () => PresenceStore.getActivities(user.id).filter((activity: Activity) => activity.type !== 4)
) ?? [];
useEffect(() => {
if (!activities.length) {
setCurrentActivity(null);
return;
}
if (!currentActivity || !activities.includes(currentActivity))
setCurrentActivity(activities[0]);
}, [activities]);
// we use these for other activities, it would be better to somehow get the corresponding activity props
const generalProps = useMemo(() => Object.keys(props).reduce((acc, key) => {
// exclude activity specific props to prevent copying them to all activities (e.g. buttons)
if (key !== "renderActions" && key !== "application") acc[key] = props[key];
return acc;
}, {} as Omit<typeof props, "renderActions" | "application">), [props]);
if (!activities.length) return null;
if (settings.store.allActivitiesStyle === "carousel") {
return (
<div style={{ display: "flex", flexDirection: "column" }}>
{currentActivity?.id === activity?.id ? (
<ActivityView
activity={currentActivity}
user={user}
{...props}
/>
) : (
<ActivityView
activity={currentActivity}
user={user}
// fetch optional application
application={getActivityApplication(currentActivity!)}
{...generalProps}
/>
)}
<div
className={cl("controls")}
style={{
display: "flex",
flexDirection: "row",
justifyContent: "space-between",
}}
>
<Tooltip text="Left" tooltipClassName={cl("controls-tooltip")}>{({
onMouseEnter,
onMouseLeave
}) => {
return <span
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
onClick={() => {
const index = activities.indexOf(currentActivity!);
if (index - 1 >= 0)
setCurrentActivity(activities[index - 1]);
}}
>
<Caret
disabled={activities.indexOf(currentActivity!) < 1}
direction="left" />
</span>;
}}</Tooltip>
<div className="carousell">
{activities.map((activity, index) => (
<div
key={"dot--" + index}
onClick={() => setCurrentActivity(activity)}
className={`dot ${currentActivity === activity ? "selected" : ""}`} />
))}
</div>
<Tooltip text="Right" tooltipClassName={cl("controls-tooltip")}>{({
onMouseEnter,
onMouseLeave
}) => {
return <span
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
onClick={() => {
const index = activities.indexOf(currentActivity!);
if (index + 1 < activities.length)
setCurrentActivity(activities[index + 1]);
}}
>
<Caret
disabled={activities.indexOf(currentActivity!) >= activities.length - 1}
direction="right" />
</span>;
}}</Tooltip>
</div>
</div>
);
} else {
return (
<div
style={{
display: "flex",
flexDirection: "column",
gap: "5px",
}}
>
{activities.map((activity, index) =>
index === 0 ? (
<ActivityView
key={index}
activity={activity}
user={user}
{...props}
/>) : (
<ActivityView
key={index}
activity={activity}
user={user}
application={getActivityApplication(activity)}
{...generalProps}
/>
))}
</div>
);
}
},
patches: [
{
// Patch activity icons
find: ".getHangStatusActivity():null!",
replacement: {
match: /null!=(\i)&&\i.some\(\i=>\(0,\i.\i\)\(\i,\i\)\)\?/,
replace: "$self.patchActivityList(e),false?"
},
predicate: () => settings.store.memberList,
},
{
// Show all activities in the user popout/sidebar
find: '"UserActivityContainer"',
replacement: {
match: /(?<=\(0,\i\.jsx\)\()(\i\.\i)(?=,{...(\i),activity:\i,user:\i,application:\i)/,
replace: "$2.type==='BiteSizePopout'?$self.showAllActivitiesComponent:$1"
},
predicate: () => settings.store.profiles
},
],
});

View file

@ -0,0 +1,77 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2024 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { definePluginSettings } from "@api/Settings";
import { OptionType } from "@utils/types";
import { React } from "@webpack/common";
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,
},
renderGifs: {
type: OptionType.BOOLEAN,
description: "Allow rendering GIFs",
default: true,
},
showAppDescriptions: {
type: OptionType.BOOLEAN,
description: "Show application descriptions in the activity tooltip",
default: true,
restartNeeded: false,
},
divider: {
type: OptionType.COMPONENT,
description: "",
component: () => (
<div style={{
width: "100%",
height: 1,
borderTop: "thin solid var(--background-modifier-accent)",
paddingTop: 5,
paddingBottom: 5
}} />
),
},
profiles: {
type: OptionType.BOOLEAN,
description: "Show all activities in the profile popout/sidebar",
default: true,
restartNeeded: true,
},
allActivitiesStyle: {
type: OptionType.SELECT,
description: "Style for showing all activities",
options: [
{
default: true,
label: "Carousel",
value: "carousel",
},
{
label: "List",
value: "list",
},
]
}
});
export default settings;

View file

@ -0,0 +1,95 @@
.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%;
}
.vc-bactivities-activity-tooltip {
padding: 1px;
}
.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 .carousell {
display: flex;
align-items: center;
}
.vc-bactivities-controls .carousell .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 .carousell .dot:hover:not(.selected) {
opacity: 1;
}
.vc-bactivities-controls .carousell .dot.selected {
opacity: 1;
background: var(--dot-color, var(--brand-500));
}
.vc-bactivities-controls-tooltip {
--background-floating: var(--background-secondary);
}

View file

@ -0,0 +1,91 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2024 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { User } from "discord-types/general";
import { CSSProperties, ImgHTMLAttributes } 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<HTMLImageElement> & {
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;
}
export interface ActivityViewProps {
activity: Activity | null;
user: User;
application?: Application;
renderActions?: () => JSX.Element;
type: string;
}

View file

@ -0,0 +1,122 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2024 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { findByPropsLazy, findStoreLazy } from "@webpack";
import settings from "./settings";
import { Activity, Application, ApplicationIcon } from "./types";
const ApplicationStore: {
getApplication: (id: string) => Application | null;
} = findStoreLazy("ApplicationStore");
const { fetchApplication }: {
fetchApplication: (id: string) => Promise<Application | null>;
} = findByPropsLazy("fetchApplication");
const fetchedApplications = new Map<string, Application | null>();
export function getActivityApplication({ application_id }: Activity) {
if (!application_id) return undefined;
let application = ApplicationStore.getApplication(application_id);
if (!application && fetchedApplications.has(application_id)) {
application = fetchedApplications.get(application_id) ?? null;
}
return application ?? undefined;
}
// TODO: replace with "renderXboxImage"?
const xboxUrl = "https://discord.com/assets/9a15d086141be29d9fcd.png";
export 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;
}

View file

@ -0,0 +1,68 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2024 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { openPluginModal } from "@components/PluginSettings/PluginModal";
import { isObjectEmpty } from "@utils/misc";
import { Alerts, i18n, Menu, useMemo, useState } from "@webpack/common";
import Plugins from "~plugins";
function onRestartNeeded() {
Alerts.show({
title: "Restart required",
body: <p>You have changed settings that require a restart.</p>,
confirmText: "Restart now",
cancelText: "Later!",
onConfirm: () => location.reload()
});
}
export default function PluginsSubmenu() {
const sortedPlugins = useMemo(() => Object.values(Plugins)
.sort((a, b) => a.name.localeCompare(b.name)), []);
const [query, setQuery] = useState("");
const search = query.toLowerCase();
const include = (p: typeof Plugins[keyof typeof Plugins]) => (
Vencord.Plugins.isPluginEnabled(p.name)
&& p.options && !isObjectEmpty(p.options)
&& (
p.name.toLowerCase().includes(search)
|| p.description.toLowerCase().includes(search)
|| p.tags?.some(t => t.toLowerCase().includes(search))
)
);
const plugins = sortedPlugins.filter(include);
return (
<>
<Menu.MenuControlItem
id="vc-plugins-search"
control={(props, ref) => (
<Menu.MenuSearchControl
{...props}
query={query}
onChange={setQuery}
ref={ref}
placeholder={i18n.Messages.SEARCH}
/>
)}
/>
{!!plugins.length && <Menu.MenuSeparator />}
{plugins.map(p => (
<Menu.MenuItem
key={p.name}
id={p.name}
label={p.name}
action={() => openPluginModal(p, onRestartNeeded)}
/>
))}
</>
);
}

View file

@ -13,6 +13,8 @@ import { waitFor } from "@webpack";
import { ComponentDispatch, FocusLock, i18n, Menu, useEffect, useRef } from "@webpack/common";
import type { HTMLAttributes, ReactElement } from "react";
import PluginsSubmenu from "./PluginsSubmenu";
type SettingsEntry = { section: string, label: string; };
const cl = classNameFactory("");
@ -118,13 +120,21 @@ export default definePlugin({
},
{ // Settings cog context menu
find: "Messages.USER_SETTINGS_ACTIONS_MENU_LABEL",
replacement: {
match: /(EXPERIMENTS:.+?)(\(0,\i.\i\)\(\))(?=\.filter\(\i=>\{let\{section:\i\}=)/,
replace: "$1$self.wrapMenu($2)"
}
}
replacement: [
{
match: /(EXPERIMENTS:.+?)(\(0,\i.\i\)\(\))(?=\.filter\(\i=>\{let\{section:\i\}=)/,
replace: "$1$self.wrapMenu($2)"
},
{
match: /case \i\.\i\.DEVELOPER_OPTIONS:return \i;/,
replace: "$&case 'VencordPlugins':return $self.PluginsSubmenu();"
}
]
},
],
PluginsSubmenu,
// This is the very outer layer of the entire ui, so we can't wrap this in an ErrorBoundary
// without possibly also catching unrelated errors of children.
//

View file

@ -126,7 +126,7 @@ export default definePlugin({
}
},
{
find: '"Handling ping: "',
find: '"_handleLocalVideoDisabled: ',
predicate: () => settings.store.disableNoisyLoggers,
replacement: {
match: /new \i\.\i\("RTCConnection\("\.concat.+?\)\)(?=,)/,

View file

@ -23,12 +23,13 @@ import { ErrorCard } from "@components/ErrorCard";
import { Devs } from "@utils/constants";
import { Margins } from "@utils/margins";
import definePlugin, { OptionType } from "@utils/types";
import { findByPropsLazy } from "@webpack";
import { findByPropsLazy, findLazy } from "@webpack";
import { Forms, React } from "@webpack/common";
import hideBugReport from "./hideBugReport.css?managed";
const KbdStyles = findByPropsLazy("key", "combo");
const BugReporterExperiment = findLazy(m => m?.definition?.id === "2024-09_bug_reporter");
const settings = definePluginSettings({
toolbarDevMenu: {
@ -78,8 +79,8 @@ export default definePlugin({
{
find: "toolbar:function",
replacement: {
match: /\i\.isStaff\(\)/,
replace: "true"
match: /hasBugReporterAccess:(\i)/,
replace: "_hasBugReporterAccess:$1=true"
},
predicate: () => settings.store.toolbarDevMenu
},
@ -91,10 +92,18 @@ export default definePlugin({
match: /\i\.isDM\(\)\|\|\i\.isThread\(\)/,
replace: "false",
}
},
// enable option to always record clips even if you are not streaming
{
find: "isDecoupledGameClippingEnabled(){",
replacement: {
match: /\i\.isStaff\(\)/,
replace: "true"
}
}
],
start: () => enableStyle(hideBugReport),
start: () => !BugReporterExperiment.getCurrentConfig().hasBugReporterAccess && enableStyle(hideBugReport),
stop: () => disableStyle(hideBugReport),
settingsAboutComponent: () => {

View file

@ -27,7 +27,6 @@ export default definePlugin({
name: "FriendInvites",
description: "Create and manage friend invite links via slash commands (/create friend invite, /view friend invites, /revoke friend invites).",
authors: [Devs.afn, Devs.Dziurwa],
dependencies: ["CommandsAPI"],
commands: [
{
name: "create friend invite",

View file

@ -105,6 +105,11 @@ for (const p of pluginsValues) if (isPluginEnabled(p.name)) {
settings[d].enabled = true;
dep.isDependency = true;
});
if (p.commands?.length) {
Plugins.CommandsAPI.isDependency = true;
settings.CommandsAPI.enabled = true;
}
}
for (const p of pluginsValues) {

View file

@ -82,7 +82,6 @@ export default definePlugin({
default: true
}
},
dependencies: ["CommandsAPI"],
async start() {
for (const tag of await getTags()) createTagCommand(tag);

View file

@ -33,7 +33,6 @@ export default definePlugin({
name: "MoreCommands",
description: "echo, lenny, mock",
authors: [Devs.Arjix, Devs.echo, Devs.Samu],
dependencies: ["CommandsAPI"],
commands: [
{
name: "echo",

View file

@ -24,7 +24,6 @@ export default definePlugin({
name: "MoreKaomoji",
description: "Adds more Kaomoji to discord. ヽ(´▽`)/",
authors: [Devs.JacobTm],
dependencies: ["CommandsAPI"],
commands: [
{ name: "dissatisfaction", description: " " },
{ name: "smug", description: "ಠ_ಠ" },

View file

@ -88,7 +88,6 @@ export default definePlugin({
name: "petpet",
description: "Adds a /petpet slash command to create headpet gifs from any image",
authors: [Devs.Ven],
dependencies: ["CommandsAPI"],
commands: [
{
inputType: ApplicationCommandInputType.BUILT_IN,

View file

@ -91,7 +91,7 @@ export default definePlugin({
}
},
{
find: ".PANEL,isInteractionSource:",
find: ".PANEL,interactionType:",
replacement: {
match: /{profileType:\i\.\i\.PANEL,children:\[/,
replace: "$&$self.BiteSizeReviewsButton({user:arguments[0].user}),"

View file

@ -88,7 +88,7 @@ export default definePlugin({
name: "SilentTyping",
authors: [Devs.Ven, Devs.Rini, Devs.ImBanana],
description: "Hide that you are typing",
dependencies: ["CommandsAPI", "ChatInputButtonAPI"],
dependencies: ["ChatInputButtonAPI"],
settings,
contextMenus: {
"textarea-context": ChatBarContextCheckbox

View file

@ -76,7 +76,6 @@ export default definePlugin({
name: "SpotifyShareCommands",
description: "Share your current Spotify track, album or artist via slash command (/track, /album, /artist)",
authors: [Devs.katlyn],
dependencies: ["CommandsAPI"],
commands: [
{
name: "track",

View file

@ -72,13 +72,13 @@ export interface PluginDef {
stop?(): void;
patches?: Omit<Patch, "plugin">[];
/**
* List of commands. If you specify these, you must add CommandsAPI to dependencies
* List of commands that your plugin wants to register
*/
commands?: Command[];
/**
* A list of other plugins that your plugin depends on.
* These will automatically be enabled and loaded before your plugin
* Common examples are CommandsAPI, MessageEventsAPI...
* Generally these will be API plugins
*/
dependencies?: string[],
/**

View file

@ -28,6 +28,8 @@ export let Forms = {} as {
FormText: t.FormText,
};
export let Icons = {} as t.Icons;
export let Card: t.Card;
export let Button: t.Button;
export let Switch: t.Switch;
@ -85,4 +87,5 @@ waitFor(["FormItem", "Button"], m => {
Heading
} = m);
Forms = m;
Icons = m;
});

View file

@ -18,6 +18,8 @@
import type { ComponentType, CSSProperties, FunctionComponent, HtmlHTMLAttributes, HTMLProps, KeyboardEvent, MouseEvent, PropsWithChildren, PropsWithRef, ReactNode, Ref } from "react";
import { IconNames } from "./iconNames";
export type TextVariant = "heading-sm/normal" | "heading-sm/medium" | "heading-sm/semibold" | "heading-sm/bold" | "heading-md/normal" | "heading-md/medium" | "heading-md/semibold" | "heading-md/bold" | "heading-lg/normal" | "heading-lg/medium" | "heading-lg/semibold" | "heading-lg/bold" | "heading-xl/normal" | "heading-xl/medium" | "heading-xl/bold" | "heading-xxl/normal" | "heading-xxl/medium" | "heading-xxl/bold" | "eyebrow" | "heading-deprecated-14/normal" | "heading-deprecated-14/medium" | "heading-deprecated-14/bold" | "text-xxs/normal" | "text-xxs/medium" | "text-xxs/semibold" | "text-xxs/bold" | "text-xs/normal" | "text-xs/medium" | "text-xs/semibold" | "text-xs/bold" | "text-sm/normal" | "text-sm/medium" | "text-sm/semibold" | "text-sm/bold" | "text-md/normal" | "text-md/medium" | "text-md/semibold" | "text-md/bold" | "text-lg/normal" | "text-lg/medium" | "text-lg/semibold" | "text-lg/bold" | "display-sm" | "display-md" | "display-lg" | "code";
export type FormTextTypes = Record<"DEFAULT" | "INPUT_PLACEHOLDER" | "DESCRIPTION" | "LABEL_BOLD" | "LABEL_SELECTED" | "LABEL_DESCRIPTOR" | "ERROR" | "SUCCESS", string>;
export type HeadingTag = `h${1 | 2 | 3 | 4 | 5 | 6}`;
@ -69,7 +71,7 @@ export type FormText = ComponentType<PropsWithChildren<{
}> & TextProps> & { Types: FormTextTypes; };
export type Tooltip = ComponentType<{
text: ReactNode;
text: ReactNode | ComponentType;
children: FunctionComponent<{
onClick(): void;
onMouseEnter(): void;
@ -502,3 +504,10 @@ export type Avatar = ComponentType<PropsWithChildren<{
type FocusLock = ComponentType<PropsWithChildren<{
containerRef: RefObject<HTMLElement>;
}>>;
export type Icon = ComponentType<JSX.IntrinsicElements["svg"] & {
size?: string;
colorClass?: string;
} & Record<string, any>>;
export type Icons = Record<IconNames, Icon>;

14
src/webpack/common/types/iconNames.d.ts vendored Normal file

File diff suppressed because one or more lines are too long

View file

@ -72,6 +72,11 @@ export interface Menu {
onChange(value: number): void,
renderValue?(value: number): string,
}>;
MenuSearchControl: RC<{
query: string
onChange(query: string): void;
placeholder?: string;
}>;
}
export interface ContextMenuApi {