@serifpersia/dango 3.6.7 → 3.6.8

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.
Files changed (149) hide show
  1. package/README.md +1 -1
  2. package/client/dist/assets/{AnimeCard-C3P9zWi9.js → AnimeCard-DraA_TF9.js} +1 -1
  3. package/client/dist/assets/{AnimeInfo-Et6687Tl.js → AnimeInfo-BJ7pzs-P.js} +1 -1
  4. package/client/dist/assets/{AnimeInfoPage-DX4eNnlH.js → AnimeInfoPage-B_esUr5C.js} +2 -2
  5. package/client/dist/assets/{Asmr-CCdEWrFi.js → Asmr-Ckuh2xfW.js} +1 -1
  6. package/client/dist/assets/{AsmrResetProgressModal-DgEWBJOw.js → AsmrResetProgressModal-BS6UFJN4.js} +1 -1
  7. package/client/dist/assets/{AvSyncCalibrator-CQ2fjf2E.js → AvSyncCalibrator-DwHCd9yB.js} +1 -1
  8. package/client/dist/assets/{Home-DUkUTTTy.js → Home-DuM1EuvN.js} +1 -1
  9. package/client/dist/assets/{Insights-QCh4xW12.js → Insights-D3Pd9c9V.js} +1 -1
  10. package/client/dist/assets/{LibraryListPage-BoTxRqHz.js → LibraryListPage-Dm2yVn0-.js} +1 -1
  11. package/client/dist/assets/{ListeningList-DguKjI_t.js → ListeningList-D-zt6EXZ.js} +1 -1
  12. package/client/dist/assets/{Manga-HydR-L7o.js → Manga-JwprMKBk.js} +1 -1
  13. package/client/dist/assets/{MangaInfoPage-CBakEI9a.js → MangaInfoPage-DaeyJl2p.js} +1 -1
  14. package/client/dist/assets/{MangaReadPage-DwAR3iPy.js → MangaReadPage-COg0fHI9.js} +1 -1
  15. package/client/dist/assets/{MangaResetProgressModal-DGzl8vsO.js → MangaResetProgressModal-kguXv0_M.js} +1 -1
  16. package/client/dist/assets/{Mature-Pi0TxKuF.js → Mature-IHDaasrs.js} +1 -1
  17. package/client/dist/assets/{MediaCard--cuFxOCS.js → MediaCard-CpwsrXo5.js} +1 -1
  18. package/client/dist/assets/{Music-CQpyXTLH.js → Music-Dks4tPeK.js} +1 -1
  19. package/client/dist/assets/{OptionListMenu-B3D39dGS.js → OptionListMenu-11CcAsy8.js} +1 -1
  20. package/client/dist/assets/{Player-CsH4x_I_.js → Player-DQoEQkHL.js} +2 -2
  21. package/client/dist/assets/{PlayerSettings-DGxBsi3F.js → PlayerSettings-D4Ud4kv4.js} +1 -1
  22. package/client/dist/assets/{QueueOptionsButton-Djq4Cpex.js → QueueOptionsButton-B8PlPKjC.js} +1 -1
  23. package/client/dist/assets/{QueueRail-DOrYgNVX.js → QueueRail-B4Dok7U3.js} +1 -1
  24. package/client/dist/assets/{Radio-DF6g6odd.js → Radio-BHK6LSk4.js} +1 -1
  25. package/client/dist/assets/{ReadingList-DwYoBrNk.js → ReadingList-BCsxIz57.js} +1 -1
  26. package/client/dist/assets/{ResetProgressModal-CMF86mX6.js → ResetProgressModal-BXHxSfy-.js} +1 -1
  27. package/client/dist/assets/{Search-DFkhuk-x.js → Search-PK1ds07O.js} +1 -1
  28. package/client/dist/assets/{Settings-DBB2r0VQ.js → Settings-Bxk_x55l.js} +1 -1
  29. package/client/dist/assets/{SkeletonGrid-DJelJgAv.js → SkeletonGrid-CpJCZSRN.js} +1 -1
  30. package/client/dist/assets/{Trackers-DGYXFhYZ.js → Trackers-RieCh_84.js} +1 -1
  31. package/client/dist/assets/{Tv-C5MDGD2Z.js → Tv-DLytPJUj.js} +1 -1
  32. package/client/dist/assets/{TvInfo-DrKuK13u.js → TvInfo-C8djKebR.js} +1 -1
  33. package/client/dist/assets/{TvPopup-DtHilRDT.js → TvPopup-40JGGJDk.js} +1 -1
  34. package/client/dist/assets/{TvSearch-CzNxdu_w.js → TvSearch-A3hbospf.js} +1 -1
  35. package/client/dist/assets/{TvWatchlist-D7p4br6S.js → TvWatchlist-DuXjqmZM.js} +1 -1
  36. package/client/dist/assets/{Watchlist-DrOzswl2.js → Watchlist-B9wGBh0u.js} +1 -1
  37. package/client/dist/assets/{hls-BGCX7mJy.js → hls-CZ5X3Ya-.js} +1 -1
  38. package/client/dist/assets/{index-CnfZwsds.js → index-dRhc9-ol.js} +2 -2
  39. package/client/dist/assets/{useAnimeInfoData-DYXWBPF-.js → useAnimeInfoData-ZzDtLfvD.js} +1 -1
  40. package/client/dist/assets/{useAsmr-BbhQ59bk.js → useAsmr-CKOdHgol.js} +1 -1
  41. package/client/dist/assets/{useAsmrPopup-SymV1J-b.js → useAsmrPopup-SFHPhpcl.js} +1 -1
  42. package/client/dist/assets/{useMangaLibrary-DuesWQaE.js → useMangaLibrary-CKATe31P.js} +1 -1
  43. package/client/dist/assets/{useMangaPopup-D57OVGUr.js → useMangaPopup-C75QuYgY.js} +1 -1
  44. package/client/dist/assets/{useProviders-mpQb_dix.js → useProviders-CMtvZbEi.js} +1 -1
  45. package/client/dist/assets/{useTv-DGflCsSI.js → useTv-DUX4zYMr.js} +1 -1
  46. package/client/dist/assets/{useTvLibrary-BqeSmyc4.js → useTvLibrary-By4Gt-R_.js} +1 -1
  47. package/client/dist/index.html +1 -1
  48. package/package.json +2 -2
  49. package/server/dist/app-auth.js +1 -157
  50. package/server/dist/app-hono.js +7 -557
  51. package/server/dist/config.js +1 -174
  52. package/server/dist/db/drizzle.js +1 -16
  53. package/server/dist/db/schema-anime.js +1 -61
  54. package/server/dist/db/schema-asmr.js +1 -13
  55. package/server/dist/db/schema-manga.js +1 -31
  56. package/server/dist/db/schema-tv.js +1 -18
  57. package/server/dist/db.js +1 -217
  58. package/server/dist/discord-gateway.js +1 -385
  59. package/server/dist/discord-rpc.js +1 -413
  60. package/server/dist/github-sync.js +1 -508
  61. package/server/dist/google.js +1 -741
  62. package/server/dist/hono/asmr.js +1 -10
  63. package/server/dist/hono/auth.js +3 -26
  64. package/server/dist/hono/music.js +1 -209
  65. package/server/dist/hono/proxy.js +36 -783
  66. package/server/dist/hono/tv.js +5 -200
  67. package/server/dist/hono/watchlist.js +2 -338
  68. package/server/dist/lib/anilist.js +26 -1458
  69. package/server/dist/lib/anischedule.js +1 -191
  70. package/server/dist/lib/insights.js +1 -185
  71. package/server/dist/lib/ipc.js +1 -23
  72. package/server/dist/lib/kitsu.js +1 -517
  73. package/server/dist/lib/mal-import.js +1 -291
  74. package/server/dist/lib/mal.js +1 -957
  75. package/server/dist/lib/migration.js +1 -310
  76. package/server/dist/lib/offline-db.js +4 -384
  77. package/server/dist/lib/potoken.js +1 -135
  78. package/server/dist/lib/recommendations.service.js +1 -216
  79. package/server/dist/lib/temp-ids.js +1 -3
  80. package/server/dist/lib/tmdb.js +1 -72
  81. package/server/dist/lib/tracker/anilist-tracker.js +8 -245
  82. package/server/dist/lib/tracker/manga-sync.service.js +1 -773
  83. package/server/dist/lib/tracker/sync-state.js +1 -21
  84. package/server/dist/lib/tracker/sync.service.js +1 -476
  85. package/server/dist/lib/translate.js +3 -79
  86. package/server/dist/lib/ump.js +1 -106
  87. package/server/dist/lib/ytmusic.js +1 -162
  88. package/server/dist/logger.js +1 -16
  89. package/server/dist/providers/manga/manga.types.js +0 -1
  90. package/server/dist/providers/provider.interface.js +0 -1
  91. package/server/dist/providers/remote-loader.js +2 -614
  92. package/server/dist/providers/remote-types.js +1 -159
  93. package/server/dist/providers/title-matching.js +1 -226
  94. package/server/dist/providers/tv.types.js +0 -1
  95. package/server/dist/rclone.js +2 -120
  96. package/server/dist/repositories/asmr.repository.js +11 -46
  97. package/server/dist/repositories/insights.repository.js +11 -25
  98. package/server/dist/repositories/library.repository.js +3 -94
  99. package/server/dist/repositories/mal-cache.repository.js +1 -52
  100. package/server/dist/repositories/manga.repository.js +14 -58
  101. package/server/dist/repositories/notifications.repository.js +6 -52
  102. package/server/dist/repositories/queue.repository.js +5 -58
  103. package/server/dist/repositories/settings.repository.js +2 -29
  104. package/server/dist/repositories/shows-meta.repository.js +3 -56
  105. package/server/dist/repositories/temp-show-ids.repository.js +3 -63
  106. package/server/dist/repositories/tv.repository.js +11 -54
  107. package/server/dist/repositories/watched-episodes.repository.js +6 -55
  108. package/server/dist/repositories/watchlist.repository.js +3 -54
  109. package/server/dist/scripts/backfill-episode-duration.js +1 -42
  110. package/server/dist/server.js +1 -310
  111. package/server/dist/shipped-defaults.js +1 -6
  112. package/server/dist/sync-payload.js +1 -91
  113. package/server/dist/sync.js +3 -647
  114. package/server/dist/trpc/context.js +1 -7
  115. package/server/dist/trpc/errors.js +1 -15
  116. package/server/dist/trpc/index.js +1 -18
  117. package/server/dist/trpc/router.js +1 -49
  118. package/server/dist/trpc/routers/asmr-library.js +1 -112
  119. package/server/dist/trpc/routers/asmr-progress.js +1 -121
  120. package/server/dist/trpc/routers/asmr.js +1 -87
  121. package/server/dist/trpc/routers/auth.js +1 -194
  122. package/server/dist/trpc/routers/continue-watching.js +2 -58
  123. package/server/dist/trpc/routers/data.js +1 -1026
  124. package/server/dist/trpc/routers/discord.js +1 -285
  125. package/server/dist/trpc/routers/discovery.js +1 -11
  126. package/server/dist/trpc/routers/insights.js +1 -14
  127. package/server/dist/trpc/routers/manga-library.js +1 -263
  128. package/server/dist/trpc/routers/manga-progress.js +1 -169
  129. package/server/dist/trpc/routers/manga.js +1 -201
  130. package/server/dist/trpc/routers/music.js +1 -655
  131. package/server/dist/trpc/routers/notifications.js +1 -80
  132. package/server/dist/trpc/routers/progress.js +1 -109
  133. package/server/dist/trpc/routers/queue.js +1 -93
  134. package/server/dist/trpc/routers/radio.js +1 -145
  135. package/server/dist/trpc/routers/settings.js +1 -96
  136. package/server/dist/trpc/routers/tracker.js +1 -213
  137. package/server/dist/trpc/routers/tv-library.js +1 -135
  138. package/server/dist/trpc/routers/tv-progress.js +1 -159
  139. package/server/dist/trpc/routers/tv.js +1 -551
  140. package/server/dist/trpc/routers/watchlist.js +2 -549
  141. package/server/dist/trpc/validation.js +1 -258
  142. package/server/dist/utils/cache.utils.js +1 -25
  143. package/server/dist/utils/cookie.utils.js +1 -13
  144. package/server/dist/utils/db-utils.js +5 -17
  145. package/server/dist/utils/env.utils.js +5 -77
  146. package/server/dist/utils/http.utils.js +1 -82
  147. package/server/dist/utils/request-context.js +1 -2
  148. package/server/dist/utils/security.utils.js +1 -76
  149. package/server/package.json +3 -2
