@basicbit/vrchat-mcp 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/LICENSE +21 -0
- package/README.md +266 -0
- package/assets/logo.svg +39 -0
- package/dist/bin/cli.js +4 -0
- package/dist/bin/cli.js.map +1 -0
- package/dist/package.json +109 -0
- package/dist/src/auth/cookieStore.js +136 -0
- package/dist/src/auth/cookieStore.js.map +1 -0
- package/dist/src/auth/index.js +398 -0
- package/dist/src/auth/index.js.map +1 -0
- package/dist/src/config/defaults.json +58 -0
- package/dist/src/config/index.js +405 -0
- package/dist/src/config/index.js.map +1 -0
- package/dist/src/core/client.js +262 -0
- package/dist/src/core/client.js.map +1 -0
- package/dist/src/core/generatedToolOverrides.js +104 -0
- package/dist/src/core/generatedToolOverrides.js.map +1 -0
- package/dist/src/core/generatedToolSkips.js +28 -0
- package/dist/src/core/generatedToolSkips.js.map +1 -0
- package/dist/src/core/operationSchemas.js +102 -0
- package/dist/src/core/operationSchemas.js.map +1 -0
- package/dist/src/core/readToolRegistry.js +93 -0
- package/dist/src/core/readToolRegistry.js.map +1 -0
- package/dist/src/core/readTools.js +197 -0
- package/dist/src/core/readTools.js.map +1 -0
- package/dist/src/core/spec.js +137 -0
- package/dist/src/core/spec.js.map +1 -0
- package/dist/src/core/writeToolRegistry.js +113 -0
- package/dist/src/core/writeToolRegistry.js.map +1 -0
- package/dist/src/generated/vrchat-schemas.js +3326 -0
- package/dist/src/generated/vrchat-schemas.js.map +1 -0
- package/dist/src/index.js +31 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/infra/logger.js +26 -0
- package/dist/src/infra/logger.js.map +1 -0
- package/dist/src/models/avatars.js +22 -0
- package/dist/src/models/avatars.js.map +1 -0
- package/dist/src/models/events.js +115 -0
- package/dist/src/models/events.js.map +1 -0
- package/dist/src/models/friends.js +148 -0
- package/dist/src/models/friends.js.map +1 -0
- package/dist/src/models/groups.js +228 -0
- package/dist/src/models/groups.js.map +1 -0
- package/dist/src/models/instances.js +56 -0
- package/dist/src/models/instances.js.map +1 -0
- package/dist/src/models/invites.js +34 -0
- package/dist/src/models/invites.js.map +1 -0
- package/dist/src/models/notifications.js +62 -0
- package/dist/src/models/notifications.js.map +1 -0
- package/dist/src/models/status.js +50 -0
- package/dist/src/models/status.js.map +1 -0
- package/dist/src/models/statusPage.js +84 -0
- package/dist/src/models/statusPage.js.map +1 -0
- package/dist/src/models/users.js +113 -0
- package/dist/src/models/users.js.map +1 -0
- package/dist/src/models/vrcx.js +144 -0
- package/dist/src/models/vrcx.js.map +1 -0
- package/dist/src/models/worlds.js +150 -0
- package/dist/src/models/worlds.js.map +1 -0
- package/dist/src/resources/friendsChanges.js +44 -0
- package/dist/src/resources/friendsChanges.js.map +1 -0
- package/dist/src/resources/friendsSnapshot.js +69 -0
- package/dist/src/resources/friendsSnapshot.js.map +1 -0
- package/dist/src/resources/index.js +9 -0
- package/dist/src/resources/index.js.map +1 -0
- package/dist/src/resources/subscriptions.js +39 -0
- package/dist/src/resources/subscriptions.js.map +1 -0
- package/dist/src/schemas/auth.js +3 -0
- package/dist/src/schemas/auth.js.map +1 -0
- package/dist/src/schemas/call.js +13 -0
- package/dist/src/schemas/call.js.map +1 -0
- package/dist/src/schemas/read.js +58 -0
- package/dist/src/schemas/read.js.map +1 -0
- package/dist/src/schemas/write.js +18 -0
- package/dist/src/schemas/write.js.map +1 -0
- package/dist/src/services/api/client.js +66 -0
- package/dist/src/services/api/client.js.map +1 -0
- package/dist/src/services/api/index.js +2 -0
- package/dist/src/services/api/index.js.map +1 -0
- package/dist/src/services/avatars/index.js +24 -0
- package/dist/src/services/avatars/index.js.map +1 -0
- package/dist/src/services/cache.js +158 -0
- package/dist/src/services/cache.js.map +1 -0
- package/dist/src/services/events/curated.js +229 -0
- package/dist/src/services/events/curated.js.map +1 -0
- package/dist/src/services/events/index.js +3 -0
- package/dist/src/services/events/index.js.map +1 -0
- package/dist/src/services/events/utils.js +75 -0
- package/dist/src/services/events/utils.js.map +1 -0
- package/dist/src/services/friends/changes.js +246 -0
- package/dist/src/services/friends/changes.js.map +1 -0
- package/dist/src/services/friends/curated.js +497 -0
- package/dist/src/services/friends/curated.js.map +1 -0
- package/dist/src/services/friends/fetch.js +69 -0
- package/dist/src/services/friends/fetch.js.map +1 -0
- package/dist/src/services/friends/index.js +5 -0
- package/dist/src/services/friends/index.js.map +1 -0
- package/dist/src/services/friends/match.js +98 -0
- package/dist/src/services/friends/match.js.map +1 -0
- package/dist/src/services/groups/allowlist.js +19 -0
- package/dist/src/services/groups/allowlist.js.map +1 -0
- package/dist/src/services/groups/curated.js +343 -0
- package/dist/src/services/groups/curated.js.map +1 -0
- package/dist/src/services/groups/index.js +3 -0
- package/dist/src/services/groups/index.js.map +1 -0
- package/dist/src/services/instances/curated.js +80 -0
- package/dist/src/services/instances/curated.js.map +1 -0
- package/dist/src/services/instances/index.js +3 -0
- package/dist/src/services/instances/index.js.map +1 -0
- package/dist/src/services/instances/read.js +14 -0
- package/dist/src/services/instances/read.js.map +1 -0
- package/dist/src/services/invites/curated.js +90 -0
- package/dist/src/services/invites/curated.js.map +1 -0
- package/dist/src/services/invites/index.js +2 -0
- package/dist/src/services/invites/index.js.map +1 -0
- package/dist/src/services/notifications/curated.js +54 -0
- package/dist/src/services/notifications/curated.js.map +1 -0
- package/dist/src/services/notifications/index.js +2 -0
- package/dist/src/services/notifications/index.js.map +1 -0
- package/dist/src/services/pipeline/events.js +40 -0
- package/dist/src/services/pipeline/events.js.map +1 -0
- package/dist/src/services/pipeline/handlers.js +21 -0
- package/dist/src/services/pipeline/handlers.js.map +1 -0
- package/dist/src/services/pipeline/index.js +4 -0
- package/dist/src/services/pipeline/index.js.map +1 -0
- package/dist/src/services/pipeline/manager.js +143 -0
- package/dist/src/services/pipeline/manager.js.map +1 -0
- package/dist/src/services/status/curated.js +53 -0
- package/dist/src/services/status/curated.js.map +1 -0
- package/dist/src/services/status/index.js +2 -0
- package/dist/src/services/status/index.js.map +1 -0
- package/dist/src/services/statusPage/curated.js +397 -0
- package/dist/src/services/statusPage/curated.js.map +1 -0
- package/dist/src/services/statusPage/index.js +2 -0
- package/dist/src/services/statusPage/index.js.map +1 -0
- package/dist/src/services/users/curated.js +124 -0
- package/dist/src/services/users/curated.js.map +1 -0
- package/dist/src/services/users/groups.js +54 -0
- package/dist/src/services/users/groups.js.map +1 -0
- package/dist/src/services/users/index.js +3 -0
- package/dist/src/services/users/index.js.map +1 -0
- package/dist/src/services/vrcx/db.js +13 -0
- package/dist/src/services/vrcx/db.js.map +1 -0
- package/dist/src/services/vrcx/gamelog.js +125 -0
- package/dist/src/services/vrcx/gamelog.js.map +1 -0
- package/dist/src/services/vrcx/index.js +5 -0
- package/dist/src/services/vrcx/index.js.map +1 -0
- package/dist/src/services/vrcx/memos.js +51 -0
- package/dist/src/services/vrcx/memos.js.map +1 -0
- package/dist/src/services/vrcx/paths.js +133 -0
- package/dist/src/services/vrcx/paths.js.map +1 -0
- package/dist/src/services/vrcx/relationships.js +138 -0
- package/dist/src/services/vrcx/relationships.js.map +1 -0
- package/dist/src/services/vrcx/shared.js +173 -0
- package/dist/src/services/vrcx/shared.js.map +1 -0
- package/dist/src/services/vrcx/status.js +54 -0
- package/dist/src/services/vrcx/status.js.map +1 -0
- package/dist/src/services/worlds/index.js +256 -0
- package/dist/src/services/worlds/index.js.map +1 -0
- package/dist/src/tools/auth.js +44 -0
- package/dist/src/tools/auth.js.map +1 -0
- package/dist/src/tools/cache.js +61 -0
- package/dist/src/tools/cache.js.map +1 -0
- package/dist/src/tools/curated/avatars.js +57 -0
- package/dist/src/tools/curated/avatars.js.map +1 -0
- package/dist/src/tools/curated/events.js +212 -0
- package/dist/src/tools/curated/events.js.map +1 -0
- package/dist/src/tools/curated/friends.js +168 -0
- package/dist/src/tools/curated/friends.js.map +1 -0
- package/dist/src/tools/curated/groups.js +368 -0
- package/dist/src/tools/curated/groups.js.map +1 -0
- package/dist/src/tools/curated/instances.js +35 -0
- package/dist/src/tools/curated/instances.js.map +1 -0
- package/dist/src/tools/curated/invites.js +78 -0
- package/dist/src/tools/curated/invites.js.map +1 -0
- package/dist/src/tools/curated/notifications.js +35 -0
- package/dist/src/tools/curated/notifications.js.map +1 -0
- package/dist/src/tools/curated/status.js +44 -0
- package/dist/src/tools/curated/status.js.map +1 -0
- package/dist/src/tools/curated/statusPage.js +27 -0
- package/dist/src/tools/curated/statusPage.js.map +1 -0
- package/dist/src/tools/curated/users.js +201 -0
- package/dist/src/tools/curated/users.js.map +1 -0
- package/dist/src/tools/curated/vrcx/gamelog.js +80 -0
- package/dist/src/tools/curated/vrcx/gamelog.js.map +1 -0
- package/dist/src/tools/curated/vrcx/index.js +11 -0
- package/dist/src/tools/curated/vrcx/index.js.map +1 -0
- package/dist/src/tools/curated/vrcx/memos.js +105 -0
- package/dist/src/tools/curated/vrcx/memos.js.map +1 -0
- package/dist/src/tools/curated/vrcx/relationships.js +86 -0
- package/dist/src/tools/curated/vrcx/relationships.js.map +1 -0
- package/dist/src/tools/curated/vrcx/status.js +43 -0
- package/dist/src/tools/curated/vrcx/status.js.map +1 -0
- package/dist/src/tools/curated/worlds.js +162 -0
- package/dist/src/tools/curated/worlds.js.map +1 -0
- package/dist/src/tools/generated.js +19 -0
- package/dist/src/tools/generated.js.map +1 -0
- package/dist/src/tools/raw.js +49 -0
- package/dist/src/tools/raw.js.map +1 -0
- package/dist/src/tools/read/common.js +58 -0
- package/dist/src/tools/read/common.js.map +1 -0
- package/dist/src/tools/read/system.js +19 -0
- package/dist/src/tools/read/system.js.map +1 -0
- package/dist/src/tools/registerAllTools.js +41 -0
- package/dist/src/tools/registerAllTools.js.map +1 -0
- package/dist/src/tools/write/common.js +10 -0
- package/dist/src/tools/write/common.js.map +1 -0
- package/dist/src/utils/json.js +53 -0
- package/dist/src/utils/json.js.map +1 -0
- package/dist/src/utils/schema.js +14 -0
- package/dist/src/utils/schema.js.map +1 -0
- package/dist/src/utils/stableStringify.js +36 -0
- package/dist/src/utils/stableStringify.js.map +1 -0
- package/dist/src/utils/strings.js +7 -0
- package/dist/src/utils/strings.js.map +1 -0
- package/dist/src/utils/toolAnnotations.js +17 -0
- package/dist/src/utils/toolAnnotations.js.map +1 -0
- package/dist/src/utils/toolNames.js +13 -0
- package/dist/src/utils/toolNames.js.map +1 -0
- package/dist/src/utils/toolResponses.js +12 -0
- package/dist/src/utils/toolResponses.js.map +1 -0
- package/docs/architecture.md +46 -0
- package/docs/curated-tools.md +132 -0
- package/docs/design-notes.md +48 -0
- package/docs/evals.md +129 -0
- package/docs/tools-guide.md +27 -0
- package/docs/tools.md +13849 -0
- package/docs/vrcx.md +69 -0
- package/package.json +109 -0
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { parseLocation } from '../friends/match.js';
|
|
2
|
+
import { withVrcxDb } from './db.js';
|
|
3
|
+
import { asInt, asString, computeJoinTime, fetchLocationInfoMap, getActiveUserId, resolveDbOrError, safeIso, } from './shared.js';
|
|
4
|
+
export function listRecentWorldVisits(input) {
|
|
5
|
+
const resolved = resolveDbOrError(input);
|
|
6
|
+
if (!resolved.ok)
|
|
7
|
+
return Promise.resolve(resolved);
|
|
8
|
+
const daysBack = typeof input.daysBack === 'number' ? Math.floor(input.daysBack) : 14;
|
|
9
|
+
const limit = typeof input.limit === 'number' ? Math.floor(input.limit) : 100;
|
|
10
|
+
const from = new Date(Date.now() - Math.max(1, daysBack) * 86400000).toISOString();
|
|
11
|
+
return Promise.resolve(withVrcxDb(resolved.dbPath, (db) => {
|
|
12
|
+
const rows = db
|
|
13
|
+
.prepare(`SELECT id as rowId, created_at as createdAt, location, world_id as worldId, world_name as worldName, time as timeMs, group_name as groupName
|
|
14
|
+
FROM gamelog_location
|
|
15
|
+
WHERE created_at >= ?
|
|
16
|
+
AND location != ''
|
|
17
|
+
AND location != 'traveling'
|
|
18
|
+
ORDER BY id DESC
|
|
19
|
+
LIMIT ?`)
|
|
20
|
+
.all(from, limit);
|
|
21
|
+
const visits = rows
|
|
22
|
+
.map((row) => {
|
|
23
|
+
const location = asString(row.location);
|
|
24
|
+
if (!location)
|
|
25
|
+
return null;
|
|
26
|
+
const createdAt = safeIso(row.createdAt) ?? asString(row.createdAt);
|
|
27
|
+
if (!createdAt)
|
|
28
|
+
return null;
|
|
29
|
+
const parsed = parseLocation(location);
|
|
30
|
+
const timeMs = asInt(row.timeMs);
|
|
31
|
+
return {
|
|
32
|
+
rowId: asInt(row.rowId) ?? 0,
|
|
33
|
+
createdAt,
|
|
34
|
+
location,
|
|
35
|
+
worldId: asString(row.worldId) ?? parsed.worldId,
|
|
36
|
+
worldName: asString(row.worldName) ?? undefined,
|
|
37
|
+
groupName: asString(row.groupName) ?? null,
|
|
38
|
+
groupId: parsed.groupId,
|
|
39
|
+
accessType: parsed.accessType,
|
|
40
|
+
region: parsed.region,
|
|
41
|
+
timeMs: typeof timeMs === 'number' && timeMs >= 0 ? timeMs : undefined,
|
|
42
|
+
};
|
|
43
|
+
})
|
|
44
|
+
.filter((entry) => Boolean(entry));
|
|
45
|
+
return {
|
|
46
|
+
ok: true,
|
|
47
|
+
from,
|
|
48
|
+
limit,
|
|
49
|
+
total: visits.length,
|
|
50
|
+
truncated: visits.length >= limit,
|
|
51
|
+
visits,
|
|
52
|
+
};
|
|
53
|
+
}));
|
|
54
|
+
}
|
|
55
|
+
export function listRecentInstanceSessionsForActiveUser(input) {
|
|
56
|
+
const resolved = resolveDbOrError(input);
|
|
57
|
+
if (!resolved.ok)
|
|
58
|
+
return Promise.resolve(resolved);
|
|
59
|
+
const daysBack = typeof input.daysBack === 'number' ? Math.floor(input.daysBack) : 14;
|
|
60
|
+
const limit = typeof input.limit === 'number' ? Math.floor(input.limit) : 50;
|
|
61
|
+
const from = new Date(Date.now() - Math.max(1, daysBack) * 86400000).toISOString();
|
|
62
|
+
return Promise.resolve(withVrcxDb(resolved.dbPath, (db) => {
|
|
63
|
+
const activeUserId = getActiveUserId(db);
|
|
64
|
+
if (!activeUserId) {
|
|
65
|
+
return {
|
|
66
|
+
ok: false,
|
|
67
|
+
status: 'not_available',
|
|
68
|
+
reason: 'VRCX active userId not found (config:lastuserloggedin missing).',
|
|
69
|
+
paths: resolved.paths,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
const rows = db
|
|
73
|
+
.prepare(`SELECT id as rowId, created_at as createdAt, location, time as durationMs
|
|
74
|
+
FROM gamelog_join_leave
|
|
75
|
+
WHERE type = 'OnPlayerLeft'
|
|
76
|
+
AND user_id = ?
|
|
77
|
+
AND created_at >= ?
|
|
78
|
+
AND location != ''
|
|
79
|
+
AND location != 'traveling'
|
|
80
|
+
ORDER BY id DESC
|
|
81
|
+
LIMIT ?`)
|
|
82
|
+
.all(activeUserId, from, limit);
|
|
83
|
+
const locations = rows
|
|
84
|
+
.map((row) => asString(row.location))
|
|
85
|
+
.filter((v) => Boolean(v));
|
|
86
|
+
const locationInfo = fetchLocationInfoMap(db, locations);
|
|
87
|
+
const sessions = rows
|
|
88
|
+
.map((row) => {
|
|
89
|
+
const location = asString(row.location);
|
|
90
|
+
if (!location)
|
|
91
|
+
return null;
|
|
92
|
+
const leaveTime = safeIso(row.createdAt) ?? asString(row.createdAt);
|
|
93
|
+
if (!leaveTime)
|
|
94
|
+
return null;
|
|
95
|
+
const durationMs = Math.max(0, asInt(row.durationMs) ?? 0);
|
|
96
|
+
const joinTime = computeJoinTime(leaveTime, durationMs);
|
|
97
|
+
const parsed = parseLocation(location);
|
|
98
|
+
const meta = locationInfo.get(location);
|
|
99
|
+
return {
|
|
100
|
+
rowId: asInt(row.rowId) ?? 0,
|
|
101
|
+
location,
|
|
102
|
+
joinTime,
|
|
103
|
+
leaveTime,
|
|
104
|
+
durationMs,
|
|
105
|
+
worldId: meta?.worldId ?? parsed.worldId,
|
|
106
|
+
worldName: meta?.worldName,
|
|
107
|
+
groupName: meta?.groupName ?? null,
|
|
108
|
+
groupId: parsed.groupId,
|
|
109
|
+
accessType: parsed.accessType,
|
|
110
|
+
region: parsed.region,
|
|
111
|
+
};
|
|
112
|
+
})
|
|
113
|
+
.filter((entry) => Boolean(entry));
|
|
114
|
+
return {
|
|
115
|
+
ok: true,
|
|
116
|
+
from,
|
|
117
|
+
limit,
|
|
118
|
+
total: sessions.length,
|
|
119
|
+
truncated: sessions.length >= limit,
|
|
120
|
+
activeUserId,
|
|
121
|
+
sessions,
|
|
122
|
+
};
|
|
123
|
+
}));
|
|
124
|
+
}
|
|
125
|
+
//# sourceMappingURL=gamelog.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gamelog.js","sourceRoot":"","sources":["../../../../src/services/vrcx/gamelog.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/services/vrcx/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,YAAY,CAAC;AAC3B,cAAc,cAAc,CAAC;AAC7B,cAAc,oBAAoB,CAAC"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { withVrcxDb } from './db.js';
|
|
2
|
+
import { asString, resolveDbOrError, safeIso } from './shared.js';
|
|
3
|
+
export function getVrcxUserMemo(input) {
|
|
4
|
+
const resolved = resolveDbOrError(input);
|
|
5
|
+
if (!resolved.ok)
|
|
6
|
+
return Promise.resolve(resolved);
|
|
7
|
+
return Promise.resolve(withVrcxDb(resolved.dbPath, (db) => {
|
|
8
|
+
const row = db
|
|
9
|
+
.prepare('SELECT edited_at as editedAt, memo as memo FROM memos WHERE user_id = ?')
|
|
10
|
+
.get(input.userId);
|
|
11
|
+
return {
|
|
12
|
+
ok: true,
|
|
13
|
+
userId: input.userId,
|
|
14
|
+
editedAt: safeIso(row?.editedAt),
|
|
15
|
+
memo: asString(row?.memo),
|
|
16
|
+
};
|
|
17
|
+
}));
|
|
18
|
+
}
|
|
19
|
+
export function getVrcxWorldMemo(input) {
|
|
20
|
+
const resolved = resolveDbOrError(input);
|
|
21
|
+
if (!resolved.ok)
|
|
22
|
+
return Promise.resolve(resolved);
|
|
23
|
+
return Promise.resolve(withVrcxDb(resolved.dbPath, (db) => {
|
|
24
|
+
const row = db
|
|
25
|
+
.prepare('SELECT edited_at as editedAt, memo as memo FROM world_memos WHERE world_id = ?')
|
|
26
|
+
.get(input.worldId);
|
|
27
|
+
return {
|
|
28
|
+
ok: true,
|
|
29
|
+
worldId: input.worldId,
|
|
30
|
+
editedAt: safeIso(row?.editedAt),
|
|
31
|
+
memo: asString(row?.memo),
|
|
32
|
+
};
|
|
33
|
+
}));
|
|
34
|
+
}
|
|
35
|
+
export function getVrcxAvatarMemo(input) {
|
|
36
|
+
const resolved = resolveDbOrError(input);
|
|
37
|
+
if (!resolved.ok)
|
|
38
|
+
return Promise.resolve(resolved);
|
|
39
|
+
return Promise.resolve(withVrcxDb(resolved.dbPath, (db) => {
|
|
40
|
+
const row = db
|
|
41
|
+
.prepare('SELECT edited_at as editedAt, memo as memo FROM avatar_memos WHERE avatar_id = ?')
|
|
42
|
+
.get(input.avatarId);
|
|
43
|
+
return {
|
|
44
|
+
ok: true,
|
|
45
|
+
avatarId: input.avatarId,
|
|
46
|
+
editedAt: safeIso(row?.editedAt),
|
|
47
|
+
memo: asString(row?.memo),
|
|
48
|
+
};
|
|
49
|
+
}));
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=memos.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memos.js","sourceRoot":"","sources":["../../../../src/services/vrcx/memos.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,QAAQ,EAAE,gBAAgB,EAAE,OAAO,EAAyB,MAAM,aAAa,CAAC;AAEzF,MAAM,UAAU,eAAe,CAAC,KAK/B;IAGC,MAAM,QAAQ,GAAG,gBAAgB,CAAC,KAAK,CAAC,CAAC;IACzC,IAAI,CAAC,QAAQ,CAAC,EAAE;QAAE,OAAO,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IAEnD,OAAO,OAAO,CAAC,OAAO,CACpB,UAAU,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,EAAE;QACjC,MAAM,GAAG,GAAG,EAAE;aACX,OAAO,CAAC,yEAAyE,CAAC;aAClF,GAAG,CAAC,KAAK,CAAC,MAAM,CAAuD,CAAC;QAE3E,OAAO;YACL,EAAE,EAAE,IAAI;YACR,MAAM,EAAE,KAAK,CAAC,MAAM;YACpB,QAAQ,EAAE,OAAO,CAAC,GAAG,EAAE,QAAQ,CAAC;YAChC,IAAI,EAAE,QAAQ,CAAC,GAAG,EAAE,IAAI,CAAC;SAC1B,CAAC;IACJ,CAAC,CAAC,CACH,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,KAKhC;IAGC,MAAM,QAAQ,GAAG,gBAAgB,CAAC,KAAK,CAAC,CAAC;IACzC,IAAI,CAAC,QAAQ,CAAC,EAAE;QAAE,OAAO,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IAEnD,OAAO,OAAO,CAAC,OAAO,CACpB,UAAU,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,EAAE;QACjC,MAAM,GAAG,GAAG,EAAE;aACX,OAAO,CAAC,gFAAgF,CAAC;aACzF,GAAG,CAAC,KAAK,CAAC,OAAO,CAAuD,CAAC;QAE5E,OAAO;YACL,EAAE,EAAE,IAAI;YACR,OAAO,EAAE,KAAK,CAAC,OAAO;YACtB,QAAQ,EAAE,OAAO,CAAC,GAAG,EAAE,QAAQ,CAAC;YAChC,IAAI,EAAE,QAAQ,CAAC,GAAG,EAAE,IAAI,CAAC;SAC1B,CAAC;IACJ,CAAC,CAAC,CACH,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,KAKjC;IAGC,MAAM,QAAQ,GAAG,gBAAgB,CAAC,KAAK,CAAC,CAAC;IACzC,IAAI,CAAC,QAAQ,CAAC,EAAE;QAAE,OAAO,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IAEnD,OAAO,OAAO,CAAC,OAAO,CACpB,UAAU,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,EAAE;QACjC,MAAM,GAAG,GAAG,EAAE;aACX,OAAO,CAAC,kFAAkF,CAAC;aAC3F,GAAG,CAAC,KAAK,CAAC,QAAQ,CAAuD,CAAC;QAE7E,OAAO;YACL,EAAE,EAAE,IAAI;YACR,QAAQ,EAAE,KAAK,CAAC,QAAQ;YACxB,QAAQ,EAAE,OAAO,CAAC,GAAG,EAAE,QAAQ,CAAC;YAChC,IAAI,EAAE,QAAQ,CAAC,GAAG,EAAE,IAAI,CAAC;SAC1B,CAAC;IACJ,CAAC,CAAC,CACH,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { logger } from '../../infra/logger.js';
|
|
5
|
+
function fileExists(filePath) {
|
|
6
|
+
if (!filePath)
|
|
7
|
+
return false;
|
|
8
|
+
try {
|
|
9
|
+
return fs.existsSync(filePath);
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
function toMaybeFilePath(raw) {
|
|
16
|
+
if (typeof raw !== 'string')
|
|
17
|
+
return null;
|
|
18
|
+
const trimmed = raw.trim();
|
|
19
|
+
return trimmed ? trimmed : null;
|
|
20
|
+
}
|
|
21
|
+
function readVrcxJsonDatabaseLocation(vrcxJsonPath) {
|
|
22
|
+
try {
|
|
23
|
+
const content = fs.readFileSync(vrcxJsonPath, 'utf8');
|
|
24
|
+
const parsed = JSON.parse(content);
|
|
25
|
+
if (!parsed || typeof parsed !== 'object')
|
|
26
|
+
return null;
|
|
27
|
+
const record = parsed;
|
|
28
|
+
const loc = toMaybeFilePath(record.VRCX_DatabaseLocation);
|
|
29
|
+
return loc;
|
|
30
|
+
}
|
|
31
|
+
catch (err) {
|
|
32
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
33
|
+
logger.debug('Failed to read VRCX.json', { vrcxJsonPath, message });
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
function normalizeOverridePath(overridePath, baseDir, fileNameForDir) {
|
|
38
|
+
const raw = overridePath.trim();
|
|
39
|
+
if (!raw)
|
|
40
|
+
return raw;
|
|
41
|
+
// If a directory was supplied (common user expectation), try the expected filename inside it.
|
|
42
|
+
try {
|
|
43
|
+
if (fs.existsSync(raw) && fs.statSync(raw).isDirectory()) {
|
|
44
|
+
return path.join(raw, fileNameForDir);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
// ignore
|
|
49
|
+
}
|
|
50
|
+
if (!path.isAbsolute(raw) && baseDir) {
|
|
51
|
+
return path.resolve(baseDir, raw);
|
|
52
|
+
}
|
|
53
|
+
return raw;
|
|
54
|
+
}
|
|
55
|
+
function getDefaultVrcxDirCandidates() {
|
|
56
|
+
const home = os.homedir();
|
|
57
|
+
if (process.platform === 'win32') {
|
|
58
|
+
const appData = toMaybeFilePath(process.env.APPDATA);
|
|
59
|
+
if (appData)
|
|
60
|
+
return [path.join(appData, 'VRCX')];
|
|
61
|
+
return [];
|
|
62
|
+
}
|
|
63
|
+
if (process.platform === 'darwin') {
|
|
64
|
+
return [path.join(home, 'Library', 'Application Support', 'VRCX')];
|
|
65
|
+
}
|
|
66
|
+
// Linux (native Electron builds) typically live under ~/.config
|
|
67
|
+
return [path.join(home, '.config', 'VRCX'), path.join(home, '.local', 'share', 'VRCX')];
|
|
68
|
+
}
|
|
69
|
+
function pickFirstExistingDir(candidates) {
|
|
70
|
+
for (const dir of candidates) {
|
|
71
|
+
try {
|
|
72
|
+
if (fs.existsSync(dir) && fs.statSync(dir).isDirectory())
|
|
73
|
+
return dir;
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
// ignore
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return candidates.length > 0 ? candidates[0] : null;
|
|
80
|
+
}
|
|
81
|
+
export function resolveVrcxPaths(input) {
|
|
82
|
+
const databasePathOverride = toMaybeFilePath(input?.databasePath);
|
|
83
|
+
const worldDbPathOverride = toMaybeFilePath(input?.worldDbPath);
|
|
84
|
+
const defaultDir = pickFirstExistingDir(getDefaultVrcxDirCandidates());
|
|
85
|
+
const vrcxJsonPath = defaultDir ? path.join(defaultDir, 'VRCX.json') : null;
|
|
86
|
+
const vrcxJsonExists = fileExists(vrcxJsonPath);
|
|
87
|
+
let dbPath = null;
|
|
88
|
+
let dbSource = 'unknown';
|
|
89
|
+
if (databasePathOverride) {
|
|
90
|
+
dbPath = normalizeOverridePath(databasePathOverride, defaultDir, 'VRCX.sqlite3');
|
|
91
|
+
dbSource = 'config';
|
|
92
|
+
}
|
|
93
|
+
else if (vrcxJsonPath && vrcxJsonExists) {
|
|
94
|
+
const loc = readVrcxJsonDatabaseLocation(vrcxJsonPath);
|
|
95
|
+
if (loc) {
|
|
96
|
+
dbPath = normalizeOverridePath(loc, defaultDir, 'VRCX.sqlite3');
|
|
97
|
+
dbSource = 'vrcx_json';
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
if (!dbPath && defaultDir) {
|
|
101
|
+
dbPath = path.join(defaultDir, 'VRCX.sqlite3');
|
|
102
|
+
dbSource = 'default';
|
|
103
|
+
}
|
|
104
|
+
const dbExists = fileExists(dbPath);
|
|
105
|
+
let worldDbPath = null;
|
|
106
|
+
let worldDbSource = 'unknown';
|
|
107
|
+
if (worldDbPathOverride) {
|
|
108
|
+
worldDbPath = normalizeOverridePath(worldDbPathOverride, defaultDir, 'VRCX-WorldData.db');
|
|
109
|
+
worldDbSource = 'config';
|
|
110
|
+
}
|
|
111
|
+
else if (defaultDir) {
|
|
112
|
+
worldDbPath = path.join(defaultDir, 'VRCX-WorldData.db');
|
|
113
|
+
worldDbSource = 'default';
|
|
114
|
+
}
|
|
115
|
+
const worldDbExists = fileExists(worldDbPath);
|
|
116
|
+
return {
|
|
117
|
+
db: {
|
|
118
|
+
path: dbPath,
|
|
119
|
+
exists: dbExists,
|
|
120
|
+
source: dbSource,
|
|
121
|
+
},
|
|
122
|
+
worldDb: {
|
|
123
|
+
path: worldDbPath,
|
|
124
|
+
exists: worldDbExists,
|
|
125
|
+
source: worldDbSource,
|
|
126
|
+
},
|
|
127
|
+
vrcxJson: {
|
|
128
|
+
path: vrcxJsonPath,
|
|
129
|
+
exists: vrcxJsonExists,
|
|
130
|
+
},
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
//# sourceMappingURL=paths.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"paths.js","sourceRoot":"","sources":["../../../../src/services/vrcx/paths.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAC;AAgB/C,SAAS,UAAU,CAAC,QAAmC;IACrD,IAAI,CAAC,QAAQ;QAAE,OAAO,KAAK,CAAC;IAC5B,IAAI,CAAC;QACH,OAAO,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC;IACjC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED,SAAS,eAAe,CAAC,GAAY;IACnC,IAAI,OAAO,GAAG,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC;IACzC,MAAM,OAAO,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC;IAC3B,OAAO,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC;AAClC,CAAC;AAED,SAAS,4BAA4B,CAAC,YAAoB;IACxD,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,EAAE,CAAC,YAAY,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC;QACtD,MAAM,MAAM,GAAY,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QAC5C,IAAI,CAAC,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ;YAAE,OAAO,IAAI,CAAC;QACvD,MAAM,MAAM,GAAG,MAAiC,CAAC;QACjD,MAAM,GAAG,GAAG,eAAe,CAAC,MAAM,CAAC,qBAAqB,CAAC,CAAC;QAC1D,OAAO,GAAG,CAAC;IACb,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACjE,MAAM,CAAC,KAAK,CAAC,0BAA0B,EAAE,EAAE,YAAY,EAAE,OAAO,EAAE,CAAC,CAAC;QACpE,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,SAAS,qBAAqB,CAC5B,YAAoB,EACpB,OAAsB,EACtB,cAAsB;IAEtB,MAAM,GAAG,GAAG,YAAY,CAAC,IAAI,EAAE,CAAC;IAChC,IAAI,CAAC,GAAG;QAAE,OAAO,GAAG,CAAC;IAErB,8FAA8F;IAC9F,IAAI,CAAC;QACH,IAAI,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,WAAW,EAAE,EAAE,CAAC;YACzD,OAAO,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,cAAc,CAAC,CAAC;QACxC,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,SAAS;IACX,CAAC;IAED,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,OAAO,EAAE,CAAC;QACrC,OAAO,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;IACpC,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,SAAS,2BAA2B;IAClC,MAAM,IAAI,GAAG,EAAE,CAAC,OAAO,EAAE,CAAC;IAE1B,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO,EAAE,CAAC;QACjC,MAAM,OAAO,GAAG,eAAe,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QACrD,IAAI,OAAO;YAAE,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC;QACjD,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,IAAI,OAAO,CAAC,QAAQ,KAAK,QAAQ,EAAE,CAAC;QAClC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,SAAS,EAAE,qBAAqB,EAAE,MAAM,CAAC,CAAC,CAAC;IACrE,CAAC;IAED,gEAAgE;IAChE,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,SAAS,EAAE,MAAM,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC;AAC1F,CAAC;AAED,SAAS,oBAAoB,CAAC,UAAoB;IAChD,KAAK,MAAM,GAAG,IAAI,UAAU,EAAE,CAAC;QAC7B,IAAI,CAAC;YACH,IAAI,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,WAAW,EAAE;gBAAE,OAAO,GAAG,CAAC;QACvE,CAAC;QAAC,MAAM,CAAC;YACP,SAAS;QACX,CAAC;IACH,CAAC;IACD,OAAO,UAAU,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AACtD,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,KAGhC;IACC,MAAM,oBAAoB,GAAG,eAAe,CAAC,KAAK,EAAE,YAAY,CAAC,CAAC;IAClE,MAAM,mBAAmB,GAAG,eAAe,CAAC,KAAK,EAAE,WAAW,CAAC,CAAC;IAEhE,MAAM,UAAU,GAAG,oBAAoB,CAAC,2BAA2B,EAAE,CAAC,CAAC;IACvE,MAAM,YAAY,GAAG,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,WAAW,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IAC5E,MAAM,cAAc,GAAG,UAAU,CAAC,YAAY,CAAC,CAAC;IAEhD,IAAI,MAAM,GAAkB,IAAI,CAAC;IACjC,IAAI,QAAQ,GAAmB,SAAS,CAAC;IAEzC,IAAI,oBAAoB,EAAE,CAAC;QACzB,MAAM,GAAG,qBAAqB,CAAC,oBAAoB,EAAE,UAAU,EAAE,cAAc,CAAC,CAAC;QACjF,QAAQ,GAAG,QAAQ,CAAC;IACtB,CAAC;SAAM,IAAI,YAAY,IAAI,cAAc,EAAE,CAAC;QAC1C,MAAM,GAAG,GAAG,4BAA4B,CAAC,YAAY,CAAC,CAAC;QACvD,IAAI,GAAG,EAAE,CAAC;YACR,MAAM,GAAG,qBAAqB,CAAC,GAAG,EAAE,UAAU,EAAE,cAAc,CAAC,CAAC;YAChE,QAAQ,GAAG,WAAW,CAAC;QACzB,CAAC;IACH,CAAC;IAED,IAAI,CAAC,MAAM,IAAI,UAAU,EAAE,CAAC;QAC1B,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,cAAc,CAAC,CAAC;QAC/C,QAAQ,GAAG,SAAS,CAAC;IACvB,CAAC;IAED,MAAM,QAAQ,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC;IAEpC,IAAI,WAAW,GAAkB,IAAI,CAAC;IACtC,IAAI,aAAa,GAAmB,SAAS,CAAC;IAC9C,IAAI,mBAAmB,EAAE,CAAC;QACxB,WAAW,GAAG,qBAAqB,CAAC,mBAAmB,EAAE,UAAU,EAAE,mBAAmB,CAAC,CAAC;QAC1F,aAAa,GAAG,QAAQ,CAAC;IAC3B,CAAC;SAAM,IAAI,UAAU,EAAE,CAAC;QACtB,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,mBAAmB,CAAC,CAAC;QACzD,aAAa,GAAG,SAAS,CAAC;IAC5B,CAAC;IAED,MAAM,aAAa,GAAG,UAAU,CAAC,WAAW,CAAC,CAAC;IAE9C,OAAO;QACL,EAAE,EAAE;YACF,IAAI,EAAE,MAAM;YACZ,MAAM,EAAE,QAAQ;YAChB,MAAM,EAAE,QAAQ;SACjB;QACD,OAAO,EAAE;YACP,IAAI,EAAE,WAAW;YACjB,MAAM,EAAE,aAAa;YACrB,MAAM,EAAE,aAAa;SACtB;QACD,QAAQ,EAAE;YACR,IAAI,EAAE,YAAY;YAClB,MAAM,EAAE,cAAc;SACvB;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { parseLocation } from '../friends/match.js';
|
|
2
|
+
import { withVrcxDb } from './db.js';
|
|
3
|
+
import { asInt, asString, computeJoinTime, fetchLocationInfoMap, resolveDbOrError, resolveTarget, safeIso, } from './shared.js';
|
|
4
|
+
export function getUserRelationshipSummary(input) {
|
|
5
|
+
const resolved = resolveDbOrError(input);
|
|
6
|
+
if (!resolved.ok)
|
|
7
|
+
return Promise.resolve(resolved);
|
|
8
|
+
return Promise.resolve(withVrcxDb(resolved.dbPath, (db) => {
|
|
9
|
+
const target = resolveTarget(db, { userId: input.userId, displayName: input.displayName });
|
|
10
|
+
if (target.where.kind === 'none') {
|
|
11
|
+
return {
|
|
12
|
+
ok: false,
|
|
13
|
+
status: 'invalid_input',
|
|
14
|
+
reason: 'Provide userId or displayName.',
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
const queryWhere = target.where.kind === 'userId'
|
|
18
|
+
? { sql: 'user_id = ?', value: target.where.value }
|
|
19
|
+
: { sql: 'display_name = ?', value: target.where.value };
|
|
20
|
+
const statsRow = db
|
|
21
|
+
.prepare(`SELECT
|
|
22
|
+
MAX(created_at) as lastSeen,
|
|
23
|
+
SUM(CASE WHEN type = 'OnPlayerLeft' THEN COALESCE(time, 0) ELSE 0 END) as timeSpentMs,
|
|
24
|
+
COUNT(DISTINCT location) as joinCount
|
|
25
|
+
FROM gamelog_join_leave
|
|
26
|
+
WHERE ${queryWhere.sql}
|
|
27
|
+
AND location != ''
|
|
28
|
+
AND location != 'traveling'`)
|
|
29
|
+
.get(queryWhere.value);
|
|
30
|
+
let lastDisplayName = null;
|
|
31
|
+
if (target.resolvedUserId) {
|
|
32
|
+
try {
|
|
33
|
+
const nameRow = db
|
|
34
|
+
.prepare('SELECT display_name as displayName FROM gamelog_join_leave WHERE user_id = ? ORDER BY id DESC LIMIT 1')
|
|
35
|
+
.get(target.resolvedUserId);
|
|
36
|
+
lastDisplayName = asString(nameRow?.displayName);
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
// ignore
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
const lastSeen = safeIso(statsRow?.lastSeen);
|
|
43
|
+
const timeSpentMs = Math.max(0, asInt(statsRow?.timeSpentMs) ?? 0);
|
|
44
|
+
const joinCount = Math.max(0, asInt(statsRow?.joinCount) ?? 0);
|
|
45
|
+
const timeSpentHours = timeSpentMs / 1000 / 60 / 60;
|
|
46
|
+
const hasData = Boolean(lastSeen) || timeSpentMs > 0 || joinCount > 0;
|
|
47
|
+
return {
|
|
48
|
+
ok: true,
|
|
49
|
+
query: {
|
|
50
|
+
userId: asString(input.userId) ?? undefined,
|
|
51
|
+
displayName: asString(input.displayName) ?? undefined,
|
|
52
|
+
},
|
|
53
|
+
resolvedBy: target.resolvedBy,
|
|
54
|
+
resolvedUserId: target.resolvedUserId,
|
|
55
|
+
displayName: lastDisplayName ?? target.displayName,
|
|
56
|
+
lastSeen,
|
|
57
|
+
joinCount,
|
|
58
|
+
timeSpentMs,
|
|
59
|
+
timeSpentHours,
|
|
60
|
+
hasData,
|
|
61
|
+
};
|
|
62
|
+
}));
|
|
63
|
+
}
|
|
64
|
+
export function listUserRelationshipSessions(input) {
|
|
65
|
+
const resolved = resolveDbOrError(input);
|
|
66
|
+
if (!resolved.ok)
|
|
67
|
+
return Promise.resolve(resolved);
|
|
68
|
+
const limit = typeof input.limit === 'number' ? Math.floor(input.limit) : 50;
|
|
69
|
+
return Promise.resolve(withVrcxDb(resolved.dbPath, (db) => {
|
|
70
|
+
const target = resolveTarget(db, { userId: input.userId, displayName: input.displayName });
|
|
71
|
+
if (target.where.kind === 'none') {
|
|
72
|
+
return {
|
|
73
|
+
ok: false,
|
|
74
|
+
status: 'invalid_input',
|
|
75
|
+
reason: 'Provide userId or displayName.',
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
const queryWhere = target.where.kind === 'userId'
|
|
79
|
+
? { sql: 'user_id = ?', value: target.where.value }
|
|
80
|
+
: { sql: 'display_name = ?', value: target.where.value };
|
|
81
|
+
const rows = db
|
|
82
|
+
.prepare(`SELECT id as rowId, created_at as createdAt, display_name as displayName, location, time as durationMs
|
|
83
|
+
FROM gamelog_join_leave
|
|
84
|
+
WHERE type = 'OnPlayerLeft'
|
|
85
|
+
AND ${queryWhere.sql}
|
|
86
|
+
AND location != ''
|
|
87
|
+
AND location != 'traveling'
|
|
88
|
+
ORDER BY id DESC
|
|
89
|
+
LIMIT ?`)
|
|
90
|
+
.all(queryWhere.value, limit);
|
|
91
|
+
const locations = rows
|
|
92
|
+
.map((row) => asString(row.location))
|
|
93
|
+
.filter((v) => Boolean(v));
|
|
94
|
+
const locationInfo = fetchLocationInfoMap(db, locations);
|
|
95
|
+
const sessions = rows
|
|
96
|
+
.map((row) => {
|
|
97
|
+
const location = asString(row.location);
|
|
98
|
+
if (!location)
|
|
99
|
+
return null;
|
|
100
|
+
const leaveTime = safeIso(row.createdAt) ?? asString(row.createdAt);
|
|
101
|
+
if (!leaveTime)
|
|
102
|
+
return null;
|
|
103
|
+
const durationMs = Math.max(0, asInt(row.durationMs) ?? 0);
|
|
104
|
+
const joinTime = computeJoinTime(leaveTime, durationMs);
|
|
105
|
+
const parsed = parseLocation(location);
|
|
106
|
+
const meta = locationInfo.get(location);
|
|
107
|
+
return {
|
|
108
|
+
rowId: asInt(row.rowId) ?? 0,
|
|
109
|
+
location,
|
|
110
|
+
joinTime,
|
|
111
|
+
leaveTime,
|
|
112
|
+
durationMs,
|
|
113
|
+
worldId: meta?.worldId ?? parsed.worldId,
|
|
114
|
+
worldName: meta?.worldName,
|
|
115
|
+
groupName: meta?.groupName ?? null,
|
|
116
|
+
groupId: parsed.groupId,
|
|
117
|
+
accessType: parsed.accessType,
|
|
118
|
+
region: parsed.region,
|
|
119
|
+
displayName: asString(row.displayName) ?? undefined,
|
|
120
|
+
};
|
|
121
|
+
})
|
|
122
|
+
.filter((entry) => Boolean(entry));
|
|
123
|
+
return {
|
|
124
|
+
ok: true,
|
|
125
|
+
query: {
|
|
126
|
+
userId: asString(input.userId) ?? undefined,
|
|
127
|
+
displayName: asString(input.displayName) ?? undefined,
|
|
128
|
+
},
|
|
129
|
+
resolvedBy: target.resolvedBy,
|
|
130
|
+
resolvedUserId: target.resolvedUserId,
|
|
131
|
+
total: sessions.length,
|
|
132
|
+
limit,
|
|
133
|
+
truncated: sessions.length >= limit,
|
|
134
|
+
sessions,
|
|
135
|
+
};
|
|
136
|
+
}));
|
|
137
|
+
}
|
|
138
|
+
//# sourceMappingURL=relationships.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"relationships.js","sourceRoot":"","sources":["../../../../src/services/vrcx/relationships.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { logger } from '../../infra/logger.js';
|
|
2
|
+
import { resolveVrcxPaths } from './paths.js';
|
|
3
|
+
export function disabledError() {
|
|
4
|
+
return { ok: false, status: 'disabled', reason: 'VRCX integration is disabled.' };
|
|
5
|
+
}
|
|
6
|
+
export function notAvailableError(paths) {
|
|
7
|
+
const hint = paths.db.path
|
|
8
|
+
? `VRCX database not found at "${paths.db.path}".`
|
|
9
|
+
: 'VRCX database path could not be resolved.';
|
|
10
|
+
return {
|
|
11
|
+
ok: false,
|
|
12
|
+
status: 'not_available',
|
|
13
|
+
reason: hint,
|
|
14
|
+
paths,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
export function asString(value) {
|
|
18
|
+
if (typeof value !== 'string')
|
|
19
|
+
return null;
|
|
20
|
+
const trimmed = value.trim();
|
|
21
|
+
return trimmed ? trimmed : null;
|
|
22
|
+
}
|
|
23
|
+
export function asInt(value) {
|
|
24
|
+
if (typeof value === 'number' && Number.isFinite(value))
|
|
25
|
+
return Math.trunc(value);
|
|
26
|
+
if (typeof value !== 'string')
|
|
27
|
+
return null;
|
|
28
|
+
const trimmed = value.trim();
|
|
29
|
+
if (!trimmed)
|
|
30
|
+
return null;
|
|
31
|
+
const parsed = Number(trimmed);
|
|
32
|
+
if (!Number.isFinite(parsed))
|
|
33
|
+
return null;
|
|
34
|
+
return Math.trunc(parsed);
|
|
35
|
+
}
|
|
36
|
+
export function safeIso(value) {
|
|
37
|
+
const raw = asString(value);
|
|
38
|
+
if (!raw)
|
|
39
|
+
return null;
|
|
40
|
+
const ms = Date.parse(raw);
|
|
41
|
+
if (!Number.isFinite(ms))
|
|
42
|
+
return raw;
|
|
43
|
+
return new Date(ms).toISOString();
|
|
44
|
+
}
|
|
45
|
+
export function computeJoinTime(leaveTimeIso, durationMs) {
|
|
46
|
+
if (!leaveTimeIso)
|
|
47
|
+
return null;
|
|
48
|
+
const leaveMs = Date.parse(leaveTimeIso);
|
|
49
|
+
if (!Number.isFinite(leaveMs))
|
|
50
|
+
return null;
|
|
51
|
+
const joinMs = leaveMs - Math.max(0, durationMs);
|
|
52
|
+
if (!Number.isFinite(joinMs) || joinMs <= 0)
|
|
53
|
+
return null;
|
|
54
|
+
return new Date(joinMs).toISOString();
|
|
55
|
+
}
|
|
56
|
+
export function toUserPrefix(userId) {
|
|
57
|
+
const raw = userId.replace(/[-_]/g, '');
|
|
58
|
+
if (!raw)
|
|
59
|
+
return null;
|
|
60
|
+
const prefixed = /^\d/.test(raw) ? `_${raw}` : raw;
|
|
61
|
+
if (!/^[A-Za-z0-9_]+$/.test(prefixed))
|
|
62
|
+
return null;
|
|
63
|
+
return prefixed;
|
|
64
|
+
}
|
|
65
|
+
function readConfigValue(db, key) {
|
|
66
|
+
try {
|
|
67
|
+
const row = db.prepare('SELECT value FROM configs WHERE key = ?').get(key);
|
|
68
|
+
return asString(row?.value);
|
|
69
|
+
}
|
|
70
|
+
catch (err) {
|
|
71
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
72
|
+
logger.debug('VRCX configs query failed', { message, key });
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
export function getActiveUserId(db) {
|
|
77
|
+
// Stored by VRCX ConfigRepository as config:lastuserloggedin
|
|
78
|
+
return readConfigValue(db, 'config:lastuserloggedin');
|
|
79
|
+
}
|
|
80
|
+
export function getDatabaseVersion(db) {
|
|
81
|
+
// Stored by VRCX under config:vrcx_databaseversion
|
|
82
|
+
return asInt(readConfigValue(db, 'config:vrcx_databaseversion'));
|
|
83
|
+
}
|
|
84
|
+
export function resolveDbOrError(input) {
|
|
85
|
+
if (!input.enabled)
|
|
86
|
+
return disabledError();
|
|
87
|
+
const paths = resolveVrcxPaths({
|
|
88
|
+
databasePath: input.databasePath,
|
|
89
|
+
worldDbPath: input.worldDbPath,
|
|
90
|
+
});
|
|
91
|
+
if (!paths.db.exists || !paths.db.path)
|
|
92
|
+
return notAvailableError(paths);
|
|
93
|
+
return { ok: true, paths, dbPath: paths.db.path };
|
|
94
|
+
}
|
|
95
|
+
export function fetchLocationInfoMap(db, locations) {
|
|
96
|
+
const map = new Map();
|
|
97
|
+
const unique = Array.from(new Set(locations.filter(Boolean)));
|
|
98
|
+
if (unique.length === 0)
|
|
99
|
+
return map;
|
|
100
|
+
try {
|
|
101
|
+
const placeholders = unique.map(() => '?').join(',');
|
|
102
|
+
const rows = db
|
|
103
|
+
.prepare(`SELECT gl.location as location, gl.world_id as worldId, gl.world_name as worldName, gl.group_name as groupName
|
|
104
|
+
FROM gamelog_location gl
|
|
105
|
+
JOIN (
|
|
106
|
+
SELECT location, MAX(created_at) as max_created_at
|
|
107
|
+
FROM gamelog_location
|
|
108
|
+
WHERE location IN (${placeholders})
|
|
109
|
+
GROUP BY location
|
|
110
|
+
) latest
|
|
111
|
+
ON latest.location = gl.location AND latest.max_created_at = gl.created_at`)
|
|
112
|
+
.all(...unique);
|
|
113
|
+
for (const row of rows) {
|
|
114
|
+
const location = asString(row.location);
|
|
115
|
+
if (!location)
|
|
116
|
+
continue;
|
|
117
|
+
map.set(location, {
|
|
118
|
+
worldId: asString(row.worldId) ?? undefined,
|
|
119
|
+
worldName: asString(row.worldName) ?? undefined,
|
|
120
|
+
groupName: asString(row.groupName) ?? null,
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
catch (err) {
|
|
125
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
126
|
+
logger.debug('VRCX gamelog_location lookup failed', { message });
|
|
127
|
+
}
|
|
128
|
+
return map;
|
|
129
|
+
}
|
|
130
|
+
export function resolveTarget(db, input) {
|
|
131
|
+
const userId = asString(input.userId);
|
|
132
|
+
const displayName = asString(input.displayName);
|
|
133
|
+
if (userId) {
|
|
134
|
+
return {
|
|
135
|
+
resolvedBy: 'userId',
|
|
136
|
+
resolvedUserId: userId,
|
|
137
|
+
displayName: null,
|
|
138
|
+
where: { kind: 'userId', value: userId },
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
if (displayName) {
|
|
142
|
+
try {
|
|
143
|
+
const row = db
|
|
144
|
+
.prepare("SELECT user_id as userId FROM gamelog_join_leave WHERE display_name = ? AND user_id != '' ORDER BY id DESC LIMIT 1")
|
|
145
|
+
.get(displayName);
|
|
146
|
+
const resolvedUserId = asString(row?.userId);
|
|
147
|
+
if (resolvedUserId) {
|
|
148
|
+
return {
|
|
149
|
+
resolvedBy: 'displayName',
|
|
150
|
+
resolvedUserId,
|
|
151
|
+
displayName,
|
|
152
|
+
where: { kind: 'userId', value: resolvedUserId },
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
catch {
|
|
157
|
+
// ignore
|
|
158
|
+
}
|
|
159
|
+
return {
|
|
160
|
+
resolvedBy: 'displayName',
|
|
161
|
+
resolvedUserId: null,
|
|
162
|
+
displayName,
|
|
163
|
+
where: { kind: 'displayName', value: displayName },
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
return {
|
|
167
|
+
resolvedBy: 'none',
|
|
168
|
+
resolvedUserId: null,
|
|
169
|
+
displayName: null,
|
|
170
|
+
where: { kind: 'none' },
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
//# sourceMappingURL=shared.js.map
|