YouBar+: fix presence updates never firing (wrong Flux event name) and inbox toggle needing a restart

This commit is contained in:
Ally Founder 2026-08-12 14:02:04 -04:00
parent d6d3c711c7
commit 1663602e72
3 changed files with 40 additions and 19 deletions

View file

@ -1,11 +1,10 @@
import { logger } from "@vendetta"; import { logger } from "@vendetta";
import { storage } from "@vendetta/plugin"; import { storage } from "@vendetta/plugin";
import patchYouBarButtons from "./patches/youBarButtons"; import patchYouBarButtons from "./patches/youBarButtons";
import { startTrackingNotifications } from "./lib/notifications"; import { setInboxTracking } from "./lib/notifications";
import Settings from "./ui/Settings"; import Settings from "./ui/Settings";
let unpatchButtons: () => void = () => {}; let unpatchButtons: () => void = () => {};
let stopTrackingNotifications: () => void = () => {};
let patched = false; let patched = false;
let retryHandle: ReturnType<typeof setInterval> | undefined; let retryHandle: ReturnType<typeof setInterval> | undefined;
@ -39,11 +38,7 @@ export default {
storage.showInboxButton ??= false; storage.showInboxButton ??= false;
storage.notifications ??= []; storage.notifications ??= [];
// Same "may require a restart" contract as the other buttons - only tracks in the setInboxTracking(!!storage.showInboxButton);
// background when the feature is actually on, not for every YouBar+ user by default.
if (storage.showInboxButton) {
stopTrackingNotifications = startTrackingNotifications();
}
attempt(); attempt();
if (!patched) { if (!patched) {
@ -57,7 +52,7 @@ export default {
onUnload: () => { onUnload: () => {
stopRetrying(); stopRetrying();
unpatchButtons(); unpatchButtons();
stopTrackingNotifications(); setInboxTracking(false);
}, },
settings: Settings settings: Settings
}; };

View file

@ -263,9 +263,10 @@ function handleThreadMembersUpdate(payload: any) {
// Friends only, no bots - a status update from a random server member or a bot's "Playing" state // Friends only, no bots - a status update from a random server member or a bot's "Playing" state
// isn't the kind of thing worth an inbox entry for. // isn't the kind of thing worth an inbox entry for.
function handlePresenceUpdate(payload: any) { function handlePresenceUpdate(update: any) {
try { try {
const userId = payload?.user?.id ?? payload?.userId; const user = update?.user;
const userId = user?.id;
if (!userId) return; if (!userId) return;
const currentUser = UserStore?.getCurrentUser?.(); const currentUser = UserStore?.getCurrentUser?.();
@ -273,11 +274,9 @@ function handlePresenceUpdate(payload: any) {
const friendIds: string[] = RelationshipStore?.getFriendIDs?.() ?? []; const friendIds: string[] = RelationshipStore?.getFriendIDs?.() ?? [];
if (!friendIds.includes(userId)) return; if (!friendIds.includes(userId)) return;
if (user.bot) return;
const user = UserStore?.getUser?.(userId); const activities = update?.activities;
if (user?.bot) return;
const activities = payload?.activities;
const activity = Array.isArray(activities) ? activities.find((a: any) => a?.type !== 4) : undefined; const activity = Array.isArray(activities) ? activities.find((a: any) => a?.type !== 4) : undefined;
const signature = activity ? `${activity.type}:${activity.name}` : ""; const signature = activity ? `${activity.type}:${activity.name}` : "";
@ -287,7 +286,7 @@ function handlePresenceUpdate(payload: any) {
if (!activity) return; // cleared their activity - nothing to notify about if (!activity) return; // cleared their activity - nothing to notify about
const verb = ACTIVITY_VERBS[activity.type] ?? "Playing"; const verb = ACTIVITY_VERBS[activity.type] ?? "Playing";
const displayName = user?.globalName || user?.username || payload?.user?.globalName || payload?.user?.username || "A friend"; const displayName = user.globalName || user.username || "A friend";
pushNotification({ pushNotification({
id: `presence-${userId}-${Date.now()}`, id: `presence-${userId}-${Date.now()}`,
@ -297,14 +296,24 @@ function handlePresenceUpdate(payload: any) {
guildName: "", guildName: "",
channelName: "", channelName: "",
timestamp: new Date().toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }), timestamp: new Date().toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }),
author: user ?? payload?.user, author: user,
}); });
} catch (err) { } catch (err) {
console.error("[YouBar+] Inbox presence error:", err); console.error("[YouBar+] Inbox presence error:", err);
} }
} }
export function startTrackingNotifications(): () => void { // The raw gateway op is singular PRESENCE_UPDATE, but Discord's own socket layer batches them
// before they ever reach Flux - confirmed against the decompiled bundle's handlePresenceUpdates,
// which reads payload.updates (an array of {guildId, user, status, activities}). Subscribing to
// the singular name means this handler simply never fires.
function handlePresenceUpdates(payload: any) {
const updates = payload?.updates;
if (!Array.isArray(updates)) return;
for (const update of updates) handlePresenceUpdate(update);
}
function startTrackingNotifications(): () => void {
memoryNotifications = Array.isArray(storage.notifications) ? [...storage.notifications] : []; memoryNotifications = Array.isArray(storage.notifications) ? [...storage.notifications] : [];
const unsubMessage = fluxSubscribe("MESSAGE_CREATE", handleIncomingMessage); const unsubMessage = fluxSubscribe("MESSAGE_CREATE", handleIncomingMessage);
@ -312,7 +321,7 @@ export function startTrackingNotifications(): () => void {
const unsubRelationship = fluxSubscribe("RELATIONSHIP_ADD", handleRelationshipAdd); const unsubRelationship = fluxSubscribe("RELATIONSHIP_ADD", handleRelationshipAdd);
const unsubFriendAccepted = fluxSubscribe("FRIEND_REQUEST_ACCEPTED", handleFriendRequestAccepted); const unsubFriendAccepted = fluxSubscribe("FRIEND_REQUEST_ACCEPTED", handleFriendRequestAccepted);
const unsubThreadMembers = fluxSubscribe("THREAD_MEMBERS_UPDATE", handleThreadMembersUpdate); const unsubThreadMembers = fluxSubscribe("THREAD_MEMBERS_UPDATE", handleThreadMembersUpdate);
const unsubPresence = fluxSubscribe("PRESENCE_UPDATE", handlePresenceUpdate); const unsubPresence = fluxSubscribe("PRESENCE_UPDATES", handlePresenceUpdates);
return () => { return () => {
if (saveTimeout) clearTimeout(saveTimeout); if (saveTimeout) clearTimeout(saveTimeout);
@ -326,3 +335,18 @@ export function startTrackingNotifications(): () => void {
lastActivitySignature.clear(); lastActivitySignature.clear();
}; };
} }
let stopTracking: () => void = () => {};
let tracking = false;
// Lets the settings toggle flip tracking on/off live instead of requiring a restart to take effect.
export function setInboxTracking(enabled: boolean) {
if (enabled === tracking) return;
tracking = enabled;
if (enabled) {
stopTracking = startTrackingNotifications();
} else {
stopTracking();
stopTracking = () => {};
}
}

View file

@ -5,6 +5,7 @@ import { TableRowGroup, TableSwitchRow } from "@shared/ui/table";
import SettingsScaffold from "@shared/ui/SettingsScaffold"; import SettingsScaffold from "@shared/ui/SettingsScaffold";
import NoteBox from "@shared/ui/NoteBox"; import NoteBox from "@shared/ui/NoteBox";
import { updateYouBar } from "../patches/youBarButtons"; import { updateYouBar } from "../patches/youBarButtons";
import { setInboxTracking } from "../lib/notifications";
export default function Settings() { export default function Settings() {
@ -34,10 +35,11 @@ export default function Settings() {
/> />
<TableSwitchRow <TableSwitchRow
label="Inbox button" label="Inbox button"
subLabel="Show a categorized mentions/replies/reactions inbox in the YouBar - requires a restart to take effect" subLabel="Show a categorized mentions/replies/reactions inbox in the YouBar"
value={!!storage.showInboxButton} value={!!storage.showInboxButton}
onValueChange={(v: boolean) => { onValueChange={(v: boolean) => {
storage.showInboxButton = v; storage.showInboxButton = v;
setInboxTracking(v);
updateYouBar(); updateYouBar();
}} }}
/> />