@@ -1,159 +1 @@
1
- const ID_RE = /^[a-z0-9][a-z0-9-]*$/i;
2
- const SHA_RE = /^[0-9a-f]{64}$/i;
3
- export function parseRegistry(raw) {
4
- if (!raw || typeof raw !== 'object')
5
- throw new Error('registry: not an object');
6
- const r = raw;
7
- if (typeof r.version !== 'number')
8
- throw new Error('registry.version must be a number');
9
- if (!Array.isArray(r.providers))
10
- throw new Error('registry.providers must be an array');
11
- const providers = r.providers.map((p, i) => parseEntry(p, i));
12
- const seen = new Set();
13
- for (const p of providers) {
14
- const key = p.id.toLowerCase();
15
- if (seen.has(key))
16
- throw new Error(`registry: duplicate provider id "${p.id}"`);
17
- seen.add(key);
18
- }
19
- return {
20
- version: r.version,
21
- updatedAt: typeof r.updatedAt === 'string' ? r.updatedAt : undefined,
22
- providers,
23
- };
24
- }
25
- function parseEntry(raw, index) {
26
- if (!raw || typeof raw !== 'object')
27
- throw new Error(`registry.providers[${index}]: not an object`);
28
- const p = raw;
29
- const id = p.id;
30
- const entry = p.entry;
31
- const label = p.label;
32
- const version = p.version;
33
- if (typeof id !== 'string' || !ID_RE.test(id.trim())) {
34
- throw new Error(`registry.providers[${index}].id must match ${ID_RE}`);
35
- }
36
- if (typeof label !== 'string' || !label.trim()) {
37
- throw new Error(`registry.providers[${index}].label must be a non-empty string`);
38
- }
39
- if (typeof version !== 'string' || !version.trim()) {
40
- throw new Error(`registry.providers[${index}].version must be a non-empty string`);
41
- }
42
- const embedBase = p.embedBase;
43
- if (embedBase !== undefined) {
44
- if (typeof embedBase !== 'string' || !/^https:\/\/[^/]+/i.test(embedBase.trim())) {
45
- throw new Error(`registry.providers[${index}].embedBase must be an https URL`);
46
- }
47
- }
48
- if ((typeof entry !== 'string' || !entry.trim()) && embedBase === undefined) {
49
- throw new Error(`registry.providers[${index}].entry must be a non-empty string`);
50
- }
51
- if (p.sha256 !== undefined && (typeof p.sha256 !== 'string' || !SHA_RE.test(p.sha256.trim()))) {
52
- throw new Error(`registry.providers[${index}].sha256 must be 64 hex chars`);
53
- }
54
- if (p.mature !== undefined && typeof p.mature !== 'boolean') {
55
- throw new Error(`registry.providers[${index}].mature must be boolean`);
56
- }
57
- if (p.kind !== undefined &&
58
- p.kind !== 'anime' &&
59
- p.kind !== 'asmr' &&
60
- p.kind !== 'manga' &&
61
- p.kind !== 'tv') {
62
- throw new Error(`registry.providers[${index}].kind must be anime|asmr|manga|tv`);
63
- }
64
- if (p.sub !== undefined && p.sub !== 'soft' && p.sub !== 'hard' && p.sub !== 'mixed') {
65
- throw new Error(`registry.providers[${index}].sub must be soft|hard|mixed`);
66
- }
67
- if (p.tier !== undefined && p.tier !== 'direct' && p.tier !== 'embed' && p.tier !== 'cookie') {
68
- throw new Error(`registry.providers[${index}].tier must be direct|embed|cookie`);
69
- }
70
- let modes;
71
- if (p.modes !== undefined) {
72
- if (!Array.isArray(p.modes))
73
- throw new Error(`registry.providers[${index}].modes must be an array`);
74
- modes = [];
75
- for (const m of p.modes) {
76
- if (m !== 'sub' && m !== 'dub') {
77
- throw new Error(`registry.providers[${index}].modes must contain only sub|dub`);
78
- }
79
- if (!modes.includes(m))
80
- modes.push(m);
81
- }
82
- }
83
- return {
84
- id: id.trim(),
85
- label: label.trim(),
86
- version: version.trim(),
87
- entry: typeof entry === 'string' ? entry.trim() : undefined,
88
- embedBase: typeof embedBase === 'string' ? embedBase.trim().replace(/\/+$/, '') : undefined,
89
- sha256: typeof p.sha256 === 'string' ? p.sha256.trim().toLowerCase() : undefined,
90
- minDango: typeof p.minDango === 'string' ? p.minDango : undefined,
91
- mature: typeof p.mature === 'boolean' ? p.mature : false,
92
- kind: p.kind === 'asmr' || p.kind === 'manga' || p.kind === 'tv' ? p.kind : 'anime',
93
- sub: p.sub,
94
- tier: p.tier,
95
- modes,
96
- browse: parseBrowseCaps(p.browse, index),
97
- proxy: parseProxyRule(p.proxy, index),
98
- auth: p.auth === 'cookie' ? 'cookie' : undefined,
99
- enabledByDefault: p.enabledByDefault === false ? false : true,
100
- };
101
- }
102
- function parseProxyRule(raw, index) {
103
- if (raw === undefined)
104
- return undefined;
105
- if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
106
- throw new Error(`registry.providers[${index}].proxy must be an object`);
107
- }
108
- const p = raw;
109
- const rule = {};
110
- for (const key of ['hosts', 'suffixes', 'mirrors']) {
111
- if (p[key] === undefined)
112
- continue;
113
- if (!Array.isArray(p[key]) || !p[key].every((v) => typeof v === 'string' && v)) {
114
- throw new Error(`registry.providers[${index}].proxy.${key} must be a non-empty string array`);
115
- }
116
- rule[key] = p[key].map((v) => v.trim().toLowerCase());
117
- }
118
- for (const key of ['referer', 'origin']) {
119
- if (p[key] === undefined)
120
- continue;
121
- if (typeof p[key] !== 'string' || !/^https:\/\//i.test(p[key].trim())) {
122
- throw new Error(`registry.providers[${index}].proxy.${key} must be an https URL`);
123
- }
124
- rule[key] = p[key].trim();
125
- }
126
- for (const key of ['originFromReferer', 'scrape', 'cfCookie', 'embedPatch']) {
127
- if (p[key] === undefined)
128
- continue;
129
- if (typeof p[key] !== 'boolean') {
130
- throw new Error(`registry.providers[${index}].proxy.${key} must be boolean`);
131
- }
132
- rule[key] = p[key];
133
- }
134
- return rule;
135
- }
136
- function parseBrowseCaps(raw, index) {
137
- if (raw === undefined)
138
- return undefined;
139
- if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
140
- throw new Error(`registry.providers[${index}].browse must be an object`);
141
- }
142
- const b = raw;
143
- const caps = {};
144
- for (const key of ['genre', 'order', 'studio', 'sort']) {
145
- if (b[key] === undefined)
146
- continue;
147
- if (typeof b[key] !== 'boolean') {
148
- throw new Error(`registry.providers[${index}].browse.${key} must be boolean`);
149
- }
150
- caps[key] = b[key];
151
- }
152
- if (b.pageSize !== undefined) {
153
- if (typeof b.pageSize !== 'number' || !Number.isInteger(b.pageSize) || b.pageSize < 1) {
154
- throw new Error(`registry.providers[${index}].browse.pageSize must be a positive integer`);
155
- }
156
- caps.pageSize = b.pageSize;
157
- }
158
- return caps;
159
- }
1
+ const f=/^[a-z0-9][a-z0-9-]*$/i,m=/^[0-9a-f]{64}$/i;function g(t){if(!t||typeof t!="object")throw new Error("registry: not an object");const o=t;if(typeof o.version!="number")throw new Error("registry.version must be a number");if(!Array.isArray(o.providers))throw new Error("registry.providers must be an array");const e=o.providers.map((r,s)=>p(r,s)),n=new Set;for(const r of e){const s=r.id.toLowerCase();if(n.has(s))throw new Error(`registry: duplicate provider id "${r.id}"`);n.add(s)}return{version:o.version,updatedAt:typeof o.updatedAt=="string"?o.updatedAt:void 0,providers:e}}function p(t,o){if(!t||typeof t!="object")throw new Error(`registry.providers[${o}]: not an object`);const e=t,n=e.id,r=e.entry,s=e.label,u=e.version;if(typeof n!="string"||!f.test(n.trim()))throw new Error(`registry.providers[${o}].id must match ${f}`);if(typeof s!="string"||!s.trim())throw new Error(`registry.providers[${o}].label must be a non-empty string`);if(typeof u!="string"||!u.trim())throw new Error(`registry.providers[${o}].version must be a non-empty string`);const i=e.embedBase;if(i!==void 0&&(typeof i!="string"||!/^https:\/\/[^/]+/i.test(i.trim())))throw new Error(`registry.providers[${o}].embedBase must be an https URL`);if((typeof r!="string"||!r.trim())&&i===void 0)throw new Error(`registry.providers[${o}].entry must be a non-empty string`);if(e.sha256!==void 0&&(typeof e.sha256!="string"||!m.test(e.sha256.trim())))throw new Error(`registry.providers[${o}].sha256 must be 64 hex chars`);if(e.mature!==void 0&&typeof e.mature!="boolean")throw new Error(`registry.providers[${o}].mature must be boolean`);if(e.kind!==void 0&&e.kind!=="anime"&&e.kind!=="asmr"&&e.kind!=="manga"&&e.kind!=="tv")throw new Error(`registry.providers[${o}].kind must be anime|asmr|manga|tv`);if(e.sub!==void 0&&e.sub!=="soft"&&e.sub!=="hard"&&e.sub!=="mixed")throw new Error(`registry.providers[${o}].sub must be soft|hard|mixed`);if(e.tier!==void 0&&e.tier!=="direct"&&e.tier!=="embed"&&e.tier!=="cookie")throw new Error(`registry.providers[${o}].tier must be direct|embed|cookie`);let a;if(e.modes!==void 0){if(!Array.isArray(e.modes))throw new Error(`registry.providers[${o}].modes must be an array`);a=[];for(const d of e.modes){if(d!=="sub"&&d!=="dub")throw new Error(`registry.providers[${o}].modes must contain only sub|dub`);a.includes(d)||a.push(d)}}return{id:n.trim(),label:s.trim(),version:u.trim(),entry:typeof r=="string"?r.trim():void 0,embedBase:typeof i=="string"?i.trim().replace(/\/+$/,""):void 0,sha256:typeof e.sha256=="string"?e.sha256.trim().toLowerCase():void 0,minDango:typeof e.minDango=="string"?e.minDango:void 0,mature:typeof e.mature=="boolean"?e.mature:!1,kind:e.kind==="asmr"||e.kind==="manga"||e.kind==="tv"?e.kind:"anime",sub:e.sub,tier:e.tier,modes:a,browse:y(e.browse,o),proxy:b(e.proxy,o),auth:e.auth==="cookie"?"cookie":void 0,enabledByDefault:e.enabledByDefault!==!1}}function b(t,o){if(t===void 0)return;if(!t||typeof t!="object"||Array.isArray(t))throw new Error(`registry.providers[${o}].proxy must be an object`);const e=t,n={};for(const r of["hosts","suffixes","mirrors"])if(e[r]!==void 0){if(!Array.isArray(e[r])||!e[r].every(s=>typeof s=="string"&&s))throw new Error(`registry.providers[${o}].proxy.${r} must be a non-empty string array`);n[r]=e[r].map(s=>s.trim().toLowerCase())}for(const r of["referer","origin"])if(e[r]!==void 0){if(typeof e[r]!="string"||!/^https:\/\//i.test(e[r].trim()))throw new Error(`registry.providers[${o}].proxy.${r} must be an https URL`);n[r]=e[r].trim()}for(const r of["originFromReferer","scrape","cfCookie","embedPatch"])if(e[r]!==void 0){if(typeof e[r]!="boolean")throw new Error(`registry.providers[${o}].proxy.${r} must be boolean`);n[r]=e[r]}return n}function y(t,o){if(t===void 0)return;if(!t||typeof t!="object"||Array.isArray(t))throw new Error(`registry.providers[${o}].browse must be an object`);const e=t,n={};for(const r of["genre","order","studio","sort"])if(e[r]!==void 0){if(typeof e[r]!="boolean")throw new Error(`registry.providers[${o}].browse.${r} must be boolean`);n[r]=e[r]}if(e.pageSize!==void 0){if(typeof e.pageSize!="number"||!Number.isInteger(e.pageSize)||e.pageSize<1)throw new Error(`registry.providers[${o}].browse.pageSize must be a positive integer`);n.pageSize=e.pageSize}return n}export{g as parseRegistry};
@@ -1,226 +1 @@
1
- function normalizeCompact(s) {
2
- return s.toLowerCase().replace(/[^a-z0-9]/g, '');
3
- }
4
- function tokenize(s) {
5
- return s
6
- .toLowerCase()
7
- .replace(/[^\p{L}\p{N}\s]/gu, ' ')
8
- .split(/\s+/)
9
- .filter((w) => w.length >= 2);
10
- }
11
- function bigramDice(a, b) {
12
- if (a.length < 2 || b.length < 2)
13
- return a === b ? 1 : 0;
14
- const grams = (s) => {
15
- const map = new Map();
16
- for (let i = 0; i < s.length - 1; i++) {
17
- const g = s.slice(i, i + 2);
18
- map.set(g, (map.get(g) || 0) + 1);
19
- }
20
- return map;
21
- };
22
- const A = grams(a);
23
- const B = grams(b);
24
- let totalA = 0;
25
- let totalB = 0;
26
- let shared = 0;
27
- A.forEach((c) => (totalA += c));
28
- B.forEach((c) => (totalB += c));
29
- A.forEach((c, g) => {
30
- const other = B.get(g);
31
- if (other)
32
- shared += Math.min(c, other);
33
- });
34
- return (2 * shared) / (totalA + totalB);
35
- }
36
- const STOPWORDS = new Set([
37
- 'the',
38
- 'animation',
39
- 'no',
40
- 'wa',
41
- 'ga',
42
- 'wo',
43
- 'ni',
44
- 'de',
45
- 'to',
46
- 'e',
47
- 'o',
48
- 'x',
49
- 'ii',
50
- 'yo',
51
- 'ova',
52
- 'oad',
53
- 'hd',
54
- 'ep',
55
- 'episode',
56
- 'season',
57
- 'part',
58
- 'hen',
59
- 'a',
60
- ]);
61
- const SEASON_TOKENS = new Set([
62
- 'season',
63
- '2nd',
64
- '3rd',
65
- '4th',
66
- '5th',
67
- 'ii',
68
- 'iii',
69
- 'iv',
70
- 'v',
71
- 'vi',
72
- 'vii',
73
- 'viii',
74
- 'ix',
75
- 'x',
76
- 'two',
77
- 'three',
78
- 'four',
79
- 'five',
80
- 'six',
81
- 'seven',
82
- 'eight',
83
- 'nine',
84
- 'ten',
85
- ]);
86
- function distinctiveTokensExcludingSeason(s) {
87
- return [...new Set(tokenize(s).filter((w) => !STOPWORDS.has(w) && !SEASON_TOKENS.has(w)))];
88
- }
89
- const ROMAN_TO_NUM = {
90
- ii: 2,
91
- iii: 3,
92
- iv: 4,
93
- v: 5,
94
- vi: 6,
95
- vii: 7,
96
- viii: 8,
97
- ix: 9,
98
- x: 10,
99
- };
100
- const WORD_TO_NUM = {
101
- two: 2,
102
- three: 3,
103
- four: 4,
104
- five: 5,
105
- six: 6,
106
- seven: 7,
107
- eight: 8,
108
- nine: 9,
109
- ten: 10,
110
- };
111
- function detectSeasonMarker(s) {
112
- const t = s.toLowerCase();
113
- // "season 2", "season ii", "season 2nd", "season three"
114
- const seasonNum = t.match(/\bseason\s*(\d+|ii|iii|iv|v|vi|vii|viii|ix|x|two|three|four|five|six|seven|eight|nine|ten)\b/);
115
- if (seasonNum) {
116
- const token = seasonNum[1];
117
- const n = ROMAN_TO_NUM[token];
118
- const w = WORD_TO_NUM[token];
119
- return String(n ?? w ?? token);
120
- }
121
- const ord = t.match(/\b(\d+)(st|nd|rd|th)\b/);
122
- if (ord)
123
- return ord[1];
124
- const trailing = t.match(/\b(ii|iii|iv|v|vi|vii|viii|ix|x)\b(?!\s*(st|nd|rd|th|season))/);
125
- if (trailing)
126
- return String(ROMAN_TO_NUM[trailing[1]]);
127
- const trailingNum = t.match(/\b(\d+)\s*(season)?\s*$/);
128
- if (trailingNum)
129
- return trailingNum[1];
130
- return '';
131
- }
132
- function distinctiveTokens(s) {
133
- return [...new Set(tokenize(s).filter((w) => !STOPWORDS.has(w)))];
134
- }
135
- function titleSimilarity(query, candidate) {
136
- const q = normalizeCompact(query);
137
- const c = normalizeCompact(candidate);
138
- if (!q || !c)
139
- return 0;
140
- if (q === c)
141
- return 1;
142
- let score = bigramDice(q, c);
143
- if (q.startsWith(c) || c.startsWith(q)) {
144
- const prefixScore = 0.5 + 0.5 * (Math.min(q.length, c.length) / Math.max(q.length, c.length));
145
- if (prefixScore > score)
146
- score = prefixScore;
147
- }
148
- const qD = distinctiveTokensExcludingSeason(query);
149
- const cD = distinctiveTokensExcludingSeason(candidate);
150
- if (qD.length > 0 && cD.length >= 2) {
151
- const cSet = new Set(cD);
152
- let hits = 0;
153
- for (const t of qD) {
154
- if (cSet.has(t))
155
- hits++;
156
- }
157
- const coverage = hits / Math.min(qD.length, cD.length);
158
- if (coverage > score)
159
- score = coverage;
160
- }
161
- const qMarker = detectSeasonMarker(query);
162
- if (qMarker) {
163
- const cMarker = detectSeasonMarker(candidate);
164
- if (cMarker === qMarker) {
165
- score += 0.25;
166
- }
167
- else if (!cMarker && score >= 0.5) {
168
- score -= 0.25;
169
- }
170
- }
171
- return score >= 1 ? 0.99 : score;
172
- }
173
- export function pickBestMatch(candidates, targets, minScore = 0.6) {
174
- if (!candidates.length)
175
- return null;
176
- const cleanTargets = targets.filter((t) => !!t && t.trim().length > 0);
177
- if (!cleanTargets.length)
178
- return null;
179
- let best = null;
180
- for (const item of candidates) {
181
- if (!item.title)
182
- continue;
183
- let score = 0;
184
- for (const target of cleanTargets) {
185
- const s = titleSimilarity(target, item.title);
186
- if (s > score)
187
- score = s;
188
- }
189
- if (!best || score > best.score)
190
- best = { item, score };
191
- }
192
- return best && best.score >= minScore ? best : null;
193
- }
194
- export function buildQueryVariants(title, romaji) {
195
- const seen = new Set();
196
- const out = [];
197
- const push = (q) => {
198
- const v = q.replace(/\s+/g, ' ').trim();
199
- if (v && !seen.has(v.toLowerCase())) {
200
- seen.add(v.toLowerCase());
201
- out.push(v);
202
- }
203
- };
204
- if (romaji && romaji.trim())
205
- push(romaji);
206
- push(title);
207
- if (/^re\s*zero$/i.test(title.trim()))
208
- push('Re Zero');
209
- const stripped = (s) => s.replace(/[^\p{L}\p{N}\s]/gu, ' ');
210
- push(stripped(title));
211
- if (romaji)
212
- push(stripped(romaji));
213
- const words = stripped(title).split(/\s+/).filter(Boolean);
214
- if (words.length > 3)
215
- push(words.slice(0, 3).join(' '));
216
- if (words.length > 4)
217
- push(words.slice(0, 4).join(' '));
218
- const tokens = distinctiveTokens(title)
219
- .concat(romaji ? distinctiveTokens(romaji) : [])
220
- .filter((w) => w.length >= 4);
221
- const unique = [...new Set(tokens)];
222
- unique.sort((a, b) => b.length - a.length);
223
- for (const t of unique.slice(0, 4))
224
- push(t);
225
- return out;
226
- }
1
+ function h(e){return e.toLowerCase().replace(/[^a-z0-9]/g,"")}function u(e){return e.toLowerCase().replace(/[^\p{L}\p{N}\s]/gu," ").split(/\s+/).filter(n=>n.length>=2)}function x(e,n){if(e.length<2||n.length<2)return e===n?1:0;const c=i=>{const g=new Map;for(let l=0;l<i.length-1;l++){const f=i.slice(l,l+2);g.set(f,(g.get(f)||0)+1)}return g},o=c(e),t=c(n);let s=0,r=0,a=0;return o.forEach(i=>s+=i),t.forEach(i=>r+=i),o.forEach((i,g)=>{const l=t.get(g);l&&(a+=Math.min(i,l))}),2*a/(s+r)}const v=new Set(["the","animation","no","wa","ga","wo","ni","de","to","e","o","x","ii","yo","ova","oad","hd","ep","episode","season","part","hen","a"]),S=new Set(["season","2nd","3rd","4th","5th","ii","iii","iv","v","vi","vii","viii","ix","x","two","three","four","five","six","seven","eight","nine","ten"]);function d(e){return[...new Set(u(e).filter(n=>!v.has(n)&&!S.has(n)))]}const m={ii:2,iii:3,iv:4,v:5,vi:6,vii:7,viii:8,ix:9,x:10},b={two:2,three:3,four:4,five:5,six:6,seven:7,eight:8,nine:9,ten:10};function p(e){const n=e.toLowerCase(),c=n.match(/\bseason\s*(\d+|ii|iii|iv|v|vi|vii|viii|ix|x|two|three|four|five|six|seven|eight|nine|ten)\b/);if(c){const r=c[1],a=m[r],i=b[r];return String(a??i??r)}const o=n.match(/\b(\d+)(st|nd|rd|th)\b/);if(o)return o[1];const t=n.match(/\b(ii|iii|iv|v|vi|vii|viii|ix|x)\b(?!\s*(st|nd|rd|th|season))/);if(t)return String(m[t[1]]);const s=n.match(/\b(\d+)\s*(season)?\s*$/);return s?s[1]:""}function w(e){return[...new Set(u(e).filter(n=>!v.has(n)))]}function M(e,n){const c=h(e),o=h(n);if(!c||!o)return 0;if(c===o)return 1;let t=x(c,o);if(c.startsWith(o)||o.startsWith(c)){const i=.5+.5*(Math.min(c.length,o.length)/Math.max(c.length,o.length));i>t&&(t=i)}const s=d(e),r=d(n);if(s.length>0&&r.length>=2){const i=new Set(r);let g=0;for(const f of s)i.has(f)&&g++;const l=g/Math.min(s.length,r.length);l>t&&(t=l)}const a=p(e);if(a){const i=p(n);i===a?t+=.25:!i&&t>=.5&&(t-=.25)}return t>=1?.99:t}function T(e,n,c=.6){if(!e.length)return null;const o=n.filter(s=>!!s&&s.trim().length>0);if(!o.length)return null;let t=null;for(const s of e){if(!s.title)continue;let r=0;for(const a of o){const i=M(a,s.title);i>r&&(r=i)}(!t||r>t.score)&&(t={item:s,score:r})}return t&&t.score>=c?t:null}function k(e,n){const c=new Set,o=[],t=g=>{const l=g.replace(/\s+/g," ").trim();l&&!c.has(l.toLowerCase())&&(c.add(l.toLowerCase()),o.push(l))};n&&n.trim()&&t(n),t(e),/^re\s*zero$/i.test(e.trim())&&t("Re Zero");const s=g=>g.replace(/[^\p{L}\p{N}\s]/gu," ");t(s(e)),n&&t(s(n));const r=s(e).split(/\s+/).filter(Boolean);r.length>3&&t(r.slice(0,3).join(" ")),r.length>4&&t(r.slice(0,4).join(" "));const a=w(e).concat(n?w(n):[]).filter(g=>g.length>=4),i=[...new Set(a)];i.sort((g,l)=>l.length-g.length);for(const g of i.slice(0,4))t(g);return o}export{k as buildQueryVariants,T as pickBestMatch};
@@ -1 +0,0 @@
1
- export {};
@@ -1,120 +1,2 @@
1
- import { spawn, execFile } from 'child_process';
2
- import logger from './logger.js';
3
- import { CONFIG } from './config.js';
4
- class RcloneService {
5
- activeRemote = null;
6
- executeCommand(args) {
7
- return new Promise((resolve, reject) => {
8
- execFile('rclone', args, (err, stdout, stderr) => {
9
- if (err) {
10
- if (stderr)
11
- logger.warn({ stderr }, 'Rclone command warning');
12
- return reject(new Error(stderr || err.message));
13
- }
14
- resolve(stdout.trim());
15
- });
16
- });
17
- }
18
- executeRcloneArgs(args) {
19
- return new Promise((resolve, reject) => {
20
- const process = spawn('rclone', args, { stdio: 'ignore' });
21
- process.on('close', (code) => {
22
- if (code === 0)
23
- resolve();
24
- else
25
- reject(new Error(`Rclone exited with code ${code}`));
26
- });
27
- process.on('error', (err) => reject(err));
28
- });
29
- }
30
- async listRemotes() {
31
- try {
32
- const remotesStr = await this.executeCommand(['listremotes']);
33
- return remotesStr
34
- .split('\n')
35
- .map((r) => r.trim())
36
- .filter((r) => r !== '')
37
- .map((r) => r.replace(/:$/, ''));
38
- }
39
- catch {
40
- return [];
41
- }
42
- }
43
- async init() {
44
- try {
45
- await this.executeCommand(['version']);
46
- const remotes = await this.listRemotes();
47
- if (CONFIG.RCLONE_REMOTE) {
48
- const found = remotes.find((r) => r.toLowerCase() === CONFIG.RCLONE_REMOTE?.toLowerCase());
49
- if (found) {
50
- this.activeRemote = found;
51
- logger.info(`Rclone initialized with manual remote: ${this.activeRemote}`);
52
- return true;
53
- }
54
- else {
55
- logger.warn(`Configured RCLONE_REMOTE '${CONFIG.RCLONE_REMOTE}' not found in rclone listremotes.`);
56
- }
57
- }
58
- if (remotes.length > 0 && !CONFIG.RCLONE_REMOTE) {
59
- logger.info({ remotes }, 'Rclone available but no manual remote is configured in settings.');
60
- return false;
61
- }
62
- return false;
63
- }
64
- catch (error) {
65
- logger.warn({ err: error }, 'Rclone initialization failed');
66
- return false;
67
- }
68
- }
69
- isActive() {
70
- return this.activeRemote !== null;
71
- }
72
- getRemoteName() {
73
- return this.activeRemote || 'unknown';
74
- }
75
- async downloadFile(remoteFolder, fileName, localPath) {
76
- if (!this.activeRemote)
77
- throw new Error('Rclone not active');
78
- const remotePath = `${this.activeRemote}:${remoteFolder}/${fileName}`;
79
- await this.executeRcloneArgs(['copyto', remotePath, localPath]);
80
- }
81
- async uploadFile(localPath, remoteFolder, fileName) {
82
- if (!this.activeRemote)
83
- throw new Error('Rclone not active');
84
- const remotePath = `${this.activeRemote}:${remoteFolder}/${fileName}`;
85
- await this.executeRcloneArgs(['copyto', localPath, remotePath]);
86
- }
87
- executeRcloneArgsWithOutput(args) {
88
- return new Promise((resolve, reject) => {
89
- const process = spawn('rclone', args, { stdio: 'pipe' });
90
- let stdout = '';
91
- let stderr = '';
92
- process.stdout?.on('data', (data) => (stdout += data));
93
- process.stderr?.on('data', (data) => (stderr += data));
94
- process.on('close', (code) => {
95
- if (code === 0)
96
- resolve(stdout.trim());
97
- else {
98
- if (stderr)
99
- logger.warn({ stderr }, 'Rclone command warning');
100
- reject(new Error(stderr || `Rclone exited with code ${code}`));
101
- }
102
- });
103
- process.on('error', (err) => reject(err));
104
- });
105
- }
106
- async fileExists(remoteFolder, fileName) {
107
- if (!this.activeRemote)
108
- return false;
109
- try {
110
- const remotePath = `${this.activeRemote}:${remoteFolder}/${fileName}`;
111
- const output = await this.executeRcloneArgsWithOutput(['lsjson', remotePath]);
112
- const json = JSON.parse(output);
113
- return json && json.length > 0;
114
- }
115
- catch {
116
- return false;
117
- }
118
- }
119
- }
120
- export const rcloneService = new RcloneService();
1
+ import{spawn as l,execFile as m}from"child_process";import a from"./logger.js";import{CONFIG as c}from"./config.js";class u{activeRemote=null;executeCommand(t){return new Promise((e,r)=>{m("rclone",t,(i,o,n)=>{if(i)return n&&a.warn({stderr:n},"Rclone command warning"),r(new Error(n||i.message));e(o.trim())})})}executeRcloneArgs(t){return new Promise((e,r)=>{const i=l("rclone",t,{stdio:"ignore"});i.on("close",o=>{o===0?e():r(new Error(`Rclone exited with code ${o}`))}),i.on("error",o=>r(o))})}async listRemotes(){try{return(await this.executeCommand(["listremotes"])).split(`
2
+ `).map(e=>e.trim()).filter(e=>e!=="").map(e=>e.replace(/:$/,""))}catch{return[]}}async init(){try{await this.executeCommand(["version"]);const t=await this.listRemotes();if(c.RCLONE_REMOTE){const e=t.find(r=>r.toLowerCase()===c.RCLONE_REMOTE?.toLowerCase());if(e)return this.activeRemote=e,a.info(`Rclone initialized with manual remote: ${this.activeRemote}`),!0;a.warn(`Configured RCLONE_REMOTE '${c.RCLONE_REMOTE}' not found in rclone listremotes.`)}return t.length>0&&!c.RCLONE_REMOTE&&a.info({remotes:t},"Rclone available but no manual remote is configured in settings."),!1}catch(t){return a.warn({err:t},"Rclone initialization failed"),!1}}isActive(){return this.activeRemote!==null}getRemoteName(){return this.activeRemote||"unknown"}async downloadFile(t,e,r){if(!this.activeRemote)throw new Error("Rclone not active");const i=`${this.activeRemote}:${t}/${e}`;await this.executeRcloneArgs(["copyto",i,r])}async uploadFile(t,e,r){if(!this.activeRemote)throw new Error("Rclone not active");const i=`${this.activeRemote}:${e}/${r}`;await this.executeRcloneArgs(["copyto",t,i])}executeRcloneArgsWithOutput(t){return new Promise((e,r)=>{const i=l("rclone",t,{stdio:"pipe"});let o="",n="";i.stdout?.on("data",s=>o+=s),i.stderr?.on("data",s=>n+=s),i.on("close",s=>{s===0?e(o.trim()):(n&&a.warn({stderr:n},"Rclone command warning"),r(new Error(n||`Rclone exited with code ${s}`)))}),i.on("error",s=>r(s))})}async fileExists(t,e){if(!this.activeRemote)return!1;try{const r=`${this.activeRemote}:${t}/${e}`,i=await this.executeRcloneArgsWithOutput(["lsjson",r]),o=JSON.parse(i);return o&&o.length>0}catch{return!1}}}const w=new u;export{w as rcloneService};
@@ -1,42 +1,19 @@
1
- import { eq, inArray, sql } from 'drizzle-orm';
2
- import { getDrizzle } from '../db/drizzle.js';
3
- import { asmrProgress } from '../db/schema-asmr.js';
4
- import { makeLibraryRepo } from './library.repository.js';
5
- export const ASMR_STATUSES = ['Listening', 'Completed', 'On-Hold', 'Dropped', 'Planned'];
6
- export function buildAsmrId(rjCode) {
7
- return String(rjCode).trim().toUpperCase();
8
- }
9
- export const AsmrLibraryRepository = {
10
- ...makeLibraryRepo('asmr_library'),
11
- upsert: (db, data) => getDrizzle(db).run(sql `
1
+ import{eq as i,inArray as l,sql as a}from"drizzle-orm";import{getDrizzle as s}from"../db/drizzle.js";import{asmrProgress as n}from"../db/schema-asmr.js";import{makeLibraryRepo as u}from"./library.repository.js";const C=["Listening","Completed","On-Hold","Dropped","Planned"];function A(t){return String(t).trim().toUpperCase()}const g={...u("asmr_library"),upsert:(t,r)=>s(t).run(a`
12
2
  INSERT INTO asmr_library (id, rjCode, title, thumbnail, status, isAdult, updatedAt)
13
- VALUES (${data.id}, ${data.rjCode}, ${data.title}, ${data.thumbnail}, ${data.status}, ${data.isAdult ? 1 : 0}, strftime('%s', 'now'))
3
+ VALUES (${r.id}, ${r.rjCode}, ${r.title}, ${r.thumbnail}, ${r.status}, ${r.isAdult?1:0}, strftime('%s', 'now'))
14
4
  ON CONFLICT(id) DO UPDATE SET
15
5
  rjCode = COALESCE(NULLIF(EXCLUDED.rjCode, ''), asmr_library.rjCode),
16
6
  title = COALESCE(NULLIF(EXCLUDED.title, ''), asmr_library.title),
17
7
  thumbnail = COALESCE(NULLIF(EXCLUDED.thumbnail, ''), asmr_library.thumbnail),
18
8
  status = COALESCE(NULLIF(EXCLUDED.status, ''), asmr_library.status),
19
9
  isAdult = COALESCE(EXCLUDED.isAdult, asmr_library.isAdult),
20
- updatedAt = strftime('%s', 'now')`),
21
- touchProgress: (db, id, progress) => getDrizzle(db).run(sql `
22
- UPDATE asmr_library SET lastTrackIndex = ${progress.trackIndex}, lastTrackLabel = ${progress.trackLabel}, lastPosition = ${progress.position}, updatedAt = strftime('%s', 'now') WHERE id = ${id}`),
23
- };
24
- export const AsmrProgressRepository = {
25
- getByWork: (db, workId) => getDrizzle(db).all(sql `
26
- SELECT workId, trackIndex, trackLabel, currentTime, duration, updatedAt FROM asmr_progress WHERE workId = ${workId} ORDER BY updatedAt DESC`),
27
- getTrack: async (db, workId, trackIndex) => {
28
- const rows = await getDrizzle(db).all(sql `
29
- SELECT workId, trackIndex, trackLabel, currentTime, duration, updatedAt FROM asmr_progress WHERE workId = ${workId} AND trackIndex = ${trackIndex}`);
30
- return rows[0];
31
- },
32
- getLatest: async (db, workId) => {
33
- const rows = await getDrizzle(db).all(sql `
34
- SELECT workId, trackIndex, trackLabel, currentTime, duration, updatedAt FROM asmr_progress WHERE workId = ${workId} ORDER BY updatedAt DESC LIMIT 1`);
35
- return rows[0];
36
- },
37
- upsert: (db, data) => getDrizzle(db).run(sql `
10
+ updatedAt = strftime('%s', 'now')`),touchProgress:(t,r,e)=>s(t).run(a`
11
+ UPDATE asmr_library SET lastTrackIndex = ${e.trackIndex}, lastTrackLabel = ${e.trackLabel}, lastPosition = ${e.position}, updatedAt = strftime('%s', 'now') WHERE id = ${r}`)},c={getByWork:(t,r)=>s(t).all(a`
12
+ SELECT workId, trackIndex, trackLabel, currentTime, duration, updatedAt FROM asmr_progress WHERE workId = ${r} ORDER BY updatedAt DESC`),getTrack:async(t,r,e)=>(await s(t).all(a`
13
+ SELECT workId, trackIndex, trackLabel, currentTime, duration, updatedAt FROM asmr_progress WHERE workId = ${r} AND trackIndex = ${e}`))[0],getLatest:async(t,r)=>(await s(t).all(a`
14
+ SELECT workId, trackIndex, trackLabel, currentTime, duration, updatedAt FROM asmr_progress WHERE workId = ${r} ORDER BY updatedAt DESC LIMIT 1`))[0],upsert:(t,r)=>s(t).run(a`
38
15
  INSERT INTO asmr_progress (workId, trackIndex, trackLabel, currentTime, duration, title, thumbnail, rjCode, isAdult, updatedAt)
39
- VALUES (${data.workId}, ${data.trackIndex}, ${data.trackLabel}, ${data.currentTime}, ${data.duration}, ${data.title ?? null}, ${data.thumbnail ?? null}, ${data.rjCode ?? null}, ${data.isAdult ?? null}, strftime('%s', 'now'))
16
+ VALUES (${r.workId}, ${r.trackIndex}, ${r.trackLabel}, ${r.currentTime}, ${r.duration}, ${r.title??null}, ${r.thumbnail??null}, ${r.rjCode??null}, ${r.isAdult??null}, strftime('%s', 'now'))
40
17
  ON CONFLICT(workId, trackIndex) DO UPDATE SET
41
18
  trackLabel = COALESCE(NULLIF(EXCLUDED.trackLabel, ''), asmr_progress.trackLabel),
42
19
  currentTime = EXCLUDED.currentTime,
@@ -45,18 +22,8 @@ export const AsmrProgressRepository = {
45
22
  thumbnail = COALESCE(EXCLUDED.thumbnail, asmr_progress.thumbnail),
46
23
  rjCode = COALESCE(EXCLUDED.rjCode, asmr_progress.rjCode),
47
24
  isAdult = COALESCE(EXCLUDED.isAdult, asmr_progress.isAdult),
48
- updatedAt = strftime('%s', 'now')`),
49
- deleteByWork: (db, workId) => getDrizzle(db).delete(asmrProgress).where(eq(asmrProgress.workId, workId)),
50
- deleteMany: (db, ids) => {
51
- if (ids.length === 0)
52
- return Promise.resolve();
53
- return getDrizzle(db).delete(asmrProgress).where(inArray(asmrProgress.workId, ids));
54
- },
55
- deleteTrack: (db, workId, trackIndex) => getDrizzle(db).run(sql `
56
- DELETE FROM asmr_progress WHERE workId = ${workId} AND trackIndex = ${trackIndex}`),
57
- getContinueListening: (db, limit) => {
58
- const limitClause = typeof limit === 'number' ? `LIMIT ${limit}` : '';
59
- return getDrizzle(db).all(sql `
25
+ updatedAt = strftime('%s', 'now')`),deleteByWork:(t,r)=>s(t).delete(n).where(i(n.workId,r)),deleteMany:(t,r)=>r.length===0?Promise.resolve():s(t).delete(n).where(l(n.workId,r)),deleteTrack:(t,r,e)=>s(t).run(a`
26
+ DELETE FROM asmr_progress WHERE workId = ${r} AND trackIndex = ${e}`),getContinueListening:(t,r)=>{const e=typeof r=="number"?`LIMIT ${r}`:"";return s(t).all(a`
60
27
  SELECT p.workId as id,
61
28
  COALESCE(l.rjCode, p.rjCode, p.workId) as rjCode,
62
29
  COALESCE(l.title, p.title) as title,
@@ -72,6 +39,4 @@ export const AsmrProgressRepository = {
72
39
  WHERE p.rn = 1
73
40
  AND (l.status IS NULL OR l.status = 'Listening')
74
41
  ORDER BY p.updatedAt DESC
75
- ${sql.raw(limitClause)}`);
76
- },
77
- };
42
+ ${a.raw(e)}`)}};export{C as ASMR_STATUSES,g as AsmrLibraryRepository,c as AsmrProgressRepository,A as buildAsmrId};