Plugin
IgnoreActivities
Ignore activities from showing up on your status ONLY. You can configure which ones are specifically ignored from the Registered Games and Activities tabs, or use the general settings below
1
/*2
* FiveCord — a Discord client mod3
* Copyright (c) 2025 FiveCord4
* SPDX-License-Identifier: GPL-3.0-or-later5
*/6
7
import { definePluginSettings } from "@api/Settings";8
import { getUserSettingLazy } from "@api/UserSettings";9
import ErrorBoundary from "@components/ErrorBoundary";10
import { Flex } from "@components/Flex";11
import CustomRpcPlugin from "@plugins/customRPC";12
import { Devs } from "@utils/constants";13
import { Margins } from "@utils/margins";14
import definePlugin, { OptionType } from "@utils/types";15
import { Button, Forms, RunningGameStore, showToast, TextArea, Toasts, Tooltip, useEffect, useState } from "@webpack/common";16
17
const enum ActivitiesTypes {18
Game,19
Embedded20
}21
22
interface IgnoredActivity {23
id: string;24
name: string;25
type: ActivitiesTypes;26
}27
28
const enum FilterMode {29
Whitelist,30
Blacklist31
}32
33
const ShowCurrentGame = getUserSettingLazy("status", "showCurrentGame")!;34
35
function ToggleIcon(activity: IgnoredActivity, tooltipText: string, path: string, fill: string) {36
return (37
<Tooltip text={tooltipText}>38
{tooltipProps => (39
<button40
{...tooltipProps}41
onClick={e => handleActivityToggle(e, activity)}42
style={{ all: "unset", cursor: "pointer", display: "flex", justifyContent: "center", alignItems: "center" }}43
>44
<svg45
width="24"46
height="24"47
viewBox="0 -960 960 960"48
>49
<path fill={fill} d={path} />50
</svg>51
</button>52
)}53
</Tooltip>54
);55
}56
57
const ToggleIconOn = (activity: IgnoredActivity, fill: string) => ToggleIcon(activity, "Disable activity", "M480-320q75 0 127.5-52.5T660-500q0-75-52.5-127.5T480-680q-75 0-127.5 52.5T300-500q0 75 52.5 127.5T480-320Zm0-72q-45 0-76.5-31.5T372-500q0-45 31.5-76.5T480-608q45 0 76.5 31.5T588-500q0 45-31.5 76.5T480-392Zm0 192q-146 0-266-81.5T40-500q54-137 174-218.5T480-800q146 0 266 81.5T920-500q-54 137-174 218.5T480-200Zm0-300Zm0 220q113 0 207.5-59.5T832-500q-50-101-144.5-160.5T480-720q-113 0-207.5 59.5T128-500q50 101 144.5 160.5T480-280Z", fill);58
const ToggleIconOff = (activity: IgnoredActivity, fill: string) => ToggleIcon(activity, "Enable activity", "m644-428-58-58q9-47-27-88t-93-32l-58-58q17-8 34.5-12t37.5-4q75 0 127.5 52.5T660-500q0 20-4 37.5T644-428Zm128 126-58-56q38-29 67.5-63.5T832-500q-50-101-143.5-160.5T480-720q-29 0-57 4t-55 12l-62-62q41-17 84-25.5t90-8.5q151 0 269 83.5T920-500q-23 59-60.5 109.5T772-302Zm20 246L624-222q-35 11-70.5 16.5T480-200q-151 0-269-83.5T40-500q21-53 53-98.5t73-81.5L56-792l56-56 736 736-56 56ZM222-624q-29 26-53 57t-41 67q50 101 143.5 160.5T480-280q20 0 39-2.5t39-5.5l-36-38q-11 3-21 4.5t-21 1.5q-75 0-127.5-52.5T300-500q0-11 1.5-21t4.5-21l-84-82Zm319 93Zm-151 75Z", fill);59
60
function ToggleActivityComponent(activity: IgnoredActivity, isPlaying = false) {61
const s = settings.use(["ignoredActivities"]);62
const { ignoredActivities } = s;63
64
if (ignoredActivities.some(act => act.id === activity.id)) return ToggleIconOff(activity, "var(--status-danger)");65
return ToggleIconOn(activity, isPlaying ? "var(--green-300)" : "var(--interactive-icon-default)");66
}67
68
function handleActivityToggle(e: React.MouseEvent<HTMLButtonElement, MouseEvent>, activity: IgnoredActivity) {69
e.stopPropagation();70
71
const ignoredActivityIndex = settings.store.ignoredActivities.findIndex(act => act.id === activity.id);72
if (ignoredActivityIndex === -1) settings.store.ignoredActivities.push(activity);73
else settings.store.ignoredActivities.splice(ignoredActivityIndex, 1);74
}75
76
function recalculateActivities() {77
ShowCurrentGame.updateSetting(old => old);78
}79
80
function ImportCustomRPCComponent() {81
return (82
<Flex flexDirection="column">83
<Forms.FormText>Import the application id of the CustomRPC plugin to the filter list</Forms.FormText>84
<div>85
<Button86
onClick={() => {87
const id = CustomRpcPlugin.settings.store.appID;88
if (!id) {89
return showToast("CustomRPC application ID is not set.", Toasts.Type.FAILURE);90
}91
92
const isAlreadyAdded = idsListPushID?.(id);93
if (isAlreadyAdded) {94
showToast("CustomRPC application ID is already added.", Toasts.Type.FAILURE);95
}96
}}97
>98
Import CustomRPC ID99
</Button>100
</div>101
</Flex>102
);103
}104
105
let idsListPushID: ((id: string) => boolean) | null = null;106
107
function IdsListComponent(props: { setValue: (value: string) => void; }) {108
const [idsList, setIdsList] = useState<string>(settings.store.idsList ?? "");109
110
idsListPushID = (id: string) => {111
const currentIds = new Set(idsList.split(",").map(id => id.trim()).filter(Boolean));112
113
const isAlreadyAdded = currentIds.has(id) || (currentIds.add(id), false);114
115
const ids = Array.from(currentIds).join(", ");116
setIdsList(ids);117
props.setValue(ids);118
119
return isAlreadyAdded;120
};121
122
useEffect(() => () => {123
idsListPushID = null;124
}, []);125
126
function handleChange(newValue: string) {127
setIdsList(newValue);128
props.setValue(newValue);129
}130
131
return (132
<section>133
<Forms.FormTitle tag="h3">Filter List</Forms.FormTitle>134
<Forms.FormText className={Margins.bottom8}>Comma separated list of activity IDs to filter (Useful for filtering specific RPC activities and CustomRPC</Forms.FormText>135
<TextArea136
type="text"137
value={idsList}138
onChange={handleChange}139
placeholder="235834946571337729, 343383572805058560"140
/>141
</section>142
);143
}144
145
const settings = definePluginSettings({146
importCustomRPC: {147
type: OptionType.COMPONENT,148
component: ImportCustomRPCComponent149
},150
listMode: {151
type: OptionType.SELECT,152
description: "Change the mode of the filter list",153
options: [154
{155
label: "Whitelist",156
value: FilterMode.Whitelist,157
default: true158
},159
{160
label: "Blacklist",161
value: FilterMode.Blacklist,162
}163
],164
onChange: recalculateActivities165
},166
idsList: {167
type: OptionType.COMPONENT,168
default: "",169
onChange(newValue: string) {170
const ids = new Set(newValue.split(",").map(id => id.trim()).filter(Boolean));171
settings.store.idsList = Array.from(ids).join(", ");172
recalculateActivities();173
},174
component: props => <IdsListComponent setValue={props.setValue} />175
},176
ignorePlaying: {177
type: OptionType.BOOLEAN,178
description: "Ignore all playing activities (These are usually game and RPC activities)",179
default: false,180
onChange: recalculateActivities181
},182
ignoreStreaming: {183
type: OptionType.BOOLEAN,184
description: "Ignore all streaming activities",185
default: false,186
onChange: recalculateActivities187
},188
ignoreListening: {189
type: OptionType.BOOLEAN,190
description: "Ignore all listening activities (These are usually spotify activities)",191
default: false,192
onChange: recalculateActivities193
},194
ignoreWatching: {195
type: OptionType.BOOLEAN,196
description: "Ignore all watching activities",197
default: false,198
onChange: recalculateActivities199
},200
ignoreCompeting: {201
type: OptionType.BOOLEAN,202
description: "Ignore all competing activities (These are normally special game activities)",203
default: false,204
onChange: recalculateActivities205
},206
ignoredActivities: {207
type: OptionType.CUSTOM,208
default: [] as IgnoredActivity[],209
onChange: recalculateActivities210
}211
});212
213
function isActivityTypeIgnored(type: number, id?: string) {214
if (id && settings.store.idsList.includes(id)) {215
return settings.store.listMode === FilterMode.Blacklist;216
}217
218
switch (type) {219
case 0: return settings.store.ignorePlaying;220
case 1: return settings.store.ignoreStreaming;221
case 2: return settings.store.ignoreListening;222
case 3: return settings.store.ignoreWatching;223
case 5: return settings.store.ignoreCompeting;224
}225
226
return false;227
}228
229
export default definePlugin({230
name: "IgnoreActivities",231
authors: [Devs.Nuckyz, Devs.Kylie],232
description: "Ignore activities from showing up on your status ONLY. You can configure which ones are specifically ignored from the Registered Games and Activities tabs, or use the general settings below",233
tags: ["Activity", "Privacy", "Customisation"],234
dependencies: ["UserSettingsAPI"],235
236
settings,237
238
patches: [239
{240
find: 039;"LocalActivityStore"039;,241
replacement: [242
{243
match: /\.LISTENING.+?(?=!?\i\(\)\(\i,\i\))(?<=(\i)\.push.+?)/,244
replace: (m, activities) => `${m}${activities}=${activities}.filter($self.isActivityNotIgnored);`245
}246
]247
},248
{249
find: 039;"ActivityTrackingStore"039;,250
replacement: {251
match: /getVisibleRunningGames\(\).+?;(?=for)(?<=(\i)=\i\.\i\.getVisibleRunningGames.+?)/,252
replace: (m, runningGames) => `${m}${runningGames}=${runningGames}.filter(({id,name})=>$self.isActivityNotIgnored({type:0,application_id:id,name}));`253
}254
},255
{256
find: "#{intl::SETTINGS_GAMES_TOGGLE_OVERLAY}",257
replacement: {258
match: /(\i)&&!\i\|\|\i\?null(?<=(\i)\.verified&&.+?)/,259
replace: "$self.renderToggleGameActivityButton($2,$1),$&"260
}261
},262
263
// Activities from the apps launcher in the bottom right of the chat bar264
{265
find: "#{intl::EMBEDDED_ACTIVITIES_DEVELOPER_ACTIVITY}",266
replacement: {267
match: /lineClamp:1.{0,50}?(?=!\i&&\i\?.+?application:(\i))/,268
replace: "$&$self.renderToggleActivityButton($1),"269
}270
}271
],272
273
async start() {274
if (settings.store.ignoredActivities.length !== 0) {275
const gamesSeen = RunningGameStore.getGamesSeen() as { id?: string; exePath: string; }[];276
277
for (const [index, ignoredActivity] of settings.store.ignoredActivities.entries()) {278
if (ignoredActivity.type !== ActivitiesTypes.Game) continue;279
280
if (!gamesSeen.some(game => game.id === ignoredActivity.id || game.exePath === ignoredActivity.id)) {281
settings.store.ignoredActivities.splice(index, 1);282
}283
}284
}285
},286
287
isActivityNotIgnored(props: { type: number; application_id?: string; name?: string; }) {288
if (isActivityTypeIgnored(props.type, props.application_id)) return false;289
290
if (props.application_id != null) {291
return !settings.store.ignoredActivities.some(activity => activity.id === props.application_id) || (settings.store.listMode === FilterMode.Whitelist && settings.store.idsList.includes(props.application_id));292
} else {293
const exePath = RunningGameStore.getRunningGames().find(game => game.name === props.name)?.exePath;294
if (exePath) {295
return !settings.store.ignoredActivities.some(activity => activity.id === exePath);296
}297
}298
299
return true;300
},301
302
renderToggleGameActivityButton(props: { id?: string; name: string, exePath: string; }, nowPlaying: boolean) {303
return (304
<ErrorBoundary noop>305
<div style={{ marginLeft: 12, zIndex: 0 }}>306
{ToggleActivityComponent({ id: props.id ?? props.exePath, name: props.name, type: ActivitiesTypes.Game }, nowPlaying)}307
</div>308
</ErrorBoundary>309
);310
},311
312
renderToggleActivityButton(props: { id: string; name: string; }) {313
return (314
<ErrorBoundary noop>315
{ToggleActivityComponent({ id: props.id, name: props.name, type: ActivitiesTypes.Embedded })}316
</ErrorBoundary>317
);318
}319
});320