feat: Instance grouping and user count

This commit is contained in:
2026-06-28 03:49:50 +07:00
parent 2dd6931425
commit 387fa18927
11 changed files with 281 additions and 25 deletions
+27 -3
View File
@@ -1,4 +1,5 @@
import { app } from "electron";
import { readFileSync } from "node:fs";
import { KeyvFile } from "keyv-file";
import { VRChat } from "vrchat";
import { activeId, pendingFile, sessionFile } from "../accounts/store";
@@ -59,10 +60,33 @@ export function closeClients(): void {
loginClient?.pipeline.close();
}
interface SessionCookie {
name: string;
value: string;
}
function readAuthCookieFromSession(filename: string): string | null {
try {
const raw = JSON.parse(readFileSync(filename, "utf8")) as {
cache?: [string, { value?: string }][];
};
const entry = raw.cache?.find(([k]) => k === "keyv:cookies")?.[1];
if (!entry?.value) return null;
const outer = JSON.parse(entry.value) as { value?: SessionCookie[] };
const auth = outer.value?.find((c) => c.name === "auth");
return auth?.value ?? null;
} catch {
return null;
}
}
export async function getPipelineAuthToken(vrc: VRChat): Promise<string | null> {
const getCookies = (
vrc as unknown as { getCookies?: () => Promise<{ name: string; value: string }[]> }
).getCookies;
const id = activeId();
if (id) {
const fromDisk = readAuthCookieFromSession(sessionFile(id));
if (fromDisk) return fromDisk;
}
const getCookies = (vrc as unknown as { getCookies?: () => Promise<SessionCookie[]> }).getCookies;
const cookies = (await getCookies?.()) ?? [];
return cookies.find((c) => c.name === "auth")?.value ?? null;
}
+15
View File
@@ -0,0 +1,15 @@
import type { Instance } from "../../shared/types/instance";
import { toInstance } from "./mappers";
import { cachedRead } from "./cachedRead";
import { cacheKeys, policies } from "../cache/policies";
export async function getInstance(worldId: string, instanceId: string): Promise<Instance> {
return cachedRead(
cacheKeys.instance(`${worldId}:${instanceId}`),
policies.instance,
async (vrc) => {
const { data } = await vrc.getInstance({ path: { worldId, instanceId }, throwOnError: true });
return toInstance(data);
},
);
}
+20
View File
@@ -8,12 +8,14 @@ import type {
User,
CurrentUser,
LimitedUserFriend,
Instance as SdkInstance,
} from "vrchat";
import type { TrustRank, UserProfile, UserStatus } from "../../shared/types/user";
import type { CurrentUserSummary } from "../../shared/types/auth";
import type { ReleaseStatus, World, WorldPlatforms } from "../../shared/types/world";
import type { Avatar } from "../../shared/types/avatar";
import type { Group } from "../../shared/types/group";
import type { Instance } from "../../shared/types/instance";
interface RawUser {
id: string;
@@ -194,6 +196,24 @@ export function toWorld(raw: RawWorld): World {
};
}
export function toInstance(raw: SdkInstance): Instance {
return {
id: raw.id,
location: raw.location,
worldId: raw.worldId,
instanceId: raw.instanceId,
type: raw.type,
region: raw.region ?? undefined,
ownerId: raw.ownerId ?? undefined,
userCount: raw.userCount ?? 0,
capacity: raw.capacity ?? raw.recommendedCapacity ?? 0,
recommendedCapacity: raw.recommendedCapacity,
full: raw.full ?? false,
queueEnabled: raw.queueEnabled ?? false,
queueSize: raw.queueSize ?? 0,
};
}
export function toGroup(raw: LimitedUserGroups | RepresentedGroup): Group {
return {
id: raw.groupId ?? "",