Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion assets/lang/en-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -287,7 +287,7 @@
"games-blacklist": "Blacklist",
"games-blacklisted": "Blacklisted games",
"games-blacklistedEmpty": "No blacklisted games.",
"games-removeFromBlacklist": "Remove from blacklist",
"games-removeFromBlacklist": "Remove",
"games-application": "Application",
"screenshare-selectSource": "Please select a source",
"screenshare-venmicDisabled": "Venmic disabled",
Expand Down
19 changes: 19 additions & 0 deletions meta/app.legcord.Legcord.metainfo.xml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,25 @@
</screenshot>
</screenshots>
<releases>
<release version="1.2.2" date="2026-02-21" type="stable">
<url>https://github.com/Legcord/Legcord/releases/tag/v1.2.2</url>
<description>
<p>What’s New in Legcord v1.2.2 ✨</p>
<ul>
<li>🚫 **Game blacklist for arRPC detection**</li>
</ul>
<ul>
<li>⚡ **Electron updated** for better stability and compatibility.</li>
<li>🎨 **Quick CSS toggle added**.</li>
<li>🪟 Fixed **Windows titlebar duplication** ([https://github.com/Legcord/Legcord/pull/1009](https://github.com/Legcord/Legcord/pull/1009) by @passionvine).</li>
<li>🧪 **Custom Chromium flags support**</li>
</ul>
<ul>
<li>🛠️ Fixed **settings menu version patch** ([https://github.com/Legcord/Legcord/pull/1014](https://github.com/Legcord/Legcord/pull/1014) by @MahmodZE).</li>
<li>🧹 Fixed various **build warnings** ([https://github.com/Legcord/Legcord/pull/1017](https://github.com/Legcord/Legcord/pull/1017) by @MahmodZE).</li>
</ul>
</description>
</release>
<release version="1.2.1" date="2026-01-30" type="stable">
<url>https://github.com/Legcord/Legcord/releases/tag/v1.2.1</url>
<description>
Expand Down
3 changes: 3 additions & 0 deletions src/@types/legcordWindow.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,9 @@ export interface LegcordWindow {
addDetectable: (e: Game) => void;
removeDetectable: (id: string) => void;
getDetectables: () => GameList;
getBlacklist: () => DetectedGame[];
blacklistGame: (name: string, id: number) => void;
unblacklistGame: (id: number) => void;
};
/** Plugin storage API. Requires user to enable "Extended plugin abilities" in Legcord settings. */
fs: {
Expand Down
2 changes: 0 additions & 2 deletions src/@types/settings.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,4 @@ export interface Settings {
scanInterval: number;
modCache?: Record<ValidMods, string>;
extendedPluginAbilities: boolean;
/** Application IDs to hide from Discord rich presence activity. */
rpcActivityBlacklist: number[];
}
84 changes: 84 additions & 0 deletions src/common/blacklistGame.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
import path from "node:path";
import { app } from "electron";

export interface RpcBlacklistEntry {
name: string;
id: number;
}

let blacklistCache: RpcBlacklistEntry[] | null = null;
let blacklistCacheTime = 0;
const BLACKLIST_CACHE_TTL = 500;

export function getBlacklistLocation(): string {
const userDataPath = app.getPath("userData");
const storagePath = path.join(userDataPath, "storage");
return path.join(storagePath, "blacklist.json");
}

function ensureStorageDir(): void {
const loc = getBlacklistLocation();
const dir = path.dirname(loc);
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
}

function normalizeEntry(g: unknown): RpcBlacklistEntry | null {
if (!g || typeof g !== "object") return null;
const o = g as Record<string, unknown>;
const id = Number(o.id);
const name = typeof o.name === "string" ? o.name : "";
if (Number.isNaN(id) || !name) return null;
return { name, id };
}

export function getBlacklist(): RpcBlacklistEntry[] {
const now = Date.now();
if (blacklistCache !== null && now - blacklistCacheTime < BLACKLIST_CACHE_TTL) {
return blacklistCache;
}
ensureStorageDir();
const loc = getBlacklistLocation();
if (!existsSync(loc)) {
blacklistCache = [];
blacklistCacheTime = now;
return blacklistCache;
}
try {
const raw = readFileSync(loc, "utf-8");
const parsed = JSON.parse(raw);
const list = Array.isArray(parsed)
? parsed.map(normalizeEntry).filter((e): e is RpcBlacklistEntry => e !== null)
: [];
blacklistCache = list;
blacklistCacheTime = now;
return blacklistCache;
} catch {
blacklistCache = [];
blacklistCacheTime = now;
return blacklistCache;
}
}

function saveBlacklist(list: RpcBlacklistEntry[]): void {
ensureStorageDir();
writeFileSync(getBlacklistLocation(), JSON.stringify(list, null, 4), "utf-8");
blacklistCache = list;
blacklistCacheTime = Date.now();
}

export function blacklistGame(name: string, id: number): void {
const list = getBlacklist();
const entryId = Number(id);
const entryName = String(name);
if (Number.isNaN(entryId) || !entryName) return;
if (list.some((e) => e.id === entryId)) return;
saveBlacklist([...list, { name: entryName, id: entryId }]);
}

export function unblacklistGame(id: number): void {
const list = getBlacklist();
const entryId = Number(id);
if (Number.isNaN(entryId)) return;
saveBlacklist(list.filter((e) => e.id !== entryId));
}
1 change: 0 additions & 1 deletion src/common/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,6 @@ const defaults: Settings = {
useSystemCssEditor: false,
extendedPluginAbilities: false,
quickCss: true,
rpcActivityBlacklist: [],
};

const safeMode: Settings = {
Expand Down
19 changes: 18 additions & 1 deletion src/discord/ipc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ import isDev from "electron-is-dev";
import type { Keybind } from "../@types/keybind.js";
import type { Settings } from "../@types/settings.js";
import type { ThemeManifest } from "../@types/themeManifest.js";
import {
blacklistGame as blacklistGameAdd,
unblacklistGame as blacklistGameRemove,
getBlacklist,
} from "../common/blacklistGame.js";
import { getConfig, getConfigLocation, setConfig, setConfigBulk } from "../common/config.js";
import { addDetectable, getDetectables, removeDetectable } from "../common/detectables.js";
import { getLang, getLangName, getRawLang, setLang } from "../common/lang.js";
Expand Down Expand Up @@ -241,8 +246,20 @@ export function registerIpc(passedWindow: BrowserWindow): void {
console.log(`=== Chrome Flags === ${JSON.stringify(flags)}`);
event.returnValue = flags;
});
ipcMain.on("setConfig", (_event, key: keyof Settings, value: string) => {
ipcMain.on("setConfig", (event, key: keyof Settings, value: Settings[keyof Settings]) => {
setConfig(key, value);
event.returnValue = undefined;
});
ipcMain.on("getRpcBlacklist", (event) => {
event.returnValue = getBlacklist();
});
ipcMain.on("blacklistGame", (event, name: string, id: number) => {
blacklistGameAdd(name, id);
event.returnValue = undefined;
});
ipcMain.on("unblacklistGame", (event, id: number) => {
blacklistGameRemove(id);
event.returnValue = undefined;
});
ipcMain.on("addKeybind", (_event, keybind: Keybind) => {
const keybinds = getConfig("keybinds");
Expand Down
5 changes: 4 additions & 1 deletion src/discord/preload/bridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ contextBridge.exposeInMainWorld("legcord", {
},
settings: {
getConfig: () => ipcRenderer.sendSync("getEntireConfig") as Settings,
setConfig: (key: string, value: string) => ipcRenderer.send("setConfig", key, value),
setConfig: (key: string, value: unknown) => ipcRenderer.sendSync("setConfig", key, value),
addKeybind: (keybind: Keybind) => ipcRenderer.send("addKeybind", keybind),
toggleKeybind: (id: string) => ipcRenderer.send("toggleKeybind", id),
removeKeybind: (id: string) => ipcRenderer.send("removeKeybind", id),
Expand Down Expand Up @@ -101,6 +101,9 @@ contextBridge.exposeInMainWorld("legcord", {
addDetectable: (detectable: Game) => ipcRenderer.send("addDetectable", detectable),
removeDetectable: (id: string) => ipcRenderer.send("removeDetectable", id),
getDetectables: () => ipcRenderer.sendSync("getDetectables") as Game[],
getBlacklist: () => ipcRenderer.sendSync("getRpcBlacklist") as { name: string; id: number }[],
blacklistGame: (name: string, id: number) => ipcRenderer.sendSync("blacklistGame", name, id),
unblacklistGame: (id: number) => ipcRenderer.sendSync("unblacklistGame", id),
},
fs: {
/**
Expand Down
11 changes: 7 additions & 4 deletions src/shelter/rpc/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,13 @@ async function listen(msg: {
);
rpc.onLastDetectedUpdate?.(rpc.lastDetectedGames);

const raw = window.legcord.settings.getConfig().rpcActivityBlacklist ?? [];
const blacklist = Array.isArray(raw) ? raw.map((x) => Number(x)).filter((n) => !Number.isNaN(n)) : [];
if (blacklist.includes(Number(appId))) return console.log(`activity ${appId} is blacklisted, skipping...`);

const blacklist = window.legcord.rpc.getBlacklist();
if (blacklist.some((g) => g.id === Number(appId))) {
// @ts-expect-error
msg.activity = null; // clear activity to prevent blacklisted game from showing up in status
FluxDispatcher.dispatch({ type: "LOCAL_ACTIVITY_UPDATE", ...msg });
return console.log(`Game ${gameName} (${appId}) is blacklisted, skipping...`);
}
if (
msg.activity?.assets?.large_image?.startsWith("https://") ??
msg.activity?.assets?.small_image?.startsWith("https://")
Expand Down
66 changes: 35 additions & 31 deletions src/shelter/settings/pages/RegisteredGamesPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import { sleep } from "../../../common/sleep.js";
import { AddDetectableModal } from "../components/AddDetectableModal.jsx";
import { DetectableCard } from "../components/DetectableCard.jsx";
import { Dropdown } from "../components/Dropdown.jsx";
import { refreshSettings, setConfig } from "../settings.js";
import classes from "./RegisteredGames.module.css";

const {
Expand All @@ -27,34 +26,39 @@ export function RegisteredGamesPage() {
});
}

function getBlacklist(): number[] {
const raw = shelter.plugin.store.settings?.rpcActivityBlacklist ?? [];
return Array.isArray(raw) ? raw.map((x) => Number(x)).filter((n) => !Number.isNaN(n)) : [];
function getBlacklist(): DetectedGame[] {
return window.legcord.rpc.getBlacklist();
}

function blacklistGame(id: number) {
const list = getBlacklist();
if (list.includes(id)) return;
setConfig("rpcActivityBlacklist", [...list, id]);
refreshSettings();
function blacklistGame(name: string, id: number) {
window.legcord.rpc.blacklistGame(name, id);
setBlacklistVersion((v) => v + 1);
setLastDetected((list) => filterBlacklisted(list));
}

function unblacklistGame(id: number) {
setConfig(
"rpcActivityBlacklist",
getBlacklist().filter((bid) => bid !== id),
);
refreshSettings();
window.legcord.rpc.unblacklistGame(id);
setBlacklistVersion((v) => v + 1);
}

function filterBlacklisted(list: DetectedGame[]) {
const returnList: DetectedGame[] = [];
list.forEach((game) => {
if (!getBlacklist().some((g) => g.id === Number(game.id))) {
console.log(`game ${game.name} (${game.id}) is not blacklisted, adding to list...`);
console.log(getBlacklist());
returnList.push(game);
}
});
return returnList;
}

onMount(() => {
refreshDetectables();
const rpc = window.legcordRPC;
if (rpc) {
setLastDetected(rpc.lastDetectedGames ?? []);
rpc.onLastDetectedUpdate = (list) => setLastDetected(list ?? []);
setLastDetected(filterBlacklisted(rpc.lastDetectedGames ?? []));
rpc.onLastDetectedUpdate = (list) => setLastDetected(filterBlacklisted(list ?? []));
onCleanup(() => {
if (rpc) rpc.onLastDetectedUpdate = null;
});
Expand Down Expand Up @@ -137,11 +141,13 @@ export function RegisteredGamesPage() {
<For each={lastDetected()}>
{(game) => (
<li class={classes.gameRow}>
<span class={classes.gameName}>{game.name}</span>
<span class={classes.gameName}>
{game.name} ({game.id})
</span>
<Button
size={ButtonSizes.SMALL}
onClick={() => blacklistGame(game.id)}
disabled={blacklisted().includes(game.id)}
onClick={() => blacklistGame(game.name, game.id)}
disabled={blacklisted().some((g) => g.id === game.id)}
>
{t["games-blacklist"]}
</Button>
Expand All @@ -165,18 +171,16 @@ export function RegisteredGamesPage() {
>
<ul class={classes.gameList}>
<For each={blacklisted()}>
{(id) => {
const name =
lastDetected().find((g) => g.id === id)?.name ?? `${t["games-application"]} (${id})`;
return (
<li class={classes.gameRow}>
<span class={classes.gameName}>{name}</span>
<Button size={ButtonSizes.SMALL} onClick={() => unblacklistGame(id)}>
{t["games-removeFromBlacklist"]}
</Button>
</li>
);
}}
{(game) => (
<li class={classes.gameRow}>
<span class={classes.gameName}>
{game.name} ({game.id})
</span>
<Button size={ButtonSizes.SMALL} onClick={() => unblacklistGame(game.id)}>
{t["games-removeFromBlacklist"]}
</Button>
</li>
)}
</For>
</ul>
</Show>
Expand Down
Loading