mirror of
https://github.com/YuzuZensai/VRC-Circle.git
synced 2026-09-13 10:58:59 +00:00
303 lines
9.2 KiB
TypeScript
303 lines
9.2 KiB
TypeScript
import type {
|
|
World as SdkWorld,
|
|
LimitedWorld,
|
|
FavoritedWorld,
|
|
LimitedUserGroups,
|
|
RepresentedGroup,
|
|
Group as SdkGroup,
|
|
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;
|
|
displayName: string;
|
|
bio?: string;
|
|
bioLinks?: string[];
|
|
statusDescription?: string;
|
|
status?: string;
|
|
tags?: string[];
|
|
userIcon?: string;
|
|
profilePicOverride?: string;
|
|
profilePicOverrideThumbnail?: string;
|
|
currentAvatarImageUrl?: string;
|
|
currentAvatarThumbnailImageUrl?: string;
|
|
currentAvatarTags?: string[];
|
|
location?: string;
|
|
lastPlatform?: string;
|
|
last_platform?: string;
|
|
lastLogin?: string;
|
|
last_login?: string | Date | null;
|
|
lastActivity?: string;
|
|
last_activity?: string | Date | null;
|
|
state?: string;
|
|
platform?: string;
|
|
isFriend?: boolean;
|
|
friendKey?: string;
|
|
developerType?: string;
|
|
ageVerificationStatus?: string;
|
|
ageVerified?: boolean;
|
|
pronouns?: string;
|
|
date_joined?: string | Date;
|
|
dateJoined?: string;
|
|
note?: string;
|
|
pastDisplayNames?: { displayName: string; updated_at?: string | Date }[];
|
|
badges?: {
|
|
badgeId: string;
|
|
badgeName: string;
|
|
badgeDescription: string;
|
|
badgeImageUrl: string;
|
|
showcased?: boolean;
|
|
}[];
|
|
}
|
|
|
|
type Mappable = Omit<User | CurrentUser | LimitedUserFriend, "last_login" | "last_activity">;
|
|
const _rawUserCheck = (u: Mappable & { id: string; displayName: string }): RawUser => u;
|
|
void _rawUserCheck;
|
|
|
|
// VRChat's trust tag names lag behind the labels shown in-app.
|
|
export function trustRankFromTags(tags: string[] = []): TrustRank {
|
|
const has = (t: string) => tags.includes(`system_trust_${t}`);
|
|
if (tags.includes("system_troll") || tags.includes("system_probable_troll")) return "troll";
|
|
if (has("legend")) return "veteran";
|
|
if (has("veteran")) return "trusted";
|
|
if (has("trusted")) return "known";
|
|
if (has("known")) return "user";
|
|
if (has("basic")) return "new";
|
|
return "visitor";
|
|
}
|
|
|
|
function languagesFromTags(tags: string[] = []): string[] {
|
|
return tags.filter((t) => t.startsWith("language_")).map((t) => t.slice("language_".length));
|
|
}
|
|
|
|
function normalizeStatus(status?: string): UserStatus {
|
|
switch (status) {
|
|
case "join me":
|
|
case "active":
|
|
case "ask me":
|
|
case "busy":
|
|
case "offline":
|
|
return status;
|
|
default:
|
|
return "offline";
|
|
}
|
|
}
|
|
|
|
export function toIso(v?: string | Date | null): string | undefined {
|
|
if (!v) return undefined;
|
|
const date = v instanceof Date ? v : new Date(v);
|
|
return Number.isNaN(date.getTime()) ? undefined : date.toISOString();
|
|
}
|
|
|
|
export function toUserProfile(raw: RawUser, selfId: string): UserProfile {
|
|
const tags = raw.tags ?? [];
|
|
return {
|
|
id: raw.id,
|
|
displayName: raw.displayName,
|
|
bio: raw.bio ?? "",
|
|
bioLinks: raw.bioLinks ?? [],
|
|
statusDescription: raw.statusDescription ?? "",
|
|
status: normalizeStatus(raw.status),
|
|
trustRank: trustRankFromTags(tags),
|
|
tags,
|
|
|
|
userIcon: raw.userIcon ?? "",
|
|
profilePicOverride: raw.profilePicOverride ?? "",
|
|
profilePicOverrideThumbnail: raw.profilePicOverrideThumbnail ?? "",
|
|
currentAvatarImageUrl: raw.currentAvatarImageUrl ?? "",
|
|
currentAvatarThumbnailImageUrl: raw.currentAvatarThumbnailImageUrl ?? "",
|
|
currentAvatarTags: raw.currentAvatarTags ?? [],
|
|
|
|
location: raw.location,
|
|
lastPlatform: raw.lastPlatform ?? raw.last_platform,
|
|
lastLogin: toIso(raw.lastLogin ?? raw.last_login),
|
|
lastActivity: toIso(raw.lastActivity ?? raw.last_activity),
|
|
state: raw.state as UserProfile["state"],
|
|
platform: raw.platform,
|
|
|
|
isFriend: raw.isFriend ?? false,
|
|
friendKey: raw.friendKey,
|
|
|
|
developerType: raw.developerType,
|
|
ageVerificationStatus: raw.ageVerificationStatus,
|
|
ageVerified: raw.ageVerified,
|
|
pronouns: raw.pronouns,
|
|
languages: languagesFromTags(tags),
|
|
dateJoined: raw.dateJoined ?? toIso(raw.date_joined),
|
|
pastDisplayNames: raw.pastDisplayNames?.map((p) => ({
|
|
displayName: p.displayName,
|
|
updatedAt: toIso(p.updated_at),
|
|
})),
|
|
note: raw.note || undefined,
|
|
badges:
|
|
raw.badges?.map((b) => ({
|
|
id: b.badgeId,
|
|
name: b.badgeName,
|
|
description: b.badgeDescription,
|
|
imageUrl: b.badgeImageUrl,
|
|
showcased: b.showcased ?? false,
|
|
})) ?? [],
|
|
|
|
isSelf: raw.id === selfId,
|
|
};
|
|
}
|
|
|
|
export function toCurrentUserSummary(raw: RawUser): CurrentUserSummary {
|
|
return {
|
|
id: raw.id,
|
|
displayName: raw.displayName,
|
|
userIcon: raw.userIcon ?? "",
|
|
currentAvatarThumbnailImageUrl: raw.currentAvatarThumbnailImageUrl ?? "",
|
|
};
|
|
}
|
|
|
|
type RawWorld = SdkWorld | LimitedWorld | FavoritedWorld;
|
|
|
|
export function toWorld(raw: RawWorld): World {
|
|
const platforms = raw.unityPackages ? platformsOf(raw.unityPackages) : undefined;
|
|
const detailed = "visits" in raw;
|
|
return {
|
|
id: raw.id,
|
|
detailed,
|
|
name: raw.name,
|
|
authorId: raw.authorId ?? "",
|
|
authorName: raw.authorName,
|
|
description: "description" in raw ? (raw.description ?? "") : "",
|
|
imageUrl: raw.imageUrl ?? "",
|
|
thumbnailImageUrl: raw.thumbnailImageUrl ?? "",
|
|
releaseStatus: (raw.releaseStatus as ReleaseStatus) ?? "private",
|
|
capacity: raw.capacity ?? 0,
|
|
favorites: raw.favorites ?? 0,
|
|
visits: "visits" in raw ? (raw.visits ?? 0) : 0,
|
|
occupants: raw.occupants ?? 0,
|
|
heat: raw.heat ?? 0,
|
|
tags: raw.tags ?? [],
|
|
createdAt: toIso(raw.created_at),
|
|
updatedAt: toIso(raw.updated_at),
|
|
|
|
recommendedCapacity: raw.recommendedCapacity,
|
|
popularity: raw.popularity,
|
|
version: "version" in raw ? raw.version : undefined,
|
|
publishedAt: validDate(raw.publicationDate),
|
|
labsPublishedAt: validDate(raw.labsPublicationDate),
|
|
previewYoutubeId: raw.previewYoutubeId ?? undefined,
|
|
platforms,
|
|
publicOccupants: "publicOccupants" in raw ? raw.publicOccupants : undefined,
|
|
privateOccupants: "privateOccupants" in raw ? raw.privateOccupants : undefined,
|
|
};
|
|
}
|
|
|
|
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,
|
|
secureName: raw.secureName ?? undefined,
|
|
shortName: raw.shortName ?? undefined,
|
|
};
|
|
}
|
|
|
|
export function toGroup(raw: LimitedUserGroups | RepresentedGroup): Group {
|
|
return {
|
|
id: raw.groupId ?? "",
|
|
name: raw.name ?? "",
|
|
shortCode: raw.shortCode ?? undefined,
|
|
description: raw.description || undefined,
|
|
iconUrl: raw.iconUrl ?? undefined,
|
|
bannerUrl: raw.bannerUrl ?? undefined,
|
|
ownerId: raw.ownerId ?? undefined,
|
|
memberCount: raw.memberCount,
|
|
privacy: raw.privacy ?? undefined,
|
|
isRepresenting: raw.isRepresenting ?? undefined,
|
|
};
|
|
}
|
|
|
|
export function toGroupDetail(raw: SdkGroup): Group {
|
|
return {
|
|
id: raw.id ?? "",
|
|
detailed: true,
|
|
name: raw.name ?? "",
|
|
shortCode: raw.shortCode ?? undefined,
|
|
description: raw.description || undefined,
|
|
iconUrl: raw.iconUrl ?? undefined,
|
|
bannerUrl: raw.bannerUrl ?? undefined,
|
|
ownerId: raw.ownerId ?? undefined,
|
|
memberCount: raw.memberCount,
|
|
privacy: raw.privacy ?? undefined,
|
|
onlineMemberCount: raw.onlineMemberCount,
|
|
joinState: raw.joinState ?? undefined,
|
|
isVerified: raw.isVerified ?? undefined,
|
|
rules: raw.rules || undefined,
|
|
languages: raw.languages ?? undefined,
|
|
links: raw.links ?? undefined,
|
|
tags: raw.tags ?? undefined,
|
|
createdAt: toIso(raw.createdAt as unknown as string),
|
|
};
|
|
}
|
|
|
|
function platformsOf(pkgs: ReadonlyArray<{ platform: string }>): WorldPlatforms {
|
|
let pc = false;
|
|
let android = false;
|
|
for (const p of pkgs) {
|
|
if (p.platform === "standalonewindows") pc = true;
|
|
else if (p.platform === "android") android = true;
|
|
}
|
|
return { pc, android };
|
|
}
|
|
|
|
function validDate(v?: string): string | undefined {
|
|
if (!v || v === "none") return undefined;
|
|
return toIso(v);
|
|
}
|
|
|
|
interface RawAvatar {
|
|
id: string;
|
|
name: string;
|
|
authorId?: string;
|
|
authorName?: string;
|
|
description?: string;
|
|
imageUrl?: string;
|
|
thumbnailImageUrl?: string;
|
|
releaseStatus?: string;
|
|
tags?: string[];
|
|
favorites?: number;
|
|
created_at?: string | Date;
|
|
updated_at?: string | Date;
|
|
}
|
|
|
|
export function toAvatar(raw: RawAvatar): Avatar {
|
|
return {
|
|
id: raw.id,
|
|
name: raw.name,
|
|
authorId: raw.authorId ?? "",
|
|
authorName: raw.authorName ?? "",
|
|
description: raw.description ?? "",
|
|
imageUrl: raw.imageUrl ?? "",
|
|
thumbnailImageUrl: raw.thumbnailImageUrl ?? "",
|
|
releaseStatus: raw.releaseStatus ?? "private",
|
|
tags: raw.tags ?? [],
|
|
favorites: raw.favorites ?? 0,
|
|
createdAt: toIso(raw.created_at),
|
|
updatedAt: toIso(raw.updated_at),
|
|
};
|
|
}
|