@rocksky/sdk 0.12.0 → 0.14.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 +27 -2
- package/dist/client.d.ts +33 -9
- package/dist/client.d.ts.map +1 -1
- package/dist/dedup.js +215 -0
- package/dist/errors.d.ts +5 -2
- package/dist/errors.d.ts.map +1 -1
- package/dist/filter.d.ts +122 -0
- package/dist/filter.d.ts.map +1 -0
- package/dist/filter.test.d.ts +2 -0
- package/dist/filter.test.d.ts.map +1 -0
- package/dist/generated/types.d.ts +20 -0
- package/dist/generated/types.d.ts.map +1 -1
- package/dist/hash.d.ts.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +204 -213
- package/dist/remote.d.ts.map +1 -1
- package/package.json +8 -2
- package/src/agent.ts +3 -3
- package/src/client.ts +91 -10
- package/src/errors.ts +7 -2
- package/src/filter.test.ts +94 -0
- package/src/filter.ts +192 -0
- package/src/generated/types.ts +21 -0
- package/src/hash.ts +4 -2
- package/src/index.ts +11 -1
- package/src/library.ts +2 -2
- package/src/remote.ts +4 -5
package/dist/index.js
CHANGED
|
@@ -19,11 +19,13 @@ var RockskyError;
|
|
|
19
19
|
var init_errors = __esm(() => {
|
|
20
20
|
RockskyError = class RockskyError extends Error {
|
|
21
21
|
kind;
|
|
22
|
-
|
|
22
|
+
status;
|
|
23
|
+
constructor(payload, status) {
|
|
23
24
|
const p = payload;
|
|
24
25
|
super(p?.message || p?.error || "rocksky request failed");
|
|
25
26
|
this.name = "RockskyError";
|
|
26
27
|
this.kind = p?.error;
|
|
28
|
+
this.status = status;
|
|
27
29
|
}
|
|
28
30
|
};
|
|
29
31
|
});
|
|
@@ -42,7 +44,7 @@ class RockskyLibrary {
|
|
|
42
44
|
}
|
|
43
45
|
const res = await this.rpc.get(nsid, { params: clean });
|
|
44
46
|
if (!res.ok)
|
|
45
|
-
throw new RockskyError(res.data);
|
|
47
|
+
throw new RockskyError(res.data, res.status);
|
|
46
48
|
return res.data;
|
|
47
49
|
}
|
|
48
50
|
async procedure(nsid, input) {
|
|
@@ -53,7 +55,7 @@ class RockskyLibrary {
|
|
|
53
55
|
}
|
|
54
56
|
const res = await this.rpc.post(nsid, { input: clean });
|
|
55
57
|
if (!res.ok)
|
|
56
|
-
throw new RockskyError(res.data);
|
|
58
|
+
throw new RockskyError(res.data, res.status);
|
|
57
59
|
return res.data;
|
|
58
60
|
}
|
|
59
61
|
ping() {
|
|
@@ -230,7 +232,7 @@ class RockskyClient {
|
|
|
230
232
|
}
|
|
231
233
|
const res = await this.rpc.get(nsid, { params: clean });
|
|
232
234
|
if (!res.ok)
|
|
233
|
-
throw new RockskyError(res.data);
|
|
235
|
+
throw new RockskyError(res.data, res.status);
|
|
234
236
|
return res.data;
|
|
235
237
|
}
|
|
236
238
|
profile(actor) {
|
|
@@ -247,6 +249,20 @@ class RockskyClient {
|
|
|
247
249
|
get(nsid, params = {}) {
|
|
248
250
|
return this.query(nsid, params);
|
|
249
251
|
}
|
|
252
|
+
async post(nsid, opts = {}) {
|
|
253
|
+
const clean = {};
|
|
254
|
+
for (const [k, v] of Object.entries(opts.params ?? {})) {
|
|
255
|
+
if (v !== undefined && v !== "")
|
|
256
|
+
clean[k] = v;
|
|
257
|
+
}
|
|
258
|
+
const call = { params: clean };
|
|
259
|
+
if (opts.body !== undefined)
|
|
260
|
+
call.input = opts.body;
|
|
261
|
+
const res = await this.rpc.post(nsid, call);
|
|
262
|
+
if (!res.ok)
|
|
263
|
+
throw new RockskyError(res.data, res.status);
|
|
264
|
+
return res.data;
|
|
265
|
+
}
|
|
250
266
|
async songs(actor, limit = 50, offset = 0) {
|
|
251
267
|
const out = await this.query("app.rocksky.actor.getActorSongs", {
|
|
252
268
|
did: actor,
|
|
@@ -297,27 +313,30 @@ class RockskyClient {
|
|
|
297
313
|
});
|
|
298
314
|
return out.artists ?? [];
|
|
299
315
|
}
|
|
300
|
-
async catalogAlbums(limit = 50, offset = 0, genre) {
|
|
316
|
+
async catalogAlbums(limit = 50, offset = 0, genre, filter) {
|
|
301
317
|
const out = await this.query("app.rocksky.album.getAlbums", {
|
|
302
318
|
limit,
|
|
303
319
|
offset,
|
|
304
|
-
genre
|
|
320
|
+
genre,
|
|
321
|
+
filter: filter?.toString()
|
|
305
322
|
});
|
|
306
323
|
return out.albums ?? [];
|
|
307
324
|
}
|
|
308
|
-
async catalogArtists(limit = 50, offset = 0, genre) {
|
|
325
|
+
async catalogArtists(limit = 50, offset = 0, genre, filter) {
|
|
309
326
|
const out = await this.query("app.rocksky.artist.getArtists", {
|
|
310
327
|
limit,
|
|
311
328
|
offset,
|
|
312
|
-
genre
|
|
329
|
+
genre,
|
|
330
|
+
filter: filter?.toString()
|
|
313
331
|
});
|
|
314
332
|
return out.artists ?? [];
|
|
315
333
|
}
|
|
316
|
-
async catalogSongs(limit = 50, offset = 0, genre) {
|
|
334
|
+
async catalogSongs(limit = 50, offset = 0, genre, filter) {
|
|
317
335
|
const out = await this.query("app.rocksky.song.getSongs", {
|
|
318
336
|
limit,
|
|
319
337
|
offset,
|
|
320
|
-
genre
|
|
338
|
+
genre,
|
|
339
|
+
filter: filter?.toString()
|
|
321
340
|
});
|
|
322
341
|
return out.tracks ?? [];
|
|
323
342
|
}
|
|
@@ -337,12 +356,13 @@ class RockskyClient {
|
|
|
337
356
|
});
|
|
338
357
|
return out.tracks ?? [];
|
|
339
358
|
}
|
|
340
|
-
async scrobbleFeed(did, following = false, limit = 50, offset = 0) {
|
|
359
|
+
async scrobbleFeed(did, following = false, limit = 50, offset = 0, filter) {
|
|
341
360
|
const out = await this.query("app.rocksky.scrobble.getScrobbles", {
|
|
342
361
|
did,
|
|
343
362
|
following,
|
|
344
363
|
limit,
|
|
345
|
-
offset
|
|
364
|
+
offset,
|
|
365
|
+
filter: filter?.toString()
|
|
346
366
|
});
|
|
347
367
|
return out.scrobbles ?? [];
|
|
348
368
|
}
|
|
@@ -485,9 +505,24 @@ class RockskyClient {
|
|
|
485
505
|
input: ids && ids.length ? { ids } : {}
|
|
486
506
|
});
|
|
487
507
|
if (!res.ok)
|
|
488
|
-
throw new RockskyError(res.data);
|
|
508
|
+
throw new RockskyError(res.data, res.status);
|
|
489
509
|
return res.data;
|
|
490
510
|
}
|
|
511
|
+
followAccount(account) {
|
|
512
|
+
return this.post("app.rocksky.graph.followAccount", { params: { account } });
|
|
513
|
+
}
|
|
514
|
+
unfollowAccount(account) {
|
|
515
|
+
return this.post("app.rocksky.graph.unfollowAccount", { params: { account } });
|
|
516
|
+
}
|
|
517
|
+
createScrobble(input) {
|
|
518
|
+
return this.post("app.rocksky.scrobble.createScrobble", { body: input });
|
|
519
|
+
}
|
|
520
|
+
putMirrorSource(input) {
|
|
521
|
+
return this.post("app.rocksky.mirror.putMirrorSource", { body: input });
|
|
522
|
+
}
|
|
523
|
+
putAudioSettings(input) {
|
|
524
|
+
return this.post("app.rocksky.rockbox.putAudioSettings", { body: input });
|
|
525
|
+
}
|
|
491
526
|
}
|
|
492
527
|
var Interval, DEFAULT_APPVIEW = "https://api.rocksky.app";
|
|
493
528
|
var init_client = __esm(() => {
|
|
@@ -741,7 +776,7 @@ class Agent {
|
|
|
741
776
|
input: { repo: this.did, collection, record: { ...record, $type: collection } }
|
|
742
777
|
});
|
|
743
778
|
if (!res.ok)
|
|
744
|
-
throw new RockskyError(res.data);
|
|
779
|
+
throw new RockskyError(res.data, res.status);
|
|
745
780
|
return res.data.uri;
|
|
746
781
|
}
|
|
747
782
|
async putRecord(collection, rkey, record) {
|
|
@@ -750,7 +785,7 @@ class Agent {
|
|
|
750
785
|
input: { repo: this.did, collection, rkey, record: { ...record, $type: collection } }
|
|
751
786
|
});
|
|
752
787
|
if (!res.ok)
|
|
753
|
-
throw new RockskyError(res.data);
|
|
788
|
+
throw new RockskyError(res.data, res.status);
|
|
754
789
|
return res.data.uri;
|
|
755
790
|
}
|
|
756
791
|
async delete(collection, rkey) {
|
|
@@ -759,7 +794,7 @@ class Agent {
|
|
|
759
794
|
input: { repo: this.did, collection, rkey }
|
|
760
795
|
});
|
|
761
796
|
if (!res.ok)
|
|
762
|
-
throw new RockskyError(res.data);
|
|
797
|
+
throw new RockskyError(res.data, res.status);
|
|
763
798
|
}
|
|
764
799
|
async scrobble(rec) {
|
|
765
800
|
const record = { ...rec, createdAt: rec.createdAt || nowISO() };
|
|
@@ -900,200 +935,6 @@ class Agent {
|
|
|
900
935
|
return this.delete(C_STATUS, "self");
|
|
901
936
|
}
|
|
902
937
|
}
|
|
903
|
-
// src/dedup.ts
|
|
904
|
-
import { fromUint8Array } from "@atcute/car";
|
|
905
|
-
import { decode as cborDecode, fromBytes, isBytes, isCidLink } from "@atcute/cbor";
|
|
906
|
-
import { toString as cidToString } from "@atcute/cid";
|
|
907
|
-
import { ClassicLevel } from "classic-level";
|
|
908
|
-
|
|
909
|
-
// src/hash.ts
|
|
910
|
-
import { createHash } from "node:crypto";
|
|
911
|
-
function sha256Lower(s) {
|
|
912
|
-
return createHash("sha256").update(s.toLowerCase()).digest("hex");
|
|
913
|
-
}
|
|
914
|
-
function songHash(title, artist, album) {
|
|
915
|
-
return sha256Lower(`${title} - ${artist} - ${album}`);
|
|
916
|
-
}
|
|
917
|
-
function albumHash(album, albumArtist) {
|
|
918
|
-
return sha256Lower(`${album} - ${albumArtist}`);
|
|
919
|
-
}
|
|
920
|
-
function artistHash(albumArtist) {
|
|
921
|
-
return sha256Lower(albumArtist);
|
|
922
|
-
}
|
|
923
|
-
|
|
924
|
-
// src/dedup.ts
|
|
925
|
-
var C_ARTIST2 = "app.rocksky.artist";
|
|
926
|
-
var C_ALBUM2 = "app.rocksky.album";
|
|
927
|
-
var C_SONG2 = "app.rocksky.song";
|
|
928
|
-
var C_SCROBBLE2 = "app.rocksky.scrobble";
|
|
929
|
-
var SEP = "\x00";
|
|
930
|
-
function totalIndexed(s) {
|
|
931
|
-
return s.artists + s.albums + s.songs + s.scrobbles;
|
|
932
|
-
}
|
|
933
|
-
function identKey(did, col, hash) {
|
|
934
|
-
return `${did}${SEP}${col}${SEP}${hash}`;
|
|
935
|
-
}
|
|
936
|
-
function scrobbleKey(did, songH, secs) {
|
|
937
|
-
return `${did}${SEP}${C_SCROBBLE2}${SEP}${songH}${SEP}${secs}`;
|
|
938
|
-
}
|
|
939
|
-
function primaryFor(did, col, rec) {
|
|
940
|
-
const s = (k) => typeof rec[k] === "string" ? rec[k] : "";
|
|
941
|
-
switch (col) {
|
|
942
|
-
case C_ARTIST2:
|
|
943
|
-
return s("name") ? identKey(did, C_ARTIST2, artistHash(s("name"))) : undefined;
|
|
944
|
-
case C_ALBUM2:
|
|
945
|
-
return s("title") && s("artist") ? identKey(did, C_ALBUM2, albumHash(s("title"), s("artist"))) : undefined;
|
|
946
|
-
case C_SONG2:
|
|
947
|
-
return s("title") && s("artist") && s("album") ? identKey(did, C_SONG2, songHash(s("title"), s("artist"), s("album"))) : undefined;
|
|
948
|
-
case C_SCROBBLE2: {
|
|
949
|
-
if (s("title") && s("artist") && s("album") && s("createdAt")) {
|
|
950
|
-
const secs = Math.floor(Date.parse(s("createdAt")) / 1000);
|
|
951
|
-
if (!Number.isNaN(secs))
|
|
952
|
-
return scrobbleKey(did, songHash(s("title"), s("artist"), s("album")), secs);
|
|
953
|
-
}
|
|
954
|
-
return;
|
|
955
|
-
}
|
|
956
|
-
}
|
|
957
|
-
return;
|
|
958
|
-
}
|
|
959
|
-
|
|
960
|
-
class RockskyIndex {
|
|
961
|
-
db;
|
|
962
|
-
constructor(path) {
|
|
963
|
-
this.db = new ClassicLevel(path, { keyEncoding: "utf8", valueEncoding: "utf8" });
|
|
964
|
-
}
|
|
965
|
-
open() {
|
|
966
|
-
return this.db.open();
|
|
967
|
-
}
|
|
968
|
-
close() {
|
|
969
|
-
return this.db.close();
|
|
970
|
-
}
|
|
971
|
-
async get(key) {
|
|
972
|
-
try {
|
|
973
|
-
return await this.db.get(key);
|
|
974
|
-
} catch {
|
|
975
|
-
return;
|
|
976
|
-
}
|
|
977
|
-
}
|
|
978
|
-
songUri(did, title, artist, album) {
|
|
979
|
-
return this.get(identKey(did, C_SONG2, songHash(title, artist, album)));
|
|
980
|
-
}
|
|
981
|
-
albumUri(did, album, albumArtist) {
|
|
982
|
-
return this.get(identKey(did, C_ALBUM2, albumHash(album, albumArtist)));
|
|
983
|
-
}
|
|
984
|
-
artistUri(did, albumArtist) {
|
|
985
|
-
return this.get(identKey(did, C_ARTIST2, artistHash(albumArtist)));
|
|
986
|
-
}
|
|
987
|
-
scrobbleUri(did, title, artist, album, secs) {
|
|
988
|
-
return this.get(scrobbleKey(did, songHash(title, artist, album), secs));
|
|
989
|
-
}
|
|
990
|
-
async putPrimary(did, col, primary, uri) {
|
|
991
|
-
const rkey = uri.slice(uri.lastIndexOf("/") + 1);
|
|
992
|
-
await this.db.batch([
|
|
993
|
-
{ type: "put", key: primary, value: uri },
|
|
994
|
-
{ type: "put", key: `${SEP}rk${SEP}${did}${SEP}${col}${SEP}${rkey}`, value: primary }
|
|
995
|
-
]);
|
|
996
|
-
}
|
|
997
|
-
recordSong(did, title, artist, album, uri) {
|
|
998
|
-
return this.putPrimary(did, C_SONG2, identKey(did, C_SONG2, songHash(title, artist, album)), uri);
|
|
999
|
-
}
|
|
1000
|
-
recordAlbum(did, album, albumArtist, uri) {
|
|
1001
|
-
return this.putPrimary(did, C_ALBUM2, identKey(did, C_ALBUM2, albumHash(album, albumArtist)), uri);
|
|
1002
|
-
}
|
|
1003
|
-
recordArtist(did, albumArtist, uri) {
|
|
1004
|
-
return this.putPrimary(did, C_ARTIST2, identKey(did, C_ARTIST2, artistHash(albumArtist)), uri);
|
|
1005
|
-
}
|
|
1006
|
-
recordScrobble(did, title, artist, album, secs, uri) {
|
|
1007
|
-
return this.putPrimary(did, C_SCROBBLE2, scrobbleKey(did, songHash(title, artist, album), secs), uri);
|
|
1008
|
-
}
|
|
1009
|
-
cursor(did) {
|
|
1010
|
-
return this.get(`${SEP}meta${SEP}cursor${SEP}${did}`).then((v) => v ? Number(v) : 0);
|
|
1011
|
-
}
|
|
1012
|
-
async setCursor(did, timeUS) {
|
|
1013
|
-
await this.db.put(`${SEP}meta${SEP}cursor${SEP}${did}`, String(timeUS));
|
|
1014
|
-
}
|
|
1015
|
-
async indexCar(did, car) {
|
|
1016
|
-
const reader = fromUint8Array(car);
|
|
1017
|
-
const blocks = new Map;
|
|
1018
|
-
for (const entry of reader)
|
|
1019
|
-
blocks.set(cidToString(entry.cid), entry.bytes);
|
|
1020
|
-
const root = reader.roots[0];
|
|
1021
|
-
if (!root)
|
|
1022
|
-
throw new Error("CAR has no root");
|
|
1023
|
-
const commit = cborDecode(blocks.get(root.$link));
|
|
1024
|
-
const stats = { artists: 0, albums: 0, songs: 0, scrobbles: 0 };
|
|
1025
|
-
const ops = [];
|
|
1026
|
-
for (const [path, valueLink] of walkMst(blocks, commit.data.$link)) {
|
|
1027
|
-
const slash = path.indexOf("/");
|
|
1028
|
-
if (slash < 0)
|
|
1029
|
-
continue;
|
|
1030
|
-
const col = path.slice(0, slash);
|
|
1031
|
-
const rkey = path.slice(slash + 1);
|
|
1032
|
-
if (col !== C_ARTIST2 && col !== C_ALBUM2 && col !== C_SONG2 && col !== C_SCROBBLE2)
|
|
1033
|
-
continue;
|
|
1034
|
-
const recBytes = blocks.get(valueLink);
|
|
1035
|
-
if (!recBytes)
|
|
1036
|
-
continue;
|
|
1037
|
-
const rec = cborDecode(recBytes);
|
|
1038
|
-
const primary = primaryFor(did, col, rec);
|
|
1039
|
-
if (!primary)
|
|
1040
|
-
continue;
|
|
1041
|
-
const uri = `at://${did}/${col}/${rkey}`;
|
|
1042
|
-
ops.push({ type: "put", key: primary, value: uri });
|
|
1043
|
-
ops.push({ type: "put", key: `${SEP}rk${SEP}${did}${SEP}${col}${SEP}${rkey}`, value: primary });
|
|
1044
|
-
if (col === C_ARTIST2)
|
|
1045
|
-
stats.artists++;
|
|
1046
|
-
else if (col === C_ALBUM2)
|
|
1047
|
-
stats.albums++;
|
|
1048
|
-
else if (col === C_SONG2)
|
|
1049
|
-
stats.songs++;
|
|
1050
|
-
else
|
|
1051
|
-
stats.scrobbles++;
|
|
1052
|
-
}
|
|
1053
|
-
if (commit.rev)
|
|
1054
|
-
ops.push({ type: "put", key: `${SEP}meta${SEP}rev${SEP}${did}`, value: commit.rev });
|
|
1055
|
-
await this.db.batch(ops);
|
|
1056
|
-
return stats;
|
|
1057
|
-
}
|
|
1058
|
-
async applyCommit(did, col, operation, rkey, record) {
|
|
1059
|
-
if (operation === "create" || operation === "update") {
|
|
1060
|
-
if (!record)
|
|
1061
|
-
return;
|
|
1062
|
-
const primary = primaryFor(did, col, record);
|
|
1063
|
-
if (!primary)
|
|
1064
|
-
return;
|
|
1065
|
-
await this.putPrimary(did, col, primary, `at://${did}/${col}/${rkey}`);
|
|
1066
|
-
} else if (operation === "delete") {
|
|
1067
|
-
const rk = `${SEP}rk${SEP}${did}${SEP}${col}${SEP}${rkey}`;
|
|
1068
|
-
const primary = await this.get(rk);
|
|
1069
|
-
if (primary) {
|
|
1070
|
-
await this.db.batch([
|
|
1071
|
-
{ type: "del", key: primary },
|
|
1072
|
-
{ type: "del", key: rk }
|
|
1073
|
-
]);
|
|
1074
|
-
}
|
|
1075
|
-
}
|
|
1076
|
-
}
|
|
1077
|
-
}
|
|
1078
|
-
function* walkMst(blocks, cid) {
|
|
1079
|
-
const raw = blocks.get(cid);
|
|
1080
|
-
if (!raw)
|
|
1081
|
-
return;
|
|
1082
|
-
const node = cborDecode(raw);
|
|
1083
|
-
if (node.l && isCidLink(node.l))
|
|
1084
|
-
yield* walkMst(blocks, node.l.$link);
|
|
1085
|
-
let last = new Uint8Array(0);
|
|
1086
|
-
for (const e of node.e) {
|
|
1087
|
-
const suffix = isBytes(e.k) ? fromBytes(e.k) : e.k;
|
|
1088
|
-
const key = new Uint8Array(e.p + suffix.length);
|
|
1089
|
-
key.set(last.subarray(0, e.p), 0);
|
|
1090
|
-
key.set(suffix, e.p);
|
|
1091
|
-
yield [new TextDecoder().decode(key), e.v.$link];
|
|
1092
|
-
last = key;
|
|
1093
|
-
if (e.t && isCidLink(e.t))
|
|
1094
|
-
yield* walkMst(blocks, e.t.$link);
|
|
1095
|
-
}
|
|
1096
|
-
}
|
|
1097
938
|
// src/remote-player.ts
|
|
1098
939
|
var DEFAULT_REMOTE_WS = "wss://api.rocksky.app/ws";
|
|
1099
940
|
|
|
@@ -1606,26 +1447,176 @@ function deviceFromJson(d) {
|
|
|
1606
1447
|
queue: (d.queue?.queue ?? []).map(queueItemFromJson)
|
|
1607
1448
|
};
|
|
1608
1449
|
}
|
|
1450
|
+
// src/filter.ts
|
|
1451
|
+
var SAFE_VALUE = /^[A-Za-z0-9_.:@*+-]+$/;
|
|
1452
|
+
var renderValue = (value) => {
|
|
1453
|
+
if (typeof value === "number" || typeof value === "boolean")
|
|
1454
|
+
return String(value);
|
|
1455
|
+
if (value.length > 0 && SAFE_VALUE.test(value))
|
|
1456
|
+
return value;
|
|
1457
|
+
return `"${value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"")}"`;
|
|
1458
|
+
};
|
|
1459
|
+
|
|
1460
|
+
class Filter {
|
|
1461
|
+
expr;
|
|
1462
|
+
kind;
|
|
1463
|
+
constructor(expr, kind) {
|
|
1464
|
+
this.expr = expr;
|
|
1465
|
+
this.kind = kind;
|
|
1466
|
+
}
|
|
1467
|
+
static comparison(field, op, value) {
|
|
1468
|
+
return new Filter(`${field}${op}${renderValue(value)}`, "comparison");
|
|
1469
|
+
}
|
|
1470
|
+
static list(field, op, values) {
|
|
1471
|
+
if (values.length === 0) {
|
|
1472
|
+
throw new Error(`Filter.${op === "=in=" ? "in" : "out"}("${field}", ...) needs at least one value`);
|
|
1473
|
+
}
|
|
1474
|
+
return new Filter(`${field}${op}(${values.map(renderValue).join(",")})`, "comparison");
|
|
1475
|
+
}
|
|
1476
|
+
static eq(field, value) {
|
|
1477
|
+
return Filter.comparison(field, "==", value);
|
|
1478
|
+
}
|
|
1479
|
+
static ne(field, value) {
|
|
1480
|
+
return Filter.comparison(field, "!=", value);
|
|
1481
|
+
}
|
|
1482
|
+
static gt(field, value) {
|
|
1483
|
+
return Filter.comparison(field, "=gt=", value);
|
|
1484
|
+
}
|
|
1485
|
+
static ge(field, value) {
|
|
1486
|
+
return Filter.comparison(field, "=ge=", value);
|
|
1487
|
+
}
|
|
1488
|
+
static lt(field, value) {
|
|
1489
|
+
return Filter.comparison(field, "=lt=", value);
|
|
1490
|
+
}
|
|
1491
|
+
static le(field, value) {
|
|
1492
|
+
return Filter.comparison(field, "=le=", value);
|
|
1493
|
+
}
|
|
1494
|
+
static in(field, values) {
|
|
1495
|
+
return Filter.list(field, "=in=", values);
|
|
1496
|
+
}
|
|
1497
|
+
static out(field, values) {
|
|
1498
|
+
return Filter.list(field, "=out=", values);
|
|
1499
|
+
}
|
|
1500
|
+
static isNull(field) {
|
|
1501
|
+
return new Filter(`${field}==null`, "comparison");
|
|
1502
|
+
}
|
|
1503
|
+
static isNotNull(field) {
|
|
1504
|
+
return new Filter(`${field}!=null`, "comparison");
|
|
1505
|
+
}
|
|
1506
|
+
and(other) {
|
|
1507
|
+
return new Filter(`${this.renderIn("and")};${other.renderIn("and")}`, "and");
|
|
1508
|
+
}
|
|
1509
|
+
or(other) {
|
|
1510
|
+
return new Filter(`${this.expr},${other.expr}`, "or");
|
|
1511
|
+
}
|
|
1512
|
+
renderIn(parent) {
|
|
1513
|
+
return this.kind === "or" ? `(${this.expr})` : this.expr;
|
|
1514
|
+
}
|
|
1515
|
+
build() {
|
|
1516
|
+
return this.expr;
|
|
1517
|
+
}
|
|
1518
|
+
toString() {
|
|
1519
|
+
return this.expr;
|
|
1520
|
+
}
|
|
1521
|
+
}
|
|
1522
|
+
var SongFields = {
|
|
1523
|
+
title: "title",
|
|
1524
|
+
artist: "artist",
|
|
1525
|
+
album: "album",
|
|
1526
|
+
albumArtist: "albumArtist",
|
|
1527
|
+
genre: "genre",
|
|
1528
|
+
composer: "composer",
|
|
1529
|
+
label: "label",
|
|
1530
|
+
duration: "duration",
|
|
1531
|
+
trackNumber: "trackNumber",
|
|
1532
|
+
discNumber: "discNumber",
|
|
1533
|
+
mbId: "mbId",
|
|
1534
|
+
isrc: "isrc",
|
|
1535
|
+
sha256: "sha256",
|
|
1536
|
+
uri: "uri",
|
|
1537
|
+
albumUri: "albumUri",
|
|
1538
|
+
artistUri: "artistUri",
|
|
1539
|
+
createdAt: "createdAt"
|
|
1540
|
+
};
|
|
1541
|
+
var AlbumFields = {
|
|
1542
|
+
title: "title",
|
|
1543
|
+
artist: "artist",
|
|
1544
|
+
year: "year",
|
|
1545
|
+
releaseDate: "releaseDate",
|
|
1546
|
+
sha256: "sha256",
|
|
1547
|
+
uri: "uri",
|
|
1548
|
+
artistUri: "artistUri",
|
|
1549
|
+
createdAt: "createdAt"
|
|
1550
|
+
};
|
|
1551
|
+
var ArtistFields = {
|
|
1552
|
+
name: "name",
|
|
1553
|
+
genres: "genres",
|
|
1554
|
+
bornIn: "bornIn",
|
|
1555
|
+
born: "born",
|
|
1556
|
+
died: "died",
|
|
1557
|
+
sha256: "sha256",
|
|
1558
|
+
uri: "uri",
|
|
1559
|
+
createdAt: "createdAt"
|
|
1560
|
+
};
|
|
1561
|
+
var ScrobbleFields = {
|
|
1562
|
+
uri: "uri",
|
|
1563
|
+
date: "date",
|
|
1564
|
+
timestamp: "timestamp",
|
|
1565
|
+
title: "title",
|
|
1566
|
+
artist: "artist",
|
|
1567
|
+
album: "album",
|
|
1568
|
+
trackTitle: "track.title",
|
|
1569
|
+
trackArtist: "track.artist",
|
|
1570
|
+
trackAlbum: "track.album",
|
|
1571
|
+
trackAlbumArtist: "track.albumArtist",
|
|
1572
|
+
trackGenre: "track.genre",
|
|
1573
|
+
trackDuration: "track.duration",
|
|
1574
|
+
trackIsrc: "track.isrc",
|
|
1575
|
+
trackMbId: "track.mbId",
|
|
1576
|
+
userDid: "user.did",
|
|
1577
|
+
userHandle: "user.handle",
|
|
1578
|
+
userDisplayName: "user.displayName",
|
|
1579
|
+
artistName: "artist.name",
|
|
1580
|
+
artistGenres: "artist.genres"
|
|
1581
|
+
};
|
|
1582
|
+
// src/hash.ts
|
|
1583
|
+
import { sha256 } from "@noble/hashes/sha2.js";
|
|
1584
|
+
import { bytesToHex } from "@noble/hashes/utils.js";
|
|
1585
|
+
function sha256Lower(s) {
|
|
1586
|
+
return bytesToHex(sha256(new TextEncoder().encode(s.toLowerCase())));
|
|
1587
|
+
}
|
|
1588
|
+
function songHash(title, artist, album) {
|
|
1589
|
+
return sha256Lower(`${title} - ${artist} - ${album}`);
|
|
1590
|
+
}
|
|
1591
|
+
function albumHash(album, albumArtist) {
|
|
1592
|
+
return sha256Lower(`${album} - ${albumArtist}`);
|
|
1593
|
+
}
|
|
1594
|
+
function artistHash(albumArtist) {
|
|
1595
|
+
return sha256Lower(albumArtist);
|
|
1596
|
+
}
|
|
1609
1597
|
|
|
1610
1598
|
// src/index.ts
|
|
1611
1599
|
init_errors();
|
|
1612
1600
|
export {
|
|
1613
1601
|
Agent,
|
|
1602
|
+
AlbumFields,
|
|
1603
|
+
ArtistFields,
|
|
1614
1604
|
DEFAULT_APPVIEW,
|
|
1615
1605
|
DEFAULT_JETSTREAM_SERVERS,
|
|
1616
1606
|
DEFAULT_MATCH_SONG_PER_HOUR,
|
|
1617
1607
|
DEFAULT_REMOTE_WS,
|
|
1608
|
+
Filter,
|
|
1618
1609
|
Interval,
|
|
1619
1610
|
MAX_SAFE_WRITES_PER_HOUR,
|
|
1620
1611
|
RemoteController,
|
|
1621
1612
|
RemotePlayer,
|
|
1622
1613
|
RockskyClient,
|
|
1623
1614
|
RockskyError,
|
|
1624
|
-
RockskyIndex,
|
|
1625
1615
|
RockskyLibrary,
|
|
1616
|
+
ScrobbleFields,
|
|
1617
|
+
SongFields,
|
|
1626
1618
|
albumHash,
|
|
1627
1619
|
artistHash,
|
|
1628
1620
|
runJetstream,
|
|
1629
|
-
songHash
|
|
1630
|
-
totalIndexed
|
|
1621
|
+
songHash
|
|
1631
1622
|
};
|
package/dist/remote.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"remote.d.ts","sourceRoot":"","sources":["../src/remote.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"remote.d.ts","sourceRoot":"","sources":["../src/remote.ts"],"names":[],"mappings":"AAMA,OAAO,EACL,YAAY,EACZ,iBAAiB,EACjB,KAAK,mBAAmB,EACxB,KAAK,oBAAoB,EACzB,KAAK,gBAAgB,EACrB,KAAK,eAAe,EACpB,KAAK,cAAc,GACpB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACL,gBAAgB,EAChB,KAAK,uBAAuB,EAC5B,KAAK,sBAAsB,EAC3B,KAAK,YAAY,EACjB,KAAK,YAAY,GAClB,MAAM,wBAAwB,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rocksky/sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.0",
|
|
4
4
|
"description": "TypeScript SDK for Rocksky — built on atcute: AppView reads, AT Protocol PDS writes (scrobble, like, follow, shout), a local dedup index, and Jetstream real-time sync.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -26,6 +26,11 @@
|
|
|
26
26
|
"types": "./dist/remote.d.ts",
|
|
27
27
|
"import": "./dist/remote.js",
|
|
28
28
|
"default": "./dist/remote.js"
|
|
29
|
+
},
|
|
30
|
+
"./dedup": {
|
|
31
|
+
"types": "./dist/dedup.d.ts",
|
|
32
|
+
"import": "./dist/dedup.js",
|
|
33
|
+
"default": "./dist/dedup.js"
|
|
29
34
|
}
|
|
30
35
|
},
|
|
31
36
|
"files": [
|
|
@@ -34,7 +39,7 @@
|
|
|
34
39
|
"README.md"
|
|
35
40
|
],
|
|
36
41
|
"scripts": {
|
|
37
|
-
"build": "bun build ./src/index.ts ./src/remote.ts --outdir ./dist --target node --format esm --packages external && bun run build:types",
|
|
42
|
+
"build": "bun build ./src/index.ts ./src/remote.ts ./src/dedup.ts --outdir ./dist --target node --format esm --packages external && bun run build:types",
|
|
38
43
|
"build:types": "tsc -p tsconfig.build.json",
|
|
39
44
|
"typecheck": "tsc --noEmit",
|
|
40
45
|
"test": "bun test",
|
|
@@ -69,6 +74,7 @@
|
|
|
69
74
|
"@atcute/identity": "^2.0.2",
|
|
70
75
|
"@atcute/identity-resolver": "^2.0.1",
|
|
71
76
|
"@atcute/password-session": "^1.0.2",
|
|
77
|
+
"@noble/hashes": "^2.3.0",
|
|
72
78
|
"classic-level": "^3.0.0"
|
|
73
79
|
}
|
|
74
80
|
}
|
package/src/agent.ts
CHANGED
|
@@ -325,7 +325,7 @@ export class Agent {
|
|
|
325
325
|
const res = await this.rpc.post("com.atproto.repo.createRecord" as never, {
|
|
326
326
|
input: { repo: this.did, collection, record: { ...record, $type: collection } },
|
|
327
327
|
} as never);
|
|
328
|
-
if (!res.ok) throw new RockskyError(res.data);
|
|
328
|
+
if (!res.ok) throw new RockskyError(res.data, res.status);
|
|
329
329
|
return (res.data as { uri: string }).uri;
|
|
330
330
|
}
|
|
331
331
|
|
|
@@ -334,7 +334,7 @@ export class Agent {
|
|
|
334
334
|
const res = await this.rpc.post("com.atproto.repo.putRecord" as never, {
|
|
335
335
|
input: { repo: this.did, collection, rkey, record: { ...record, $type: collection } },
|
|
336
336
|
} as never);
|
|
337
|
-
if (!res.ok) throw new RockskyError(res.data);
|
|
337
|
+
if (!res.ok) throw new RockskyError(res.data, res.status);
|
|
338
338
|
return (res.data as { uri: string }).uri;
|
|
339
339
|
}
|
|
340
340
|
|
|
@@ -344,7 +344,7 @@ export class Agent {
|
|
|
344
344
|
const res = await this.rpc.post("com.atproto.repo.deleteRecord" as never, {
|
|
345
345
|
input: { repo: this.did, collection, rkey },
|
|
346
346
|
} as never);
|
|
347
|
-
if (!res.ok) throw new RockskyError(res.data);
|
|
347
|
+
if (!res.ok) throw new RockskyError(res.data, res.status);
|
|
348
348
|
}
|
|
349
349
|
|
|
350
350
|
/**
|