aboutsummaryrefslogtreecommitdiff
path: root/src/plugins/ignoreActivities.tsx
blob: 981145c681b632309714fdb5e556d38e96567066 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
/*
 * 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 * as DataStore from "@api/DataStore";
import ErrorBoundary from "@components/ErrorBoundary";
import { Devs } from "@utils/constants";
import { useForceUpdater } from "@utils/misc";
import definePlugin from "@utils/types";
import { findByPropsLazy } from "@webpack";
import { Tooltip } from "webpack/common";

enum ActivitiesTypes {
    Game,
    Embedded
}

interface IgnoredActivity {
    id: string;
    type: ActivitiesTypes;
}

const RegisteredGamesClasses = findByPropsLazy("overlayToggleIconOff", "overlayToggleIconOn");
const PreviewBadgeClasses = findByPropsLazy("previewBadge", "previewBadgeIcon");
const BaseShapeRoundClasses = findByPropsLazy("baseShapeRound", "baseShapeRoundLeft", "baseShapeRoundRight");
const RunningGameStore = findByPropsLazy("getRunningGames", "getGamesSeen");

function ToggleIconOff() {
    return (
        <svg
            className={RegisteredGamesClasses.overlayToggleIconOff}
            height="24"
            width="24"
            viewBox="0 0 32 26"
            aria-hidden={true}
            role="img"
        >
            <g
                fill="none"
                fillRule="evenodd"
            >
                <path
                    className={RegisteredGamesClasses.fill}
                    fill="currentColor"
                    d="M 16 8 C 7.664063 8 1.25 15.34375 1.25 15.34375 L 0.65625 16 L 1.25 16.65625 C 1.25 16.65625 7.097656 23.324219 14.875 23.9375 C 15.246094 23.984375 15.617188 24 16 24 C 16.382813 24 16.753906 23.984375 17.125 23.9375 C 24.902344 23.324219 30.75 16.65625 30.75 16.65625 L 31.34375 16 L 30.75 15.34375 C 30.75 15.34375 24.335938 8 16 8 Z M 16 10 C 18.203125 10 20.234375 10.601563 22 11.40625 C 22.636719 12.460938 23 13.675781 23 15 C 23 18.613281 20.289063 21.582031 16.78125 21.96875 C 16.761719 21.972656 16.738281 21.964844 16.71875 21.96875 C 16.480469 21.980469 16.242188 22 16 22 C 15.734375 22 15.476563 21.984375 15.21875 21.96875 C 11.710938 21.582031 9 18.613281 9 15 C 9 13.695313 9.351563 12.480469 9.96875 11.4375 L 9.9375 11.4375 C 11.71875 10.617188 13.773438 10 16 10 Z M 16 12 C 14.34375 12 13 13.34375 13 15 C 13 16.65625 14.34375 18 16 18 C 17.65625 18 19 16.65625 19 15 C 19 13.34375 17.65625 12 16 12 Z M 7.25 12.9375 C 7.09375 13.609375 7 14.285156 7 15 C 7 16.753906 7.5 18.394531 8.375 19.78125 C 5.855469 18.324219 4.105469 16.585938 3.53125 16 C 4.011719 15.507813 5.351563 14.203125 7.25 12.9375 Z M 24.75 12.9375 C 26.648438 14.203125 27.988281 15.507813 28.46875 16 C 27.894531 16.585938 26.144531 18.324219 23.625 19.78125 C 24.5 18.394531 25 16.753906 25 15 C 25 14.285156 24.90625 13.601563 24.75 12.9375 Z"
                />
                <rect
                    className={RegisteredGamesClasses.fill}
                    x="3"
                    y="26"
                    width="26"
                    height="2"
                    transform="rotate(-45 2 20)"
                />
            </g>
        </svg>
    );
}

function ToggleIconOn() {
    return (
        <svg
            className={RegisteredGamesClasses.overlayToggleIconOn}
            height="24"
            width="24"
            viewBox="0 0 32 26"
        >
            <path
                className={RegisteredGamesClasses.fill}
                d="M 16 8 C 7.664063 8 1.25 15.34375 1.25 15.34375 L 0.65625 16 L 1.25 16.65625 C 1.25 16.65625 7.097656 23.324219 14.875 23.9375 C 15.246094 23.984375 15.617188 24 16 24 C 16.382813 24 16.753906 23.984375 17.125 23.9375 C 24.902344 23.324219 30.75 16.65625 30.75 16.65625 L 31.34375 16 L 30.75 15.34375 C 30.75 15.34375 24.335938 8 16 8 Z M 16 10 C 18.203125 10 20.234375 10.601563 22 11.40625 C 22.636719 12.460938 23 13.675781 23 15 C 23 18.613281 20.289063 21.582031 16.78125 21.96875 C 16.761719 21.972656 16.738281 21.964844 16.71875 21.96875 C 16.480469 21.980469 16.242188 22 16 22 C 15.734375 22 15.476563 21.984375 15.21875 21.96875 C 11.710938 21.582031 9 18.613281 9 15 C 9 13.695313 9.351563 12.480469 9.96875 11.4375 L 9.9375 11.4375 C 11.71875 10.617188 13.773438 10 16 10 Z M 16 12 C 14.34375 12 13 13.34375 13 15 C 13 16.65625 14.34375 18 16 18 C 17.65625 18 19 16.65625 19 15 C 19 13.34375 17.65625 12 16 12 Z M 7.25 12.9375 C 7.09375 13.609375 7 14.285156 7 15 C 7 16.753906 7.5 18.394531 8.375 19.78125 C 5.855469 18.324219 4.105469 16.585938 3.53125 16 C 4.011719 15.507813 5.351563 14.203125 7.25 12.9375 Z M 24.75 12.9375 C 26.648438 14.203125 27.988281 15.507813 28.46875 16 C 27.894531 16.585938 26.144531 18.324219 23.625 19.78125 C 24.5 18.394531 25 16.753906 25 15 C 25 14.285156 24.90625 13.601563 24.75 12.9375 Z"
            />
        </svg>
    );
}

function ToggleActivityComponent({ activity }: { activity: IgnoredActivity; }) {
    const forceUpdate = useForceUpdater();

    return (
        <Tooltip text="Toggle activity">
            {({ onMouseLeave, onMouseEnter }) => (
                <div
                    onMouseLeave={onMouseLeave}
                    onMouseEnter={onMouseEnter}
                    className={RegisteredGamesClasses.overlayToggleIcon}
                    role="button"
                    aria-label="Toggle activity"
                    tabIndex={0}
                    onClick={e => handleActivityToggle(e, activity, forceUpdate)}
                >
                    {
                        ignoredActivitiesCache.has(activity.id)
                            ? <ToggleIconOff />
                            : <ToggleIconOn />
                    }
                </div>
            )}
        </Tooltip>
    );
}

function ToggleActivityComponentWithBackground({ activity }: { activity: IgnoredActivity; }) {
    return (
        <div
            className={`${PreviewBadgeClasses.previewBadge} ${BaseShapeRoundClasses.baseShapeRound}`}
            style={{ padding: "0 2px" }}
        >
            <ToggleActivityComponent activity={activity} />
        </div>
    );
}

function handleActivityToggle(e: React.MouseEvent<HTMLDivElement, MouseEvent>, activity: IgnoredActivity, forceUpdateComponent: () => void) {
    e.stopPropagation();
    if (ignoredActivitiesCache.has(activity.id)) ignoredActivitiesCache.delete(activity.id);
    else ignoredActivitiesCache.set(activity.id, activity);
    forceUpdateComponent();
    saveCacheToDatastore();
}

async function saveCacheToDatastore() {
    await DataStore.set("IgnoreActivities_ignoredActivities", ignoredActivitiesCache);
}

let ignoredActivitiesCache = new Map<IgnoredActivity["id"], IgnoredActivity>();

export default definePlugin({
    name: "IgnoreActivities",
    authors: [Devs.Nuckyz],
    description: "Ignore certain activities (like games and actual activities) from showing up on your status. You can configure which ones are ignored from the Registered Games and Activities tabs.",
    patches: [{
        find: ".Messages.SETTINGS_GAMES_OVERLAY_ON",
        replacement: {
            match: /(this.renderLastPlayed\(\)]}\),this.renderOverlayToggle\(\))/,
            replace: "$1,Vencord.Plugins.plugins.IgnoreActivities.renderToggleGameActivityButton(this.props)"
        }
    }, {
        find: ".Messages.NEW,name",
        replacement: {
            match: /\(\)\.badgeContainer.+?.\?\(0,.\.jsx\)\(.{1,2},{name:(?<props>.)\.name}\):null/,
            replace: "$&,Vencord.Plugins.plugins.IgnoreActivities.renderToggleActivityButton($<props>)"
        }
    }, {
        find: '.displayName="LocalActivityStore"',
        replacement: {
            match: /((.)\.push\(.\({type:.\..{1,3}\.LISTENING.+?;)/,
            replace: "$1$2=$2.filter(Vencord.Plugins.plugins.IgnoreActivities.isActivityEnabled);"
        }
    }],

    async start() {
        const ignoredActivitiesData = await DataStore.get<string[] | Map<IgnoredActivity["id"], IgnoredActivity>>("IgnoreActivities_ignoredActivities") ?? new Map<IgnoredActivity["id"], IgnoredActivity>();
        /** Migrate old data */
        if (Array.isArray(ignoredActivitiesData)) {
            for (const id of ignoredActivitiesData) {
                ignoredActivitiesCache.set(id, { id, type: ActivitiesTypes.Game });
            }

            await saveCacheToDatastore();
        } else ignoredActivitiesCache = ignoredActivitiesData;

        if (ignoredActivitiesCache.size !== 0) {
            const gamesSeen: { id?: string; exePath: string; }[] = RunningGameStore.getGamesSeen();

            for (const ignoredActivity of ignoredActivitiesCache.values()) {
                if (ignoredActivity.type !== ActivitiesTypes.Game) continue;

                if (!gamesSeen.some(game => game.id === ignoredActivity.id || game.exePath === ignoredActivity.id)) {
                    /** Custom added game which no longer exists */
                    ignoredActivitiesCache.delete(ignoredActivity.id);
                }
            }

            await saveCacheToDatastore();
        }
    },

    renderToggleGameActivityButton(props: { game: { id?: string; exePath: string; } | null; }) {
        if (!props.game) return (null);

        return (
            <ErrorBoundary noop>
                <ToggleActivityComponent activity={{ id: props.game.id ?? props.game.exePath, type: ActivitiesTypes.Game }} />
            </ErrorBoundary>
        );
    },

    renderToggleActivityButton(props: { id: string; }) {
        return (
            <ErrorBoundary noop>
                <ToggleActivityComponentWithBackground activity={{ id: props.id, type: ActivitiesTypes.Embedded }} />
            </ErrorBoundary>
        );
    },

    isActivityEnabled(props: { type: number; application_id?: string; name?: string; }) {
        if (props.type === 0) {
            if (props.application_id !== undefined) return !ignoredActivitiesCache.has(props.application_id);
            else {
                const exePath = RunningGameStore.getRunningGames().find(game => game.name === props.name)?.exePath;
                if (exePath) return !ignoredActivitiesCache.has(exePath);
            }
        }
        return true;
    },
});