osu-play 1.0.2 → 1.0.4

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.
@@ -0,0 +1,54 @@
1
+ import Realm from "realm";
2
+ import path from "node:path";
3
+ import { getDataDir } from "../utils/mod.js";
4
+ import {
5
+ Beatmap,
6
+ BeatmapMetadata,
7
+ BeatmapSet,
8
+ RealmFile,
9
+ RealmNamedFileUsage,
10
+ RealmUser,
11
+ } from "./schema/mod.js";
12
+
13
+ export const getLazerDB = async (realmDBPath: string) => {
14
+ const realm = await Realm.open({
15
+ schema: [
16
+ BeatmapSet,
17
+ RealmFile,
18
+ RealmNamedFileUsage,
19
+ Beatmap,
20
+ BeatmapMetadata,
21
+ RealmUser,
22
+ ],
23
+ path: realmDBPath,
24
+ schemaVersion: 36,
25
+ onMigration: (oldRealm, newRealm) => {
26
+ console.log(
27
+ `[onMigration]: Migrating ${oldRealm.path} - ${oldRealm.schemaVersion} -> ${newRealm.path} - ${newRealm.schemaVersion}`,
28
+ );
29
+ },
30
+ });
31
+
32
+ return realm;
33
+ };
34
+
35
+ export const hashedFilePath = (hash: string) => {
36
+ const dataDir = getDataDir();
37
+ if (!dataDir) return null;
38
+ return path.join(
39
+ dataDir,
40
+ "osu",
41
+ "files",
42
+ hash.slice(0, 1),
43
+ hash.slice(0, 2),
44
+ hash,
45
+ );
46
+ };
47
+
48
+ export const getNamedFileHash = (fileName: string, beatmapSet: BeatmapSet) => {
49
+ const files = beatmapSet.Files;
50
+ for (const file of files) {
51
+ if (file.Filename == fileName) return file.File.Hash;
52
+ }
53
+ return undefined;
54
+ };
@@ -0,0 +1,85 @@
1
+ import Realm from "realm";
2
+ import type { ObjectSchema } from "realm";
3
+ import { BeatmapMetadata } from "./beatmapMetadata.js";
4
+
5
+ export class Beatmap extends Realm.Object<Beatmap> {
6
+ ID!: string;
7
+ DifficultyName?: string;
8
+ Ruleset!: any;
9
+ Difficulty!: any;
10
+ Metadata!: BeatmapMetadata;
11
+ UserSettings!: any;
12
+ BeatmapSet!: any;
13
+ Status!: number;
14
+ OnlineID!: number;
15
+ Length!: number;
16
+ BPM!: number;
17
+ Hash?: string;
18
+ StarRating!: number;
19
+ MD5Hash?: string;
20
+ OnlineMD5Hash?: string;
21
+ LastLocalUpdate?: Date;
22
+ LastOnlineUpdate?: Date;
23
+ Hidden!: boolean;
24
+ AudioLeadIn!: number;
25
+ StackLeniency!: number;
26
+ SpecialStyle!: boolean;
27
+ LetterboxInBreaks!: boolean;
28
+ WidescreenStoryboard!: boolean;
29
+ EpilepsyWarning!: boolean;
30
+ SamplesMatchPlaybackRate!: boolean;
31
+ LastPlayed?: Date;
32
+ DistanceSpacing!: number;
33
+ BeatDivisor!: number;
34
+ GridSize!: number;
35
+ TimelineZoom!: number;
36
+ EditorTimestamp?: number;
37
+ CountdownOffset!: number;
38
+
39
+ static schema: ObjectSchema = {
40
+ name: "Beatmap",
41
+ primaryKey: "ID",
42
+ properties: {
43
+ ID: { type: "uuid", default: "" },
44
+ DifficultyName: { type: "string", default: "", optional: true },
45
+ // Ruleset: { type: "object", objectType: "Ruleset", default: null },
46
+ // Difficulty: { type: "object", objectType: "BeatmapDifficulty", default: null },
47
+ Metadata: {
48
+ type: "object",
49
+ objectType: "BeatmapMetadata",
50
+ default: null,
51
+ },
52
+ // UserSettings: { type: "object", objectType: "BeatmapUserSettings", default: null },
53
+ BeatmapSet: { type: "object", objectType: "BeatmapSet", default: null },
54
+ Status: { type: "int", default: -3 },
55
+ OnlineID: { type: "int", default: -1 },
56
+ Length: { type: "double", default: 0 },
57
+ BPM: { type: "double", default: 0 },
58
+ Hash: { type: "string", default: "", optional: true },
59
+ StarRating: { type: "double", default: -1 },
60
+ MD5Hash: { type: "string", default: "", optional: true },
61
+ OnlineMD5Hash: {
62
+ type: "string",
63
+ default: "",
64
+ optional: true,
65
+ },
66
+ LastLocalUpdate: { type: "date", optional: true },
67
+ LastOnlineUpdate: { type: "date", optional: true },
68
+ Hidden: { type: "bool", default: false },
69
+ AudioLeadIn: { type: "double", default: 0 },
70
+ StackLeniency: { type: "float", default: 0.7 },
71
+ SpecialStyle: { type: "bool", default: false },
72
+ LetterboxInBreaks: { type: "bool", default: false },
73
+ WidescreenStoryboard: { type: "bool", default: false },
74
+ EpilepsyWarning: { type: "bool", default: false },
75
+ SamplesMatchPlaybackRate: { type: "bool", default: false },
76
+ LastPlayed: { type: "date", optional: true },
77
+ DistanceSpacing: { type: "double", default: 0 },
78
+ BeatDivisor: { type: "int", default: 0 },
79
+ GridSize: { type: "int", default: 0 },
80
+ TimelineZoom: { type: "double", default: 0 },
81
+ EditorTimestamp: { type: "double", optional: true },
82
+ CountdownOffset: { type: "int", default: 0 },
83
+ },
84
+ };
85
+ }
@@ -0,0 +1,33 @@
1
+ import Realm from "realm";
2
+ import type { ObjectSchema } from "realm";
3
+ import { RealmUser } from "./realmUser.js";
4
+
5
+ export class BeatmapMetadata extends Realm.Object<BeatmapMetadata> {
6
+ Title?: string;
7
+ TitleUnicode?: string;
8
+ Artist?: string;
9
+ ArtistUnicode?: string;
10
+ Author?: RealmUser;
11
+ Source?: string;
12
+ Tags?: string;
13
+ PreviewTime?: number;
14
+ AudioFile?: string;
15
+ BackgroundFile?: string;
16
+
17
+ static schema: ObjectSchema = {
18
+ name: "BeatmapMetadata",
19
+ properties: {
20
+ Title: { type: "string", default: "", optional: true },
21
+ TitleUnicode: { type: "string", default: "", optional: true },
22
+ Artist: { type: "string", default: "", optional: true },
23
+ ArtistUnicode: { type: "string", default: "", optional: true },
24
+ Author: { type: "object", objectType: "RealmUser", default: null },
25
+ Source: { type: "string", default: "", optional: true },
26
+ Tags: { type: "string", default: "", optional: true },
27
+ PreviewTime: { type: "int", default: 0 },
28
+ AudioFile: { type: "string", default: "", optional: true },
29
+ BackgroundFile: { type: "string", default: "", optional: true },
30
+ },
31
+ };
32
+ }
33
+
@@ -0,0 +1,36 @@
1
+ import Realm from "realm";
2
+ import type { ObjectSchema } from "realm";
3
+ import { Beatmap } from "./beatmap.js";
4
+ import { RealmNamedFileUsage } from "./realmNamedFileUsage.js";
5
+
6
+ export class BeatmapSet extends Realm.Object<BeatmapSet> {
7
+ ID!: string;
8
+ OnlineID!: number;
9
+ DateAdded!: Date;
10
+ DateSubmitted?: Date;
11
+ DateRanked?: Date;
12
+ Beatmaps!: Array<Beatmap>;
13
+ Files!: Array<RealmNamedFileUsage>;
14
+ Status!: number;
15
+ DeletePending!: boolean;
16
+ Hash?: string;
17
+ Protected!: boolean;
18
+
19
+ static schema: ObjectSchema = {
20
+ name: "BeatmapSet",
21
+ primaryKey: "ID",
22
+ properties: {
23
+ ID: { type: "uuid", default: "" },
24
+ OnlineID: { type: "int", default: -1 },
25
+ DateAdded: { type: "date" },
26
+ DateSubmitted: { type: "date", optional: true },
27
+ DateRanked: { type: "date", optional: true },
28
+ Beatmaps: { type: "list", objectType: "Beatmap", default: [] },
29
+ Files: { type: "list", objectType: "RealmNamedFileUsage", default: [] },
30
+ Status: { type: "int", default: -3 },
31
+ DeletePending: { type: "bool", default: false },
32
+ Hash: { type: "string", default: "", optional: true },
33
+ Protected: { type: "bool", default: false },
34
+ },
35
+ };
36
+ }
@@ -0,0 +1,7 @@
1
+ export { Beatmap } from "./beatmap.js";
2
+ export { BeatmapMetadata } from "./beatmapMetadata.js";
3
+ export { BeatmapSet } from "./beatmapSet.js";
4
+ export { RealmFile } from "./realmFile.js";
5
+ export { RealmUser } from "./realmUser.js";
6
+ export { RealmNamedFileUsage } from "./realmNamedFileUsage.js";
7
+
@@ -0,0 +1,15 @@
1
+ import Realm from "realm";
2
+ import type { ObjectSchema } from "realm";
3
+
4
+ export class RealmFile extends Realm.Object<RealmFile> {
5
+ Hash?: string;
6
+
7
+ static schema: ObjectSchema = {
8
+ name: "File",
9
+ primaryKey: "Hash",
10
+ properties: {
11
+ Hash: { type: "string", default: "", optional: true },
12
+ },
13
+ };
14
+ }
15
+
@@ -0,0 +1,19 @@
1
+ import Realm from "realm";
2
+ import type { ObjectSchema } from "realm";
3
+ import { RealmFile } from "./realmFile.js";
4
+
5
+ export class RealmNamedFileUsage extends Realm.Object<RealmNamedFileUsage> {
6
+ File!: RealmFile;
7
+ Filename?: string;
8
+
9
+ static embedded = true;
10
+
11
+ static schema: ObjectSchema = {
12
+ name: "RealmNamedFileUsage",
13
+ embedded: true,
14
+ properties: {
15
+ File: "File",
16
+ Filename: { type: "string", optional: true },
17
+ },
18
+ };
19
+ }
@@ -0,0 +1,21 @@
1
+ import Realm from "realm";
2
+ import type { ObjectSchema } from "realm";
3
+
4
+ export class RealmUser extends Realm.Object<RealmUser> {
5
+ OnlineID!: number;
6
+ Username?: string;
7
+ CountryCode?: string;
8
+
9
+ static embedded = true;
10
+
11
+ static schema: ObjectSchema = {
12
+ name: "RealmUser",
13
+ embedded: true,
14
+ properties: {
15
+ OnlineID: { type: "int", default: 1 },
16
+ Username: { type: "string", default: "", optional: true },
17
+ CountryCode: { type: "string", optional: true },
18
+ },
19
+ };
20
+ }
21
+
@@ -0,0 +1,77 @@
1
+ import path from "node:path";
2
+ import { copyFileSync, existsSync, mkdirSync } from "node:fs";
3
+
4
+ export function getDataDir(): string | undefined {
5
+ switch (process.platform) {
6
+ case "linux":
7
+ case "openbsd":
8
+ case "freebsd": {
9
+ const xdg = process.env["XDG_DATA_HOME"];
10
+ if (xdg) return xdg;
11
+ const home = process.env["HOME"];
12
+ if (home) return path.join(home, ".local", "share");
13
+ break;
14
+ }
15
+
16
+ case "darwin": {
17
+ const home = process.env["HOME"];
18
+ if (home) return path.join(home, "Library", "Application Support");
19
+ break;
20
+ }
21
+
22
+ case "win32":
23
+ return process.env["LOCALAPPDATA"] ?? undefined;
24
+ }
25
+
26
+ return undefined;
27
+ }
28
+
29
+ export function getConfigDir(): string | undefined {
30
+ switch (process.platform) {
31
+ case "openbsd":
32
+ case "freebsd":
33
+ case "linux": {
34
+ const xdg = process.env["XDG_CONFIG_HOME"];
35
+ if (xdg) return xdg;
36
+ const home = process.env["HOME"];
37
+ if (home) return path.join(home, ".config");
38
+ break;
39
+ }
40
+
41
+ case "darwin": {
42
+ const home = process.env["HOME"];
43
+ if (home) return path.join(home, "Library", "Preferences");
44
+ break;
45
+ }
46
+
47
+ case "win32":
48
+ return process.env["APPDATA"] ?? undefined;
49
+ }
50
+
51
+ return undefined;
52
+ }
53
+
54
+ /**
55
+ * Get Realm for the app, import to the config if it doesn't exist
56
+ */
57
+ export function getRealmDBPath(
58
+ appConfigDir: string,
59
+ options: {
60
+ osuDataDir: string;
61
+ reload: boolean;
62
+ } = { reload: false, osuDataDir: getDataDir() || "." },
63
+ ) {
64
+ const localDBPath = path.join(appConfigDir, "client.realm");
65
+ if (!options.reload && existsSync(localDBPath)) return localDBPath;
66
+
67
+ const osuDBPath = path.join(options.osuDataDir, "client.realm");
68
+ if (existsSync(osuDBPath)) {
69
+ console.log( `[getRealmDBPath]: ${osuDBPath} is being imported from osu!lazer to ${localDBPath}`);
70
+ mkdirSync(appConfigDir, { recursive: true });
71
+ copyFileSync(osuDBPath, localDBPath );
72
+ return localDBPath;
73
+ } else {
74
+ console.log(`[getRealmDBPath]: ${osuDBPath} not found`);
75
+ return null;
76
+ }
77
+ }