Skip to content

Commit f2fc1ac

Browse files
Merge pull request #621 from Nerimity/multi-activity
Multi activity
2 parents 196df4f + 5b44a8d commit f2fc1ac

File tree

17 files changed

+204
-94
lines changed

17 files changed

+204
-94
lines changed

src/chat-api/RawData.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,12 @@ export interface RawServer {
2121
};
2222
}
2323

24-
25-
export const InventoryItemType ={
24+
export const InventoryItemType = {
2625
Badge: "badge"
2726
} as const;
2827
export interface RawInventoryItem {
2928
id: string;
30-
itemType: typeof InventoryItemType[keyof typeof InventoryItemType];
29+
itemType: (typeof InventoryItemType)[keyof typeof InventoryItemType];
3130
userId: string;
3231
itemId: string;
3332
acquiredAt: number;
@@ -342,7 +341,7 @@ export interface RawPresence {
342341
userId: string;
343342
custom?: string;
344343
status: number;
345-
activity?: ActivityStatus;
344+
activities?: ActivityStatus[];
346345
}
347346

348347
export interface RawExploreItem {

src/chat-api/emits/userEmits.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,6 @@ export function dismissChannelNotification(channelId: string) {
66
socketClient.socket.emit(ClientEvents.NOTIFICATION_DISMISS, { channelId });
77
}
88

9-
export function emitActivityStatus(activity: ActivityStatus | null) {
10-
socketClient.socket.emit(ClientEvents.UPDATE_ACTIVITY, activity);
9+
export function emitActivityStatus(activities: ActivityStatus[] | null) {
10+
socketClient.socket.emit(ClientEvents.UPDATE_ACTIVITY, activities);
1111
}

src/chat-api/events/connectionEvents.ts

Lines changed: 11 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -78,8 +78,9 @@ export const onReconnectAttempt = () => {
7878
};
7979

8080
electronWindowAPI()?.activityStatusChanged((window) => {
81+
const id = "electron-activity";
8182
if (!window) {
82-
return emitActivityStatus(null);
83+
return localRPC.updateRPC(id);
8384
}
8485
const programs = getStorageObject<ProgramWithExtras[]>(
8586
StorageKeys.PROGRAM_ACTIVITY_STATUS,
@@ -90,39 +91,23 @@ electronWindowAPI()?.activityStatusChanged((window) => {
9091
);
9192

9293
if (!program) {
93-
return emitActivityStatus(null);
94+
localRPC.updateRPC(id);
9495
}
9596

96-
emitActivityStatus({
97-
action: program.action || "Playing",
98-
name: program.name,
99-
startedAt: window.createdAt,
100-
emoji: program.emoji
97+
localRPC.updateRPC(id, {
98+
action: program?.action || "Playing",
99+
name: program?.name || "",
100+
startedAt: window?.createdAt,
101+
emoji: program?.emoji
101102
});
102103
});
103104

104105
electronWindowAPI()?.rpcChanged((data) => {
105-
if (!data) {
106-
const programs = getStorageObject<ProgramWithExtras[]>(
107-
StorageKeys.PROGRAM_ACTIVITY_STATUS,
108-
[]
109-
);
110-
electronWindowAPI()?.restartActivityStatus(programs);
111-
return;
112-
}
113-
emitActivityStatus({ startedAt: Date.now(), ...data });
106+
localRPC.updateElectronRPCs(data);
114107
});
115108

116109
localRPC.onUpdateRPC = (data) => {
117-
if (!data) {
118-
emitActivityStatus(null);
119-
const programs = getStorageObject<ProgramWithExtras[]>(
120-
StorageKeys.PROGRAM_ACTIVITY_STATUS,
121-
[]
122-
);
123-
electronWindowAPI()?.restartActivityStatus(programs);
124-
}
125-
emitActivityStatus({ startedAt: Date.now(), ...data });
110+
emitActivityStatus(data.map((data) => ({ startedAt: Date.now(), ...data })));
126111
};
127112

128113
export const onAuthenticated = (payload: AuthenticatedPayload) => {
@@ -258,10 +243,10 @@ export const onAuthenticated = (payload: AuthenticatedPayload) => {
258243
StorageKeys.PROGRAM_ACTIVITY_STATUS,
259244
[]
260245
);
246+
localRPC.start();
261247
electronWindowAPI()?.restartActivityStatus(programs);
262248

263249
electronWindowAPI()?.restartRPCServer();
264-
localRPC.start();
265250
useDiscordActivityTracker().restart();
266251
useLastFmActivityTracker().restart();
267252

src/chat-api/events/userEvents.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ export function onUserPresenceUpdate(payload: {
2020
userId: string;
2121
status?: UserStatus;
2222
custom?: string;
23-
activity?: ActivityStatus;
23+
activities?: ActivityStatus[];
2424
}) {
2525
const users = useUsers();
2626
const account = useAccount();
@@ -32,8 +32,8 @@ export function onUserPresenceUpdate(payload: {
3232
users.setPresence(payload.userId, {
3333
...(payload.status !== undefined ? { status: payload.status } : undefined),
3434
...(payload.custom !== undefined ? { custom: payload.custom } : undefined),
35-
...(payload.activity !== undefined
36-
? { activity: payload.activity }
35+
...(payload.activities !== undefined
36+
? { activities: payload.activities }
3737
: undefined)
3838
});
3939
}

src/chat-api/store/useUsers.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ export interface Presence {
2626
userId: string;
2727
custom?: string | null;
2828
status: UserStatus;
29-
activity?: ActivityStatus;
29+
activities?: ActivityStatus[];
3030
}
3131

3232
export const avatarUrl = (item: { avatar?: string }): string | null =>
@@ -137,7 +137,7 @@ const setPresence = (userId: string, presence: Partial<Presence>) => {
137137
return;
138138
}
139139
if (presence.custom === null) presence.custom = undefined;
140-
if (presence.activity === null) presence.activity = undefined;
140+
141141
setUserPresences(userId, { ...presence, userId });
142142
};
143143

src/common/Electron.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ export interface RPC {
2626
endsAt?: number;
2727
speed?: number;
2828
updatedAt?: number;
29+
emoji?: string;
2930
}
3031

3132
export const [spellcheckSuggestions, setSpellcheckSuggestions] = createSignal<
@@ -72,7 +73,7 @@ interface WindowAPI {
7273
): void;
7374

7475
restartRPCServer(): void;
75-
rpcChanged(callback: (data: RPC | false) => void): void;
76+
rpcChanged(callback: (data: { id: string; data: RPC }[]) => void): void;
7677
relaunchApp(): void;
7778

7879
onSpellcheck(callback: (suggestions: string[]) => void): void;

src/common/LocalRPC.ts

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import { RPC } from "./Electron";
22

33
export class LocalRPC {
4-
onUpdateRPC: (data: RPC | false) => void = () => {};
4+
onUpdateRPC: (data: RPC[]) => void = () => {};
55

66
RPCs: { data: RPC; id: string }[] = [];
7+
discordRPCs: { data: RPC; id: string }[] = [];
8+
electronRPCs: { data: RPC; id: string }[] = [];
79

810
constructor() {
911
window.addEventListener(
@@ -22,40 +24,46 @@ export class LocalRPC {
2224
window.parent.postMessage({ name: "NERIMITY_READY" }, "*");
2325
}
2426
emitEvent() {
25-
const firstRPC = this.RPCs[0];
26-
if (!firstRPC) {
27-
return this.onUpdateRPC(false);
28-
}
29-
this.onUpdateRPC(firstRPC.data);
27+
const RPCs = this.RPCs.map((rpc) => rpc.data);
28+
const electronRPCs = this.electronRPCs.map((rpc) => rpc.data);
29+
const discordRPCs = this.discordRPCs.map((rpc) => rpc.data);
30+
this.onUpdateRPC([...electronRPCs, ...discordRPCs, ...RPCs]);
3031
}
3132

33+
updateElectronRPCs(data: { id: string; data: RPC }[]) {
34+
this.electronRPCs = data;
35+
this.emitEvent();
36+
}
37+
updateDiscordRPCs(data: { id: string; data: RPC }[]) {
38+
this.discordRPCs = data;
39+
this.emitEvent();
40+
}
3241
updateRPC(id: string, data?: RPC) {
3342
if (!data) return this.removeRPC(id);
43+
3444
const index = this.RPCs.findIndex((rpc) => rpc.id === id);
3545
if (index === -1) {
3646
this.RPCs.push({
3747
id,
3848
data: sanitizedData(data)
3949
});
40-
if (this.RPCs.length === 1) this.emitEvent();
50+
this.emitEvent();
4151
return;
4252
}
4353

4454
if (JSONCompare(this.RPCs?.[index]?.data, sanitizedData(data))) {
4555
return;
4656
}
4757
this.RPCs[index]!.data = sanitizedData(data);
48-
if (index === 0) this.emitEvent();
58+
this.emitEvent();
4959
}
5060
removeRPC(id: string) {
5161
const index = this.RPCs.findIndex((rpc) => rpc.id === id);
5262
if (index === -1) {
5363
return;
5464
}
5565
this.RPCs.splice(index, 1);
56-
if (index === 0) {
57-
this.emitEvent();
58-
}
66+
this.emitEvent();
5967
}
6068
}
6169

src/common/useDiscordActivityTracker.ts

Lines changed: 29 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ interface FormattedPresence {
1919
}
2020
export interface FormattedActivity {
2121
name: string;
22+
applicationId: string | null;
2223
createdTimestamp: number | null;
2324
details: string | null;
2425
state: string | null;
@@ -89,7 +90,7 @@ export const useDiscordActivityTracker = () => {
8990
handleActivity(data);
9091
};
9192
const handleActivity = debounce((data: FormattedPresence) => {
92-
const activity = data.activities
93+
const activities = data.activities
9394
.filter((a) => a.type !== ActivityType.CUSTOM)
9495
.sort((a, b) => {
9596
const isASpotify = !!a.assets?.largeImage?.startsWith("spotify:");
@@ -105,36 +106,40 @@ export const useDiscordActivityTracker = () => {
105106

106107
// If neither or both are Spotify, maintain the original order (or add another sorting criteria)
107108
return 0;
108-
})[0];
109-
if (!activity) {
110-
localRPC.updateRPC(NERIMITY_APP_ID);
111-
return;
112-
}
113-
let url = undefined;
109+
});
114110

115-
const isSpotify = !!activity.assets?.largeImage?.startsWith("spotify:");
111+
let url: string | undefined = undefined;
116112

117-
if (isSpotify && activity.syncId) {
118-
url = `https://open.spotify.com/track/${activity.syncId}`;
119-
}
113+
const mapped = activities.map((activity) => {
114+
const isSpotify = !!activity.assets?.largeImage?.startsWith("spotify:");
120115

121-
console.log(`Activity Update: ${activity?.name || null}`);
122-
localRPC.updateRPC(NERIMITY_APP_ID, {
123-
startedAt: activity.timestamps?.start || undefined,
124-
endsAt: activity.timestamps?.end || undefined,
125-
imgSrc:
126-
activity.assets?.largeImageUrl ||
127-
activity.assets?.smallImageUrl ||
128-
undefined,
129-
title: activity.details || undefined,
130-
subtitle: activity.state || undefined,
131-
link: url || activity.url || undefined,
132-
...ActivityTypeToNameAndAction(activity)
116+
if (isSpotify && activity.syncId) {
117+
url = `https://open.spotify.com/track/${activity.syncId}`;
118+
}
119+
120+
console.log(`Activity Update: ${activity?.name || null}`);
121+
return {
122+
id: activity.syncId || activity.applicationId || NERIMITY_APP_ID,
123+
data: {
124+
startedAt: activity.timestamps?.start || undefined,
125+
endsAt: activity.timestamps?.end || undefined,
126+
imgSrc:
127+
activity.assets?.largeImageUrl ||
128+
activity.assets?.smallImageUrl ||
129+
undefined,
130+
title: activity.details || undefined,
131+
subtitle: activity.state || undefined,
132+
link: url || activity.url || undefined,
133+
...ActivityTypeToNameAndAction(activity)
134+
}
135+
};
133136
});
137+
138+
localRPC.updateDiscordRPCs(mapped);
134139
}, 500);
135140

136141
ws.onclose = () => {
137-
localRPC.updateRPC(NERIMITY_APP_ID);
142+
localRPC.updateDiscordRPCs([]);
138143
clearInterval(pingIntervalId);
139144
clearTimeout(restartDelayTimeoutId);
140145
restartDelayTimeoutId = setTimeout(() => {

src/components/DashboardPane.tsx

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import style from "./DashboardPane.module.css";
2-
import { RawPost } from "@/chat-api/RawData";
2+
import { ActivityStatus, RawPost } from "@/chat-api/RawData";
33
import {
44
getAnnouncementPosts,
55
getPostNotificationCount,
@@ -296,16 +296,21 @@ const ActivityList = () => {
296296
activityListEl.scrollLeft += event.deltaX;
297297
};
298298

299-
const activities = () => {
299+
const presences = () => {
300300
const presences = store.users.presencesArray();
301301
return presences
302302
.filter(
303303
(p) =>
304-
p.activity &&
304+
p.activities?.length &&
305305
!users.get(p.userId)?.bot &&
306306
!store.friends.hasBeenBlockedByMe(p.userId)
307307
)
308-
.sort((a, b) => b.activity!.startedAt - a.activity!.startedAt);
308+
.map((p) => ({
309+
...p,
310+
activities: [...(p.activities ?? [])].sort(
311+
(a, b) => b!.startedAt - a!.startedAt
312+
)
313+
}));
309314
};
310315

311316
const authenticatedInPast = () => account.lastAuthenticatedAt();
@@ -331,28 +336,37 @@ const ActivityList = () => {
331336
</Skeleton.List>
332337
</Show>
333338

334-
<Show when={authenticatedInPast() && !activities().length}>
339+
<Show when={authenticatedInPast() && !presences().length}>
335340
<div class={style.noActivity}>
336341
<Text size={14} opacity={0.6}>
337342
{t("dashboard.noActiveUsers")}
338343
</Text>
339344
</div>
340345
</Show>
341346

342-
<Show when={authenticatedInPast() && activities().length}>
343-
<For each={activities()}>
344-
{(activity) => <PresenceItem presence={activity} />}
347+
<Show when={authenticatedInPast() && presences().length}>
348+
<For each={presences()}>
349+
{(presence) => (
350+
<For each={presence.activities}>
351+
{(activity) => (
352+
<PresenceItem presence={presence} activity={activity} />
353+
)}
354+
</For>
355+
)}
345356
</For>
346357
</Show>
347358
</div>
348359
);
349360
};
350361

351-
const PresenceItem = (props: { presence: Presence }) => {
362+
const PresenceItem = (props: {
363+
presence: Presence;
364+
activity: ActivityStatus;
365+
}) => {
352366
const navigate = useNavigate();
353367
const store = useStore();
354368

355-
const activity = () => props.presence.activity!;
369+
const activity = () => props.activity;
356370

357371
const user = () => {
358372
return store.users.get(props.presence.userId);
@@ -416,7 +430,7 @@ const PresenceItem = (props: { presence: Presence }) => {
416430
/>
417431
<Text size={14} opacity={0.7}>
418432
{" "}
419-
{props.presence.activity?.name}
433+
{props.activity?.name}
420434
</Text>
421435
</span>
422436

0 commit comments

Comments
 (0)