Rejoin history from VRCNext's timeline; drop the private join memory
This commit is contained in:
1 parent
8d6850c825
commit
49b675bdcd
10 files changed
+250
-162
No files matched your search
+44
-3
@@ -15,6 +15,8 @@
|
||||
|
||||
import type { EventBus, PluginContext } from '@vrcnext/plugin-api';
|
||||
|
||||
import type { InstanceFilter } from './filters.js';
|
||||
import { summarise, toMeetEvent, toTimelineEntries, type MeetEvent, type MeetHistory } from './history.js';
|
||||
import type { Settings } from './settings.js';
|
||||
import {
|
||||
profileLoaded,
|
||||
@@ -45,6 +47,7 @@ export interface Facts {
|
||||
readonly avatar: AvatarFacts | undefined;
|
||||
/** `undefined` when no group filter is configured. */
|
||||
readonly inGroup: boolean | undefined;
|
||||
readonly history: MeetHistory;
|
||||
}
|
||||
|
||||
type Ctx = PluginContext<Settings>;
|
||||
@@ -92,6 +95,8 @@ export class FactCollector {
|
||||
#latest: CurrentInstance | undefined;
|
||||
/** Performance ranks by avatar id, so a popular avatar is only fetched once. */
|
||||
readonly #perfCache = new Map<string, AvatarFacts>();
|
||||
/** The latest first_meet / meet_again VRCNext pushed per user; it can arrive before the join line. */
|
||||
readonly #meets = new Map<string, MeetEvent>();
|
||||
|
||||
constructor(ctx: Ctx) {
|
||||
this.#ctx = ctx;
|
||||
@@ -99,6 +104,10 @@ export class FactCollector {
|
||||
const instance = toCurrentInstance(payload);
|
||||
if (instance !== undefined) this.#latest = instance;
|
||||
});
|
||||
ctx.events.on('timelineEvent', (payload) => {
|
||||
const meet = toMeetEvent(payload);
|
||||
if (meet !== undefined) this.#meets.set(meet.userId, meet);
|
||||
});
|
||||
ctx.events.on('vrcAvatarDetail', (payload) => {
|
||||
const perf = toAvatarPerformance(payload);
|
||||
if (perf !== undefined) {
|
||||
@@ -112,12 +121,14 @@ export class FactCollector {
|
||||
}
|
||||
|
||||
/** Runs every lookup in parallel and returns whatever arrived before the deadline. */
|
||||
async collect(joiner: Joiner, groupId: string, deadlineMs: number): Promise<Facts> {
|
||||
async collect(joiner: Joiner, filter: InstanceFilter, deadlineMs: number): Promise<Facts> {
|
||||
const signal = this.#ctx.signal;
|
||||
const [profile, inGroup, avatar] = await Promise.all([
|
||||
const joinedAt = Date.now();
|
||||
const [profile, inGroup, avatar, history] = await Promise.all([
|
||||
this.#profile(joiner, deadlineMs, signal),
|
||||
groupId === '' ? Promise.resolve(undefined) : this.#membership(joiner, groupId, deadlineMs, signal),
|
||||
filter.groupId === '' ? Promise.resolve(undefined) : this.#membership(joiner, filter.groupId, deadlineMs, signal),
|
||||
this.#avatar(joiner, deadlineMs, signal),
|
||||
this.#history(joiner, filter, joinedAt, { ms: deadlineMs, signal }),
|
||||
]);
|
||||
return {
|
||||
ageVerified: profile === undefined ? undefined : profile.ageVerified,
|
||||
@@ -125,9 +136,39 @@ export class FactCollector {
|
||||
platform: profile?.platform ?? '',
|
||||
avatar,
|
||||
inGroup,
|
||||
history,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* VRCNext's own record of earlier meetings: the live meet event for this join, plus the user's
|
||||
* recent timeline. Legacy accounts without a `usr_` id have neither.
|
||||
*/
|
||||
async #history(
|
||||
joiner: Joiner,
|
||||
filter: InstanceFilter,
|
||||
joinedAt: number,
|
||||
window: { readonly ms: number; readonly signal: AbortSignal },
|
||||
): Promise<MeetHistory> {
|
||||
const { ms, signal } = window;
|
||||
if (joiner.userId === '') return summarise(undefined, undefined, filter, joinedAt);
|
||||
const meetPending = this.#meets.get(joiner.userId) !== undefined
|
||||
? Promise.resolve(this.#meets.get(joiner.userId))
|
||||
: waitFor(this.#ctx.events, 'timelineEvent', (payload) => {
|
||||
const meet = toMeetEvent(payload);
|
||||
return meet?.userId === joiner.userId ? meet : undefined;
|
||||
}, { ms: Math.min(ms, 8_000), signal });
|
||||
const timelinePending = waitFor(
|
||||
this.#ctx.events,
|
||||
'timelineForUser',
|
||||
(payload) => toTimelineEntries(payload, joiner.userId),
|
||||
{ ms, signal },
|
||||
);
|
||||
this.#ctx.bridge.send('getTimelineForUser', { userId: joiner.userId });
|
||||
const [meet, entries] = await Promise.all([meetPending, timelinePending]);
|
||||
return summarise(meet, entries, filter, joinedAt);
|
||||
}
|
||||
|
||||
async #profile(joiner: Joiner, ms: number, signal: AbortSignal): Promise<InstanceUser | undefined> {
|
||||
const current = this.#latest === undefined ? undefined : findUser(this.#latest, joiner);
|
||||
if (current !== undefined && profileLoaded(current)) return current;
|
||||
|
||||
Reference in new issue
Block a user