mirror of
https://github.com/YuzuZensai/VRC-Circle.git
synced 2026-09-13 19:08:52 +00:00
✨ feat: Instance grouping and user count
This commit is contained in:
Vendored
+2
@@ -14,6 +14,7 @@ export const policies = {
|
||||
userGroups: { ttl: 15 * 60_000, staleWhileRevalidate: 60 * 60_000 },
|
||||
representedGroup: { ttl: 15 * 60_000, staleWhileRevalidate: 60 * 60_000 },
|
||||
group: { ttl: 30 * 60_000, staleWhileRevalidate: 2 * 60 * 60_000 },
|
||||
instance: { ttl: 30_000, staleWhileRevalidate: 60_000 },
|
||||
} satisfies Record<string, CachePolicy>;
|
||||
|
||||
export const cacheKeys = {
|
||||
@@ -31,4 +32,5 @@ export const cacheKeys = {
|
||||
userGroups: (id: string) => `user:groups:${id}`,
|
||||
representedGroup: (id: string) => `user:group:represented:${id}`,
|
||||
group: (id: string) => `group:${id}`,
|
||||
instance: (location: string) => `instance:${location}`,
|
||||
};
|
||||
|
||||
@@ -5,6 +5,7 @@ import * as auth from "../vrchat/authService";
|
||||
import * as users from "../vrchat/userService";
|
||||
import * as friends from "../vrchat/friendsService";
|
||||
import * as worlds from "../vrchat/worldService";
|
||||
import * as instances from "../vrchat/instanceService";
|
||||
import * as avatars from "../vrchat/avatarService";
|
||||
import * as groups from "../vrchat/groupService";
|
||||
import * as settings from "../vrchat/settingsService";
|
||||
@@ -46,6 +47,9 @@ const handlers = {
|
||||
"world:get": (worldId) => guard(() => worlds.getWorld(worldId)),
|
||||
"world:snapshot": () => guard(async () => worldStore.snapshot()),
|
||||
|
||||
"instance:get": ({ worldId, instanceId }) =>
|
||||
guard(() => instances.getInstance(worldId, instanceId)),
|
||||
|
||||
"avatar:get": (avatarId) => guard(() => avatars.getAvatar(avatarId)),
|
||||
"avatar:favorites": () => guard(() => avatars.getFavoritedAvatars()),
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -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 ?? "",
|
||||
|
||||
Reference in New Issue
Block a user