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

Activity Privacy Customisation
index.tsx
Download

Source

src/plugins/ignoreActivities/index.tsx
1import { definePluginSettings } from "@api/Settings";
2import { getUserSettingLazy } from "@api/UserSettings";
3import ErrorBoundary from "@components/ErrorBoundary";
4import { Flex } from "@components/Flex";
5import CustomRpcPlugin from "@plugins/customRPC";
6import { Devs } from "@utils/constants";
7import { Margins } from "@utils/margins";
8import definePlugin, { OptionType } from "@utils/types";
9import { Button, Forms, RunningGameStore, showToast, TextArea, Toasts, Tooltip, useEffect, useState } from "@webpack/common";
10
11const enum ActivitiesTypes {
12 Game,
13 Embedded
14}
15
16interface IgnoredActivity {
17 id: string;
18 name: string;
19 type: ActivitiesTypes;
20}
21
22const enum FilterMode {
23 Whitelist,
24 Blacklist
25}
26
27const ShowCurrentGame = getUserSettingLazy("status", "showCurrentGame")!;
28
29function ToggleIcon(activity: IgnoredActivity, tooltipText: string, path: string, fill: string) {
30 return (
31 <Tooltip text={tooltipText}>
32 {tooltipProps => (
33 <button
34 {...tooltipProps}
35 onClick={e => handleActivityToggle(e, activity)}
36 style={{ all: "unset", cursor: "pointer", display: "flex", justifyContent: "center", alignItems: "center" }}
37 >
38 <svg
39 width="24"
40 height="24"
41 viewBox="0 -960 960 960"
42 >
43 <path fill={fill} d={path} />
44 </svg>
45 </button>
46 )}
47 </Tooltip>
48 );
49}
50
51const 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);
52const 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);
53
54function ToggleActivityComponent(activity: IgnoredActivity, isPlaying = false) {
55 const s = settings.use(["ignoredActivities"]);
56 const { ignoredActivities } = s;
57
58 if (ignoredActivities.some(act => act.id === activity.id)) return ToggleIconOff(activity, "var(--status-danger)");
59 return ToggleIconOn(activity, isPlaying ? "var(--green-300)" : "var(--interactive-icon-default)");
60}
61
62function handleActivityToggle(e: React.MouseEvent<HTMLButtonElement, MouseEvent>, activity: IgnoredActivity) {
63 e.stopPropagation();
64
65 const ignoredActivityIndex = settings.store.ignoredActivities.findIndex(act => act.id === activity.id);
66 if (ignoredActivityIndex === -1) settings.store.ignoredActivities.push(activity);
67 else settings.store.ignoredActivities.splice(ignoredActivityIndex, 1);
68}
69
70function recalculateActivities() {
71 ShowCurrentGame.updateSetting(old => old);
72}
73
74function ImportCustomRPCComponent() {
75 return (
76 <Flex flexDirection="column">
77 <Forms.FormText>Import the application id of the CustomRPC plugin to the filter list</Forms.FormText>
78 <div>
79 <Button
80 onClick={() => {
81 const id = CustomRpcPlugin.settings.store.appID;
82 if (!id) {
83 return showToast("CustomRPC application ID is not set.", Toasts.Type.FAILURE);
84 }
85
86 const isAlreadyAdded = idsListPushID?.(id);
87 if (isAlreadyAdded) {
88 showToast("CustomRPC application ID is already added.", Toasts.Type.FAILURE);
89 }
90 }}
91 >
92 Import CustomRPC ID
93 </Button>
94 </div>
95 </Flex>
96 );
97}
98
99let idsListPushID: ((id: string) => boolean) | null = null;
100
101function IdsListComponent(props: { setValue: (value: string) => void; }) {
102 const [idsList, setIdsList] = useState<string>(settings.store.idsList ?? "");
103
104 idsListPushID = (id: string) => {
105 const currentIds = new Set(idsList.split(",").map(id => id.trim()).filter(Boolean));
106
107 const isAlreadyAdded = currentIds.has(id) || (currentIds.add(id), false);
108
109 const ids = Array.from(currentIds).join(", ");
110 setIdsList(ids);
111 props.setValue(ids);
112
113 return isAlreadyAdded;
114 };
115
116 useEffect(() => () => {
117 idsListPushID = null;
118 }, []);
119
120 function handleChange(newValue: string) {
121 setIdsList(newValue);
122 props.setValue(newValue);
123 }
124
125 return (
126 <section>
127 <Forms.FormTitle tag="h3">Filter List</Forms.FormTitle>
128 <Forms.FormText className={Margins.bottom8}>Comma separated list of activity IDs to filter (Useful for filtering specific RPC activities and CustomRPC</Forms.FormText>
129 <TextArea
130 type="text"
131 value={idsList}
132 onChange={handleChange}
133 placeholder="235834946571337729, 343383572805058560"
134 />
135 </section>
136 );
137}
138
139const settings = definePluginSettings({
140 importCustomRPC: {
141 type: OptionType.COMPONENT,
142 component: ImportCustomRPCComponent
143 },
144 listMode: {
145 type: OptionType.SELECT,
146 description: "Change the mode of the filter list",
147 options: [
148 {
149 label: "Whitelist",
150 value: FilterMode.Whitelist,
151 default: true
152 },
153 {
154 label: "Blacklist",
155 value: FilterMode.Blacklist,
156 }
157 ],
158 onChange: recalculateActivities
159 },
160 idsList: {
161 type: OptionType.COMPONENT,
162 default: "",
163 onChange(newValue: string) {
164 const ids = new Set(newValue.split(",").map(id => id.trim()).filter(Boolean));
165 settings.store.idsList = Array.from(ids).join(", ");
166 recalculateActivities();
167 },
168 component: props => <IdsListComponent setValue={props.setValue} />
169 },
170 ignorePlaying: {
171 type: OptionType.BOOLEAN,
172 description: "Ignore all playing activities (These are usually game and RPC activities)",
173 default: false,
174 onChange: recalculateActivities
175 },
176 ignoreStreaming: {
177 type: OptionType.BOOLEAN,
178 description: "Ignore all streaming activities",
179 default: false,
180 onChange: recalculateActivities
181 },
182 ignoreListening: {
183 type: OptionType.BOOLEAN,
184 description: "Ignore all listening activities (These are usually spotify activities)",
185 default: false,
186 onChange: recalculateActivities
187 },
188 ignoreWatching: {
189 type: OptionType.BOOLEAN,
190 description: "Ignore all watching activities",
191 default: false,
192 onChange: recalculateActivities
193 },
194 ignoreCompeting: {
195 type: OptionType.BOOLEAN,
196 description: "Ignore all competing activities (These are normally special game activities)",
197 default: false,
198 onChange: recalculateActivities
199 },
200 ignoredActivities: {
201 type: OptionType.CUSTOM,
202 default: [] as IgnoredActivity[],
203 onChange: recalculateActivities
204 }
205});
206
207function isActivityTypeIgnored(type: number, id?: string) {
208 if (id && settings.store.idsList.includes(id)) {
209 return settings.store.listMode === FilterMode.Blacklist;
210 }
211
212 switch (type) {
213 case 0: return settings.store.ignorePlaying;
214 case 1: return settings.store.ignoreStreaming;
215 case 2: return settings.store.ignoreListening;
216 case 3: return settings.store.ignoreWatching;
217 case 5: return settings.store.ignoreCompeting;
218 }
219
220 return false;
221}
222
223export default definePlugin({
224 name: "IgnoreActivities",
225 authors: [Devs.Nuckyz, Devs.Kylie],
226 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",
227 tags: ["Activity", "Privacy", "Customisation"],
228 dependencies: ["UserSettingsAPI"],
229
230 settings,
231
232 patches: [
233 {
234 find: &#039;"LocalActivityStore"&#039;,
235 replacement: [
236 {
237 match: /\.LISTENING.+?(?=!?\i\(\)\(\i,\i\))(?<=(\i)\.push.+?)/,
238 replace: (m, activities) => `${m}${activities}=${activities}.filter($self.isActivityNotIgnored);`
239 }
240 ]
241 },
242 {
243 find: &#039;"ActivityTrackingStore"&#039;,
244 replacement: {
245 match: /getVisibleRunningGames\(\).+?;(?=for)(?<=(\i)=\i\.\i\.getVisibleRunningGames.+?)/,
246 replace: (m, runningGames) => `${m}${runningGames}=${runningGames}.filter(({id,name})=>$self.isActivityNotIgnored({type:0,application_id:id,name}));`
247 }
248 },
249 {
250 find: "#{intl::SETTINGS_GAMES_TOGGLE_OVERLAY}",
251 replacement: {
252 match: /(\i)&&!\i\|\|\i\?null(?<=(\i)\.verified&&.+?)/,
253 replace: "$self.renderToggleGameActivityButton($2,$1),$&"
254 }
255 },
256
257 // Activities from the apps launcher in the bottom right of the chat bar
258 {
259 find: "#{intl::EMBEDDED_ACTIVITIES_DEVELOPER_ACTIVITY}",
260 replacement: {
261 match: /lineClamp:1.{0,50}?(?=!\i&&\i\?.+?application:(\i))/,
262 replace: "$&$self.renderToggleActivityButton($1),"
263 }
264 }
265 ],
266
267 async start() {
268 if (settings.store.ignoredActivities.length !== 0) {
269 const gamesSeen = RunningGameStore.getGamesSeen() as { id?: string; exePath: string; }[];
270
271 for (const [index, ignoredActivity] of settings.store.ignoredActivities.entries()) {
272 if (ignoredActivity.type !== ActivitiesTypes.Game) continue;
273
274 if (!gamesSeen.some(game => game.id === ignoredActivity.id || game.exePath === ignoredActivity.id)) {
275 settings.store.ignoredActivities.splice(index, 1);
276 }
277 }
278 }
279 },
280
281 isActivityNotIgnored(props: { type: number; application_id?: string; name?: string; }) {
282 if (isActivityTypeIgnored(props.type, props.application_id)) return false;
283
284 if (props.application_id != null) {
285 return !settings.store.ignoredActivities.some(activity => activity.id === props.application_id) || (settings.store.listMode === FilterMode.Whitelist && settings.store.idsList.includes(props.application_id));
286 } else {
287 const exePath = RunningGameStore.getRunningGames().find(game => game.name === props.name)?.exePath;
288 if (exePath) {
289 return !settings.store.ignoredActivities.some(activity => activity.id === exePath);
290 }
291 }
292
293 return true;
294 },
295
296 renderToggleGameActivityButton(props: { id?: string; name: string, exePath: string; }, nowPlaying: boolean) {
297 return (
298 <ErrorBoundary noop>
299 <div style={{ marginLeft: 12, zIndex: 0 }}>
300 {ToggleActivityComponent({ id: props.id ?? props.exePath, name: props.name, type: ActivitiesTypes.Game }, nowPlaying)}
301 </div>
302 </ErrorBoundary>
303 );
304 },
305
306 renderToggleActivityButton(props: { id: string; name: string; }) {
307 return (
308 <ErrorBoundary noop>
309 {ToggleActivityComponent({ id: props.id, name: props.name, type: ActivitiesTypes.Embedded })}
310 </ErrorBoundary>
311 );
312 }
313});
314