osu-play 1.0.7 → 1.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/README.md +71 -49
- package/dist/cli.cjs +671 -0
- package/dist/cli.cjs.map +22 -0
- package/dist/cli.d.cts +2 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +656 -0
- package/dist/cli.js.map +22 -0
- package/dist/core/cli/main.d.ts +20 -0
- package/dist/core/lazer/compat.d.ts +24 -0
- package/dist/core/lazer/mod.d.ts +10 -0
- package/dist/core/lazer/schema/beatmap.d.ts +35 -0
- package/dist/core/lazer/schema/beatmapMetadata.d.ts +16 -0
- package/dist/core/lazer/schema/beatmapSet.d.ts +18 -0
- package/{src/realm/schema/mod.ts → dist/core/lazer/schema/mod.d.ts} +0 -1
- package/dist/core/lazer/schema/realmFile.d.ts +6 -0
- package/dist/core/lazer/schema/realmNamedFileUsage.d.ts +9 -0
- package/dist/core/lazer/schema/realmUser.d.ts +9 -0
- package/dist/core/playlist/mod.d.ts +28 -0
- package/dist/core/utils/mod.d.ts +16 -0
- package/dist/index.cjs +529 -0
- package/dist/index.cjs.map +21 -0
- package/dist/index.d.cts +4 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +512 -0
- package/dist/index.js.map +21 -0
- package/package.json +54 -24
- package/bin/osu-play.js +0 -7
- package/index.ts +0 -1
- package/src/cli/main.ts +0 -156
- package/src/mod.ts +0 -2
- package/src/realm/mod.ts +0 -54
- package/src/realm/schema/beatmap.ts +0 -85
- package/src/realm/schema/beatmapMetadata.ts +0 -33
- package/src/realm/schema/beatmapSet.ts +0 -36
- package/src/realm/schema/realmFile.ts +0 -15
- package/src/realm/schema/realmNamedFileUsage.ts +0 -19
- package/src/realm/schema/realmUser.ts +0 -21
- package/src/utils/mod.ts +0 -77
- package/tsconfig.json +0 -109
package/dist/cli.cjs
ADDED
|
@@ -0,0 +1,671 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __toESM = (mod, isNodeMode, target) => {
|
|
9
|
+
target = mod != null ? __create(__getProtoOf(mod)) : {};
|
|
10
|
+
const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
|
|
11
|
+
for (let key of __getOwnPropNames(mod))
|
|
12
|
+
if (!__hasOwnProp.call(to, key))
|
|
13
|
+
__defProp(to, key, {
|
|
14
|
+
get: () => mod[key],
|
|
15
|
+
enumerable: true
|
|
16
|
+
});
|
|
17
|
+
return to;
|
|
18
|
+
};
|
|
19
|
+
var __moduleCache = /* @__PURE__ */ new WeakMap;
|
|
20
|
+
var __toCommonJS = (from) => {
|
|
21
|
+
var entry = __moduleCache.get(from), desc;
|
|
22
|
+
if (entry)
|
|
23
|
+
return entry;
|
|
24
|
+
entry = __defProp({}, "__esModule", { value: true });
|
|
25
|
+
if (from && typeof from === "object" || typeof from === "function")
|
|
26
|
+
__getOwnPropNames(from).map((key) => !__hasOwnProp.call(entry, key) && __defProp(entry, key, {
|
|
27
|
+
get: () => from[key],
|
|
28
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
29
|
+
}));
|
|
30
|
+
__moduleCache.set(from, entry);
|
|
31
|
+
return entry;
|
|
32
|
+
};
|
|
33
|
+
var __export = (target, all) => {
|
|
34
|
+
for (var name in all)
|
|
35
|
+
__defProp(target, name, {
|
|
36
|
+
get: all[name],
|
|
37
|
+
enumerable: true,
|
|
38
|
+
configurable: true,
|
|
39
|
+
set: (newValue) => all[name] = () => newValue
|
|
40
|
+
});
|
|
41
|
+
};
|
|
42
|
+
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
43
|
+
|
|
44
|
+
// src/core/utils/mod.ts
|
|
45
|
+
function getDataDir() {
|
|
46
|
+
switch (process.platform) {
|
|
47
|
+
case "linux":
|
|
48
|
+
case "openbsd":
|
|
49
|
+
case "freebsd": {
|
|
50
|
+
const xdg = process.env.XDG_DATA_HOME;
|
|
51
|
+
if (xdg)
|
|
52
|
+
return xdg;
|
|
53
|
+
const home = process.env.HOME;
|
|
54
|
+
if (home)
|
|
55
|
+
return import_node_path.default.join(home, ".local", "share");
|
|
56
|
+
break;
|
|
57
|
+
}
|
|
58
|
+
case "darwin": {
|
|
59
|
+
const home = process.env.HOME;
|
|
60
|
+
if (home)
|
|
61
|
+
return import_node_path.default.join(home, "Library", "Application Support");
|
|
62
|
+
break;
|
|
63
|
+
}
|
|
64
|
+
case "win32":
|
|
65
|
+
return process.env.LOCALAPPDATA ?? undefined;
|
|
66
|
+
}
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
function getConfigDir() {
|
|
70
|
+
switch (process.platform) {
|
|
71
|
+
case "openbsd":
|
|
72
|
+
case "freebsd":
|
|
73
|
+
case "linux": {
|
|
74
|
+
const xdg = process.env.XDG_CONFIG_HOME;
|
|
75
|
+
if (xdg)
|
|
76
|
+
return xdg;
|
|
77
|
+
const home = process.env.HOME;
|
|
78
|
+
if (home)
|
|
79
|
+
return import_node_path.default.join(home, ".config");
|
|
80
|
+
break;
|
|
81
|
+
}
|
|
82
|
+
case "darwin": {
|
|
83
|
+
const home = process.env.HOME;
|
|
84
|
+
if (home)
|
|
85
|
+
return import_node_path.default.join(home, "Library", "Preferences");
|
|
86
|
+
break;
|
|
87
|
+
}
|
|
88
|
+
case "win32":
|
|
89
|
+
return process.env.APPDATA ?? undefined;
|
|
90
|
+
}
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
function getDefaultOsuDataDir() {
|
|
94
|
+
return import_node_path.default.join(getDataDir() ?? ".", "osu");
|
|
95
|
+
}
|
|
96
|
+
function getDefaultConfigPath(appName = "osu-play") {
|
|
97
|
+
return import_node_path.default.join(getConfigDir() ?? ".", appName);
|
|
98
|
+
}
|
|
99
|
+
function hashedFilePath(hash, osuDataDir = getDefaultOsuDataDir()) {
|
|
100
|
+
return import_node_path.default.join(osuDataDir, "files", hash.slice(0, 1), hash.slice(0, 2), hash);
|
|
101
|
+
}
|
|
102
|
+
function getRealmDBPath(legacyAppConfigDirOrOptions = {}, maybeOptions = {}) {
|
|
103
|
+
const options = typeof legacyAppConfigDirOrOptions === "string" ? maybeOptions : legacyAppConfigDirOrOptions;
|
|
104
|
+
const osuDataDir = options.osuDataDir ?? getDefaultOsuDataDir();
|
|
105
|
+
const osuDBPath = import_node_path.default.join(osuDataDir, "client.realm");
|
|
106
|
+
if (!import_node_fs.existsSync(osuDBPath)) {
|
|
107
|
+
console.log(`[getRealmDBPath]: ${osuDBPath} not found`);
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
return osuDBPath;
|
|
111
|
+
}
|
|
112
|
+
var import_node_path, import_node_fs;
|
|
113
|
+
var init_mod = __esm(() => {
|
|
114
|
+
import_node_path = __toESM(require("node:path"));
|
|
115
|
+
import_node_fs = require("node:fs");
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
// src/core/lazer/compat.ts
|
|
119
|
+
function describeRequirement(requirement) {
|
|
120
|
+
if (requirement.type === "object" || requirement.type === "list") {
|
|
121
|
+
return `${requirement.type}:${requirement.objectType ?? "unknown"}`;
|
|
122
|
+
}
|
|
123
|
+
return requirement.type ?? "unknown";
|
|
124
|
+
}
|
|
125
|
+
function describeProperty(property) {
|
|
126
|
+
if (!property?.type) {
|
|
127
|
+
return "missing";
|
|
128
|
+
}
|
|
129
|
+
if (property.type === "object" || property.type === "list") {
|
|
130
|
+
return `${property.type}:${property.objectType ?? "unknown"}`;
|
|
131
|
+
}
|
|
132
|
+
return property.type;
|
|
133
|
+
}
|
|
134
|
+
function inspectLazerSchemaEntries(schemaEntries, version) {
|
|
135
|
+
const schemaByName = new Map(schemaEntries.map((entry) => [entry.name, entry]));
|
|
136
|
+
const missingObjects = [];
|
|
137
|
+
const issues = [];
|
|
138
|
+
for (const [objectName, requirements] of Object.entries(REQUIRED_PLAYLIST_SCHEMA)) {
|
|
139
|
+
const schemaEntry = schemaByName.get(objectName);
|
|
140
|
+
if (!schemaEntry) {
|
|
141
|
+
missingObjects.push(objectName);
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
for (const [propertyName, requirement] of Object.entries(requirements)) {
|
|
145
|
+
const property = schemaEntry.properties[propertyName];
|
|
146
|
+
const typeMatches = !requirement.type || property?.type === requirement.type;
|
|
147
|
+
const objectTypeMatches = !requirement.objectType || property?.objectType === requirement.objectType;
|
|
148
|
+
if (!typeMatches || !objectTypeMatches) {
|
|
149
|
+
issues.push({
|
|
150
|
+
objectName,
|
|
151
|
+
propertyName,
|
|
152
|
+
expected: describeRequirement(requirement),
|
|
153
|
+
actual: describeProperty(property)
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return {
|
|
159
|
+
compatible: missingObjects.length === 0 && issues.length === 0,
|
|
160
|
+
version,
|
|
161
|
+
missingObjects,
|
|
162
|
+
issues
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
function formatLazerSchemaCompatibilityError(report) {
|
|
166
|
+
const details = [
|
|
167
|
+
`Detected osu!lazer schema version ${report.version}, but the playlist reader is missing fields it needs.`
|
|
168
|
+
];
|
|
169
|
+
if (report.missingObjects.length > 0) {
|
|
170
|
+
details.push(`Missing objects: ${report.missingObjects.join(", ")}`);
|
|
171
|
+
}
|
|
172
|
+
if (report.issues.length > 0) {
|
|
173
|
+
details.push(`Mismatched properties: ${report.issues.map((issue) => `${issue.objectName}.${issue.propertyName} expected ${issue.expected}, got ${issue.actual}`).join("; ")}`);
|
|
174
|
+
}
|
|
175
|
+
details.push("This app now reflects the live lazer schema and only depends on a small playlist-safe subset, so version bumps are fine until those required fields change.");
|
|
176
|
+
return details.join(`
|
|
177
|
+
`);
|
|
178
|
+
}
|
|
179
|
+
var LAST_STATIC_LAZER_SCHEMA_VERSION = 46, REQUIRED_PLAYLIST_SCHEMA;
|
|
180
|
+
var init_compat = __esm(() => {
|
|
181
|
+
REQUIRED_PLAYLIST_SCHEMA = {
|
|
182
|
+
BeatmapSet: {
|
|
183
|
+
Beatmaps: { type: "list", objectType: "Beatmap" },
|
|
184
|
+
Files: { type: "list", objectType: "RealmNamedFileUsage" }
|
|
185
|
+
},
|
|
186
|
+
Beatmap: {
|
|
187
|
+
Metadata: { type: "object", objectType: "BeatmapMetadata" }
|
|
188
|
+
},
|
|
189
|
+
BeatmapMetadata: {
|
|
190
|
+
AudioFile: { type: "string" },
|
|
191
|
+
Title: { type: "string" },
|
|
192
|
+
Artist: { type: "string" },
|
|
193
|
+
TitleUnicode: { type: "string" },
|
|
194
|
+
ArtistUnicode: { type: "string" }
|
|
195
|
+
},
|
|
196
|
+
RealmNamedFileUsage: {
|
|
197
|
+
File: { type: "object", objectType: "File" },
|
|
198
|
+
Filename: { type: "string" }
|
|
199
|
+
},
|
|
200
|
+
File: {
|
|
201
|
+
Hash: { type: "string" }
|
|
202
|
+
}
|
|
203
|
+
};
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
// src/core/lazer/schema/beatmap.ts
|
|
207
|
+
var import_realm, Beatmap;
|
|
208
|
+
var init_beatmap = __esm(() => {
|
|
209
|
+
import_realm = __toESM(require("realm"));
|
|
210
|
+
Beatmap = class Beatmap extends import_realm.default.Object {
|
|
211
|
+
ID;
|
|
212
|
+
DifficultyName;
|
|
213
|
+
Metadata;
|
|
214
|
+
BeatmapSet;
|
|
215
|
+
Status;
|
|
216
|
+
OnlineID;
|
|
217
|
+
Length;
|
|
218
|
+
BPM;
|
|
219
|
+
Hash;
|
|
220
|
+
StarRating;
|
|
221
|
+
MD5Hash;
|
|
222
|
+
OnlineMD5Hash;
|
|
223
|
+
LastLocalUpdate;
|
|
224
|
+
LastOnlineUpdate;
|
|
225
|
+
Hidden;
|
|
226
|
+
AudioLeadIn;
|
|
227
|
+
StackLeniency;
|
|
228
|
+
SpecialStyle;
|
|
229
|
+
LetterboxInBreaks;
|
|
230
|
+
WidescreenStoryboard;
|
|
231
|
+
EpilepsyWarning;
|
|
232
|
+
SamplesMatchPlaybackRate;
|
|
233
|
+
LastPlayed;
|
|
234
|
+
DistanceSpacing;
|
|
235
|
+
BeatDivisor;
|
|
236
|
+
GridSize;
|
|
237
|
+
TimelineZoom;
|
|
238
|
+
EditorTimestamp;
|
|
239
|
+
CountdownOffset;
|
|
240
|
+
static schema = {
|
|
241
|
+
name: "Beatmap",
|
|
242
|
+
primaryKey: "ID",
|
|
243
|
+
properties: {
|
|
244
|
+
ID: { type: "uuid", default: "" },
|
|
245
|
+
DifficultyName: { type: "string", default: "", optional: true },
|
|
246
|
+
Metadata: {
|
|
247
|
+
type: "object",
|
|
248
|
+
objectType: "BeatmapMetadata",
|
|
249
|
+
default: null
|
|
250
|
+
},
|
|
251
|
+
BeatmapSet: { type: "object", objectType: "BeatmapSet", default: null },
|
|
252
|
+
Status: { type: "int", default: -3 },
|
|
253
|
+
OnlineID: { type: "int", default: -1 },
|
|
254
|
+
Length: { type: "double", default: 0 },
|
|
255
|
+
BPM: { type: "double", default: 0 },
|
|
256
|
+
Hash: { type: "string", default: "", optional: true },
|
|
257
|
+
StarRating: { type: "double", default: -1 },
|
|
258
|
+
MD5Hash: { type: "string", default: "", optional: true },
|
|
259
|
+
OnlineMD5Hash: { type: "string", default: "", optional: true },
|
|
260
|
+
LastLocalUpdate: { type: "date", optional: true },
|
|
261
|
+
LastOnlineUpdate: { type: "date", optional: true },
|
|
262
|
+
Hidden: { type: "bool", default: false },
|
|
263
|
+
AudioLeadIn: { type: "double", default: 0 },
|
|
264
|
+
StackLeniency: { type: "float", default: 0.7 },
|
|
265
|
+
SpecialStyle: { type: "bool", default: false },
|
|
266
|
+
LetterboxInBreaks: { type: "bool", default: false },
|
|
267
|
+
WidescreenStoryboard: { type: "bool", default: false },
|
|
268
|
+
EpilepsyWarning: { type: "bool", default: false },
|
|
269
|
+
SamplesMatchPlaybackRate: { type: "bool", default: false },
|
|
270
|
+
LastPlayed: { type: "date", optional: true },
|
|
271
|
+
DistanceSpacing: { type: "double", default: 0 },
|
|
272
|
+
BeatDivisor: { type: "int", default: 0 },
|
|
273
|
+
GridSize: { type: "int", default: 0 },
|
|
274
|
+
TimelineZoom: { type: "double", default: 0 },
|
|
275
|
+
EditorTimestamp: { type: "double", optional: true },
|
|
276
|
+
CountdownOffset: { type: "int", default: 0 }
|
|
277
|
+
}
|
|
278
|
+
};
|
|
279
|
+
};
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
// src/core/lazer/schema/beatmapMetadata.ts
|
|
283
|
+
var import_realm2, BeatmapMetadata;
|
|
284
|
+
var init_beatmapMetadata = __esm(() => {
|
|
285
|
+
import_realm2 = __toESM(require("realm"));
|
|
286
|
+
BeatmapMetadata = class BeatmapMetadata extends import_realm2.default.Object {
|
|
287
|
+
Title;
|
|
288
|
+
TitleUnicode;
|
|
289
|
+
Artist;
|
|
290
|
+
ArtistUnicode;
|
|
291
|
+
Author;
|
|
292
|
+
Source;
|
|
293
|
+
Tags;
|
|
294
|
+
PreviewTime;
|
|
295
|
+
AudioFile;
|
|
296
|
+
BackgroundFile;
|
|
297
|
+
static schema = {
|
|
298
|
+
name: "BeatmapMetadata",
|
|
299
|
+
properties: {
|
|
300
|
+
Title: { type: "string", default: "", optional: true },
|
|
301
|
+
TitleUnicode: { type: "string", default: "", optional: true },
|
|
302
|
+
Artist: { type: "string", default: "", optional: true },
|
|
303
|
+
ArtistUnicode: { type: "string", default: "", optional: true },
|
|
304
|
+
Author: { type: "object", objectType: "RealmUser", default: null },
|
|
305
|
+
Source: { type: "string", default: "", optional: true },
|
|
306
|
+
Tags: { type: "string", default: "", optional: true },
|
|
307
|
+
PreviewTime: { type: "int", default: 0 },
|
|
308
|
+
AudioFile: { type: "string", default: "", optional: true },
|
|
309
|
+
BackgroundFile: { type: "string", default: "", optional: true }
|
|
310
|
+
}
|
|
311
|
+
};
|
|
312
|
+
};
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
// src/core/lazer/schema/beatmapSet.ts
|
|
316
|
+
var import_realm3, BeatmapSet;
|
|
317
|
+
var init_beatmapSet = __esm(() => {
|
|
318
|
+
import_realm3 = __toESM(require("realm"));
|
|
319
|
+
BeatmapSet = class BeatmapSet extends import_realm3.default.Object {
|
|
320
|
+
ID;
|
|
321
|
+
OnlineID;
|
|
322
|
+
DateAdded;
|
|
323
|
+
DateSubmitted;
|
|
324
|
+
DateRanked;
|
|
325
|
+
Beatmaps;
|
|
326
|
+
Files;
|
|
327
|
+
Status;
|
|
328
|
+
DeletePending;
|
|
329
|
+
Hash;
|
|
330
|
+
Protected;
|
|
331
|
+
static schema = {
|
|
332
|
+
name: "BeatmapSet",
|
|
333
|
+
primaryKey: "ID",
|
|
334
|
+
properties: {
|
|
335
|
+
ID: { type: "uuid", default: "" },
|
|
336
|
+
OnlineID: { type: "int", default: -1 },
|
|
337
|
+
DateAdded: { type: "date" },
|
|
338
|
+
DateSubmitted: { type: "date", optional: true },
|
|
339
|
+
DateRanked: { type: "date", optional: true },
|
|
340
|
+
Beatmaps: { type: "list", objectType: "Beatmap", default: [] },
|
|
341
|
+
Files: { type: "list", objectType: "RealmNamedFileUsage", default: [] },
|
|
342
|
+
Status: { type: "int", default: -3 },
|
|
343
|
+
DeletePending: { type: "bool", default: false },
|
|
344
|
+
Hash: { type: "string", default: "", optional: true },
|
|
345
|
+
Protected: { type: "bool", default: false }
|
|
346
|
+
}
|
|
347
|
+
};
|
|
348
|
+
};
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
// src/core/lazer/schema/realmFile.ts
|
|
352
|
+
var import_realm4, RealmFile;
|
|
353
|
+
var init_realmFile = __esm(() => {
|
|
354
|
+
import_realm4 = __toESM(require("realm"));
|
|
355
|
+
RealmFile = class RealmFile extends import_realm4.default.Object {
|
|
356
|
+
Hash;
|
|
357
|
+
static schema = {
|
|
358
|
+
name: "File",
|
|
359
|
+
primaryKey: "Hash",
|
|
360
|
+
properties: {
|
|
361
|
+
Hash: { type: "string", default: "", optional: true }
|
|
362
|
+
}
|
|
363
|
+
};
|
|
364
|
+
};
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
// src/core/lazer/schema/realmUser.ts
|
|
368
|
+
var import_realm5, RealmUser;
|
|
369
|
+
var init_realmUser = __esm(() => {
|
|
370
|
+
import_realm5 = __toESM(require("realm"));
|
|
371
|
+
RealmUser = class RealmUser extends import_realm5.default.Object {
|
|
372
|
+
OnlineID;
|
|
373
|
+
Username;
|
|
374
|
+
CountryCode;
|
|
375
|
+
static embedded = true;
|
|
376
|
+
static schema = {
|
|
377
|
+
name: "RealmUser",
|
|
378
|
+
embedded: true,
|
|
379
|
+
properties: {
|
|
380
|
+
OnlineID: { type: "int", default: 1 },
|
|
381
|
+
Username: { type: "string", default: "", optional: true },
|
|
382
|
+
CountryCode: { type: "string", optional: true }
|
|
383
|
+
}
|
|
384
|
+
};
|
|
385
|
+
};
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
// src/core/lazer/schema/realmNamedFileUsage.ts
|
|
389
|
+
var import_realm6, RealmNamedFileUsage;
|
|
390
|
+
var init_realmNamedFileUsage = __esm(() => {
|
|
391
|
+
import_realm6 = __toESM(require("realm"));
|
|
392
|
+
RealmNamedFileUsage = class RealmNamedFileUsage extends import_realm6.default.Object {
|
|
393
|
+
File;
|
|
394
|
+
Filename;
|
|
395
|
+
static embedded = true;
|
|
396
|
+
static schema = {
|
|
397
|
+
name: "RealmNamedFileUsage",
|
|
398
|
+
embedded: true,
|
|
399
|
+
properties: {
|
|
400
|
+
File: "File",
|
|
401
|
+
Filename: { type: "string", optional: true }
|
|
402
|
+
}
|
|
403
|
+
};
|
|
404
|
+
};
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
// src/core/lazer/schema/mod.ts
|
|
408
|
+
var init_mod2 = __esm(() => {
|
|
409
|
+
init_beatmap();
|
|
410
|
+
init_beatmapMetadata();
|
|
411
|
+
init_beatmapSet();
|
|
412
|
+
init_realmFile();
|
|
413
|
+
init_realmUser();
|
|
414
|
+
init_realmNamedFileUsage();
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
// src/core/lazer/mod.ts
|
|
418
|
+
var exports_mod = {};
|
|
419
|
+
__export(exports_mod, {
|
|
420
|
+
inspectLazerSchemaEntries: () => inspectLazerSchemaEntries,
|
|
421
|
+
inspectLazerSchema: () => inspectLazerSchema,
|
|
422
|
+
hashedFilePath: () => hashedFilePath,
|
|
423
|
+
getNamedFileHash: () => getNamedFileHash2,
|
|
424
|
+
getLazerDB: () => getLazerDB,
|
|
425
|
+
getBeatmapSets: () => getBeatmapSets,
|
|
426
|
+
formatLazerSchemaCompatibilityError: () => formatLazerSchemaCompatibilityError,
|
|
427
|
+
RealmUser: () => RealmUser,
|
|
428
|
+
RealmNamedFileUsage: () => RealmNamedFileUsage,
|
|
429
|
+
RealmFile: () => RealmFile,
|
|
430
|
+
LAST_STATIC_LAZER_SCHEMA_VERSION: () => LAST_STATIC_LAZER_SCHEMA_VERSION,
|
|
431
|
+
BeatmapSet: () => BeatmapSet,
|
|
432
|
+
BeatmapMetadata: () => BeatmapMetadata,
|
|
433
|
+
Beatmap: () => Beatmap
|
|
434
|
+
});
|
|
435
|
+
function inspectLazerSchema(realm) {
|
|
436
|
+
return inspectLazerSchemaEntries(realm.schema, import_realm7.default.schemaVersion(realm.path));
|
|
437
|
+
}
|
|
438
|
+
function getBeatmapSets(realm) {
|
|
439
|
+
return realm.objects("BeatmapSet");
|
|
440
|
+
}
|
|
441
|
+
var import_realm7, getLazerDB = async (realmDBPath) => {
|
|
442
|
+
return import_realm7.default.open({
|
|
443
|
+
path: realmDBPath,
|
|
444
|
+
readOnly: true
|
|
445
|
+
});
|
|
446
|
+
}, getNamedFileHash2 = (fileName, beatmapSet) => {
|
|
447
|
+
const files = beatmapSet.Files;
|
|
448
|
+
for (const file of files) {
|
|
449
|
+
if (file.Filename == fileName)
|
|
450
|
+
return file.File.Hash;
|
|
451
|
+
}
|
|
452
|
+
return;
|
|
453
|
+
};
|
|
454
|
+
var init_mod3 = __esm(() => {
|
|
455
|
+
init_compat();
|
|
456
|
+
init_mod2();
|
|
457
|
+
init_compat();
|
|
458
|
+
init_mod();
|
|
459
|
+
import_realm7 = __toESM(require("realm"));
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
// src/core/cli/main.ts
|
|
463
|
+
init_mod();
|
|
464
|
+
var import_node_path2 = __toESM(require("node:path"));
|
|
465
|
+
var import_node_child_process = require("node:child_process");
|
|
466
|
+
var import_node_fs2 = require("node:fs");
|
|
467
|
+
var import_node_util = require("node:util");
|
|
468
|
+
var import_yargs = __toESM(require("yargs/yargs"));
|
|
469
|
+
var import_helpers = require("yargs/helpers");
|
|
470
|
+
|
|
471
|
+
// src/core/playlist/mod.ts
|
|
472
|
+
init_mod();
|
|
473
|
+
function uniqueParts(parts) {
|
|
474
|
+
return [...new Set(parts.map((part) => part?.trim()).filter(Boolean))];
|
|
475
|
+
}
|
|
476
|
+
function formatTrackTitle(metadata) {
|
|
477
|
+
const title = uniqueParts([metadata.Title, metadata.TitleUnicode]).join(" / ");
|
|
478
|
+
const artist = uniqueParts([metadata.Artist, metadata.ArtistUnicode]).join(" / ");
|
|
479
|
+
return `${title || "Unknown Title"} - ${artist || "Unknown Artist"}`;
|
|
480
|
+
}
|
|
481
|
+
function getNamedFileHash(fileName, beatmapSet) {
|
|
482
|
+
for (const file of beatmapSet.Files) {
|
|
483
|
+
if (file.Filename === fileName && file.File?.Hash) {
|
|
484
|
+
return file.File.Hash;
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
return;
|
|
488
|
+
}
|
|
489
|
+
function buildPlaylist(beatmapSets, osuDataDir) {
|
|
490
|
+
const seenHashes = new Set;
|
|
491
|
+
const playlist = [];
|
|
492
|
+
for (const beatmapSet of beatmapSets) {
|
|
493
|
+
for (const beatmap of beatmapSet.Beatmaps) {
|
|
494
|
+
const hash = getNamedFileHash(beatmap.Metadata.AudioFile ?? "", beatmapSet);
|
|
495
|
+
if (!hash || seenHashes.has(hash)) {
|
|
496
|
+
continue;
|
|
497
|
+
}
|
|
498
|
+
seenHashes.add(hash);
|
|
499
|
+
playlist.push({
|
|
500
|
+
hash,
|
|
501
|
+
path: hashedFilePath(hash, osuDataDir),
|
|
502
|
+
title: formatTrackTitle(beatmap.Metadata)
|
|
503
|
+
});
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
return playlist;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
// src/core/cli/main.ts
|
|
510
|
+
var execFilePromise = import_node_util.promisify(import_node_child_process.execFile);
|
|
511
|
+
async function loadRealmDependencies() {
|
|
512
|
+
try {
|
|
513
|
+
const [{ default: Realm8 }, lazerModule] = await Promise.all([
|
|
514
|
+
import("realm"),
|
|
515
|
+
Promise.resolve().then(() => (init_mod3(), exports_mod))
|
|
516
|
+
]);
|
|
517
|
+
return {
|
|
518
|
+
Realm: Realm8,
|
|
519
|
+
...lazerModule
|
|
520
|
+
};
|
|
521
|
+
} catch (error) {
|
|
522
|
+
throw new Error([
|
|
523
|
+
"Realm native bindings could not be loaded.",
|
|
524
|
+
`Current Node runtime: ${process.version}.`,
|
|
525
|
+
"To repair Realm bindings in this project, run `bun run repair:realm` (or `bun run setup` to reinstall dependencies).",
|
|
526
|
+
"If you installed dependencies on Node 22, switch to Node 20 LTS and rerun the repair command.",
|
|
527
|
+
`Original error: ${error instanceof Error ? error.message : String(error)}`
|
|
528
|
+
].join(`
|
|
529
|
+
`), { cause: error });
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
function getArgs() {
|
|
533
|
+
return import_yargs.default(import_helpers.hideBin(process.argv)).scriptName("osu-play").usage(`Play music from your osu!lazer beatmaps from the terminal
|
|
534
|
+
Usage: $0 [options]`).option("reload", {
|
|
535
|
+
type: "boolean",
|
|
536
|
+
default: false,
|
|
537
|
+
alias: "r",
|
|
538
|
+
describe: "Deprecated: ignored. osu-play now reads the live lazer database directly"
|
|
539
|
+
}).option("exportPlaylist", {
|
|
540
|
+
type: "string",
|
|
541
|
+
describe: "Export playlist to a file"
|
|
542
|
+
}).option("osuDataDir", {
|
|
543
|
+
type: "string",
|
|
544
|
+
default: getDefaultOsuDataDir(),
|
|
545
|
+
alias: "d",
|
|
546
|
+
describe: "Osu!lazer data directory"
|
|
547
|
+
}).option("configDir", {
|
|
548
|
+
type: "string",
|
|
549
|
+
alias: "c",
|
|
550
|
+
describe: "Deprecated: ignored. osu-play no longer copies the lazer database"
|
|
551
|
+
}).option("loop", {
|
|
552
|
+
type: "boolean",
|
|
553
|
+
default: false,
|
|
554
|
+
alias: "l",
|
|
555
|
+
describe: "Loop the playlist on end"
|
|
556
|
+
}).alias("help", "h").help().parse();
|
|
557
|
+
}
|
|
558
|
+
async function promptForBeatmap(uniqueBeatmaps) {
|
|
559
|
+
const { default: prompts } = await import("prompts");
|
|
560
|
+
const response = await prompts({
|
|
561
|
+
type: "autocomplete",
|
|
562
|
+
name: "beatmap",
|
|
563
|
+
message: "Which map do you want to play:",
|
|
564
|
+
choices: uniqueBeatmaps.map((beatmap, index) => ({
|
|
565
|
+
title: beatmap.title,
|
|
566
|
+
value: index
|
|
567
|
+
}))
|
|
568
|
+
});
|
|
569
|
+
return response.beatmap;
|
|
570
|
+
}
|
|
571
|
+
async function openBeatmap(filePath) {
|
|
572
|
+
switch (process.platform) {
|
|
573
|
+
case "darwin":
|
|
574
|
+
await execFilePromise("open", [filePath]);
|
|
575
|
+
return;
|
|
576
|
+
case "win32":
|
|
577
|
+
await execFilePromise("cmd", ["/c", "start", "", filePath]);
|
|
578
|
+
return;
|
|
579
|
+
default:
|
|
580
|
+
await execFilePromise("xdg-open", [filePath]);
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
async function main() {
|
|
584
|
+
const argv = await getArgs();
|
|
585
|
+
console.log("[INFO] osu-play");
|
|
586
|
+
if (argv.reload) {
|
|
587
|
+
console.log("[INFO] `--reload` is deprecated and ignored because osu-play reads osu!lazer's live Realm DB directly.");
|
|
588
|
+
}
|
|
589
|
+
if (argv.configDir) {
|
|
590
|
+
console.log("[INFO] `--configDir` is deprecated and ignored because osu-play no longer copies the Realm DB.");
|
|
591
|
+
}
|
|
592
|
+
if (argv.osuDataDir !== getDefaultOsuDataDir()) {
|
|
593
|
+
console.log(`[INFO] Using osu!lazer data directory: ${argv.osuDataDir}`);
|
|
594
|
+
}
|
|
595
|
+
const realmDBPath = getRealmDBPath({
|
|
596
|
+
osuDataDir: argv.osuDataDir
|
|
597
|
+
});
|
|
598
|
+
if (!realmDBPath) {
|
|
599
|
+
throw new Error("Realm DB not found");
|
|
600
|
+
}
|
|
601
|
+
const {
|
|
602
|
+
Realm: Realm8,
|
|
603
|
+
LAST_STATIC_LAZER_SCHEMA_VERSION: LAST_STATIC_LAZER_SCHEMA_VERSION2,
|
|
604
|
+
formatLazerSchemaCompatibilityError: formatLazerSchemaCompatibilityError2,
|
|
605
|
+
getBeatmapSets: getBeatmapSets2,
|
|
606
|
+
getLazerDB: getLazerDB2,
|
|
607
|
+
inspectLazerSchema: inspectLazerSchema2
|
|
608
|
+
} = await loadRealmDependencies();
|
|
609
|
+
Realm8.flags.ALLOW_CLEAR_TEST_STATE = true;
|
|
610
|
+
const realm = await getLazerDB2(realmDBPath);
|
|
611
|
+
try {
|
|
612
|
+
const schemaReport = inspectLazerSchema2(realm);
|
|
613
|
+
console.log(`currentSchema: ${schemaReport.version}`);
|
|
614
|
+
if (!schemaReport.compatible) {
|
|
615
|
+
throw new Error(formatLazerSchemaCompatibilityError2(schemaReport));
|
|
616
|
+
}
|
|
617
|
+
if (schemaReport.version !== LAST_STATIC_LAZER_SCHEMA_VERSION2) {
|
|
618
|
+
console.log(`[INFO] Detected osu!lazer schema version ${schemaReport.version}; continuing with reflected compatibility checks.`);
|
|
619
|
+
}
|
|
620
|
+
const beatmapSets = getBeatmapSets2(realm);
|
|
621
|
+
const uniqueBeatmaps = buildPlaylist(beatmapSets, argv.osuDataDir);
|
|
622
|
+
console.log(`beatmap songs: ${uniqueBeatmaps.length}`);
|
|
623
|
+
if (argv.exportPlaylist) {
|
|
624
|
+
console.log(`[INFO] Exporting playlist to ${argv.exportPlaylist}`);
|
|
625
|
+
const playlist = uniqueBeatmaps.map((beatmap) => beatmap.path).join(`
|
|
626
|
+
`);
|
|
627
|
+
import_node_fs2.writeFileSync(argv.exportPlaylist, playlist);
|
|
628
|
+
console.log(`[INFO] Done. Use something like \`mpv --playlist=${import_node_path2.default.resolve(argv.exportPlaylist)}\` to play the playlist`);
|
|
629
|
+
return;
|
|
630
|
+
}
|
|
631
|
+
const selectedBeatmap = await promptForBeatmap(uniqueBeatmaps);
|
|
632
|
+
if (selectedBeatmap === undefined) {
|
|
633
|
+
console.log("[INFO] Cancelled");
|
|
634
|
+
return;
|
|
635
|
+
}
|
|
636
|
+
console.log(`Selected: ${selectedBeatmap}`);
|
|
637
|
+
for (let i = selectedBeatmap;i < uniqueBeatmaps.length; i += 1) {
|
|
638
|
+
const beatmap = uniqueBeatmaps[i];
|
|
639
|
+
if (!beatmap) {
|
|
640
|
+
continue;
|
|
641
|
+
}
|
|
642
|
+
console.log(`Map: ${beatmap.title}`);
|
|
643
|
+
if (beatmap.path && import_node_fs2.existsSync(beatmap.path)) {
|
|
644
|
+
console.log(`Playing ${beatmap.title}`);
|
|
645
|
+
await openBeatmap(beatmap.path);
|
|
646
|
+
} else {
|
|
647
|
+
console.log(`File does not exist: ${beatmap.path}`);
|
|
648
|
+
}
|
|
649
|
+
if (i < uniqueBeatmaps.length - 1) {
|
|
650
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
651
|
+
} else if (argv.loop) {
|
|
652
|
+
console.log("[INFO] Looping playlist");
|
|
653
|
+
i = -1;
|
|
654
|
+
} else {
|
|
655
|
+
console.log("[INFO] Done. Use --loop to loop the playlist");
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
} finally {
|
|
659
|
+
realm.close();
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
// src/cli.ts
|
|
664
|
+
main().then(() => {
|
|
665
|
+
process.exit(0);
|
|
666
|
+
}).catch((error) => {
|
|
667
|
+
console.error("Error:", error);
|
|
668
|
+
process.exit(1);
|
|
669
|
+
});
|
|
670
|
+
|
|
671
|
+
//# debugId=6FDBB7EDEE4CC0EA64756E2164756E21
|