@serifpersia/dango 3.3.9 → 3.4.1

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 (180) hide show
  1. package/README.md +1 -1
  2. package/client/dist/assets/{AnimeCard-URCiiy5W.js → AnimeCard-B0oMgXBm.js} +1 -1
  3. package/client/dist/assets/{AnimeInfo-e3z3er9t.js → AnimeInfo-o0Udf0og.js} +1 -1
  4. package/client/dist/assets/{AnimeInfoPage-B7Djlmy_.js → AnimeInfoPage-CojMIEuf.js} +2 -2
  5. package/client/dist/assets/Asmr-2V3yCCiK.js +2 -0
  6. package/client/dist/assets/AsmrResetProgressModal-C9R7HlNa.js +1 -0
  7. package/client/dist/assets/AvSyncCalibrator-Bo3820j3.js +10 -0
  8. package/client/dist/assets/Home-C8uXTzIM.js +1 -0
  9. package/client/dist/assets/Insights-CRqBfZuK.js +1 -0
  10. package/client/dist/assets/{LibraryListPage-B0qWqhb_.js → LibraryListPage-B_r51cRi.js} +1 -1
  11. package/client/dist/assets/{ListeningList-CSqD8j1C.js → ListeningList-BWnwvJTt.js} +1 -1
  12. package/client/dist/assets/{Manga-Cg2YuObF.js → Manga-CI5cZg3h.js} +1 -1
  13. package/client/dist/assets/{MangaInfoPage-DcArhEsy.js → MangaInfoPage-BtkOrO34.js} +1 -1
  14. package/client/dist/assets/MangaReadPage-Uv72AYdY.js +1 -0
  15. package/client/dist/assets/{MangaResetProgressModal-D_yT3d1v.js → MangaResetProgressModal-CPMEqZyi.js} +1 -1
  16. package/client/dist/assets/Mature-DXHWvoY5.js +1 -0
  17. package/client/dist/assets/MediaCard-DzA336CA.js +1 -0
  18. package/client/dist/assets/Music-C4dM0BQx.js +1 -0
  19. package/client/dist/assets/OptionListMenu-CpwxAC3J.js +1 -0
  20. package/client/dist/assets/Player-CpMvWTkH.js +56 -0
  21. package/client/dist/assets/PlayerSettings-pMU85k7p.js +1 -0
  22. package/client/dist/assets/QueueOptionsButton-1M7MdKzM.js +1 -0
  23. package/client/dist/assets/{QueueRail-B7f1-e6i.js → QueueRail-Bpla2xD8.js} +1 -1
  24. package/client/dist/assets/Radio-DlmTC-qG.js +1 -0
  25. package/client/dist/assets/{ReadingList-BaIem8H1.js → ReadingList-wJMDUybm.js} +1 -1
  26. package/client/dist/assets/{ResetProgressModal-DES5Y-Fl.js → ResetProgressModal-CByRM1Fh.js} +1 -1
  27. package/client/dist/assets/Search-B94D0tRM.js +1 -0
  28. package/client/dist/assets/Settings-CcU3oNY8.js +21 -0
  29. package/client/dist/assets/{SkeletonGrid-BxfgjP4i.js → SkeletonGrid-BJyZkg_K.js} +1 -1
  30. package/client/dist/assets/Trackers-vz-gqxtm.js +2 -0
  31. package/client/dist/assets/Tv-Rji_lvQo.js +4 -0
  32. package/client/dist/assets/Tv-ZEA45gXK.css +1 -0
  33. package/client/dist/assets/TvInfo-Ciujd6os.js +1 -0
  34. package/client/dist/assets/TvPopup-CqlVRF-Y.js +1 -0
  35. package/client/dist/assets/{TvSearch-BWDwWnHe.js → TvSearch-BcKXQue9.js} +1 -1
  36. package/client/dist/assets/{TvWatchlist-D3n5_4MI.js → TvWatchlist-sjsiup2h.js} +1 -1
  37. package/client/dist/assets/Watchlist-DAMdvt7Z.js +1 -0
  38. package/client/dist/assets/{hls-BO_L1WoN.js → hls-Do_n10_Y.js} +1 -1
  39. package/client/dist/assets/index-B5SLDCAi.js +178 -0
  40. package/client/dist/assets/useAnimeInfoData-DU9X90no.js +1 -0
  41. package/client/dist/assets/useAsmr-D6cSjbvt.js +1 -0
  42. package/client/dist/assets/{useAsmrPopup-D_o9-QlR.js → useAsmrPopup-DK1Vx-HV.js} +1 -1
  43. package/client/dist/assets/useMangaLibrary-DcxPn0RM.js +1 -0
  44. package/client/dist/assets/{useMangaPopup-TFzkC-tG.js → useMangaPopup-BLwvRZn0.js} +1 -1
  45. package/client/dist/assets/useProviders-D3L1Chqc.js +1 -0
  46. package/client/dist/assets/useTv-B546u-Rx.js +1 -0
  47. package/client/dist/assets/useTvLibrary-C2rPeCVT.js +1 -0
  48. package/client/dist/assets/{vendor-query-BXRECSI6.js → vendor-query-BUTye89G.js} +1 -1
  49. package/client/dist/index.html +2 -2
  50. package/orchestrator.js +1 -2
  51. package/package.json +6 -6
  52. package/server/dist/app-auth.js +9 -29
  53. package/server/dist/app-hono.js +621 -0
  54. package/server/dist/config.js +0 -1
  55. package/server/dist/db/drizzle.js +32 -0
  56. package/server/dist/db/schema-anime.js +64 -0
  57. package/server/dist/db/schema-asmr.js +25 -0
  58. package/server/dist/db/schema-manga.js +31 -0
  59. package/server/dist/db/schema-tv.js +33 -0
  60. package/server/dist/db.js +75 -4
  61. package/server/dist/discord-gateway.js +1 -1
  62. package/server/dist/google.js +0 -7
  63. package/server/dist/hono/asmr.js +1 -0
  64. package/server/dist/hono/auth.js +56 -0
  65. package/server/dist/hono/music.js +419 -0
  66. package/server/dist/{controllers/proxy.controller.js → hono/proxy.js} +390 -394
  67. package/server/dist/hono/tv.js +197 -0
  68. package/server/dist/hono/watchlist.js +733 -0
  69. package/server/dist/lib/anilist.js +3 -3
  70. package/server/dist/lib/insights.js +185 -0
  71. package/server/dist/lib/ipc.js +1 -2
  72. package/server/dist/lib/kitsu.js +1 -28
  73. package/server/dist/lib/mal-import.js +291 -0
  74. package/server/dist/lib/migration.js +20 -34
  75. package/server/dist/lib/offline-db.js +36 -36
  76. package/server/dist/lib/tracker/manga-sync.service.js +21 -21
  77. package/server/dist/lib/tracker/sync.service.js +20 -20
  78. package/server/dist/lib/translate.js +79 -0
  79. package/server/dist/lib/ytmusic.js +0 -2
  80. package/server/dist/providers/title-matching.js +0 -1
  81. package/server/dist/repositories/asmr.repository.js +90 -87
  82. package/server/dist/repositories/insights.repository.js +24 -13
  83. package/server/dist/repositories/library.repository.js +33 -93
  84. package/server/dist/repositories/manga.repository.js +108 -116
  85. package/server/dist/repositories/notifications.repository.js +50 -31
  86. package/server/dist/repositories/queue.repository.js +36 -25
  87. package/server/dist/repositories/settings.repository.js +18 -8
  88. package/server/dist/repositories/shows-meta.repository.js +54 -41
  89. package/server/dist/repositories/temp-show-ids.repository.js +43 -31
  90. package/server/dist/repositories/tv.repository.js +98 -115
  91. package/server/dist/repositories/watched-episodes.repository.js +52 -20
  92. package/server/dist/repositories/watchlist.repository.js +36 -48
  93. package/server/dist/server.js +59 -151
  94. package/server/dist/sync.js +41 -4
  95. package/server/dist/trpc/context.js +7 -0
  96. package/server/dist/trpc/errors.js +21 -0
  97. package/server/dist/trpc/index.js +19 -0
  98. package/server/dist/trpc/router.js +49 -0
  99. package/server/dist/trpc/routers/asmr-library.js +122 -0
  100. package/server/dist/trpc/routers/asmr-progress.js +133 -0
  101. package/server/dist/trpc/routers/asmr.js +87 -0
  102. package/server/dist/trpc/routers/auth.js +247 -0
  103. package/server/dist/trpc/routers/continue-watching.js +83 -0
  104. package/server/dist/trpc/routers/data.js +1039 -0
  105. package/server/dist/trpc/routers/discord.js +288 -0
  106. package/server/dist/trpc/routers/discovery.js +15 -0
  107. package/server/dist/trpc/routers/insights.js +45 -0
  108. package/server/dist/trpc/routers/manga-library.js +280 -0
  109. package/server/dist/trpc/routers/manga-progress.js +181 -0
  110. package/server/dist/trpc/routers/manga.js +201 -0
  111. package/server/dist/{controllers/music.controller.js → trpc/routers/music.js} +155 -288
  112. package/server/dist/trpc/routers/notifications.js +80 -0
  113. package/server/dist/trpc/routers/progress.js +15 -0
  114. package/server/dist/trpc/routers/queue.js +95 -0
  115. package/server/dist/{routes/radio.routes.js → trpc/routers/radio.js} +30 -23
  116. package/server/dist/trpc/routers/settings.js +105 -0
  117. package/server/dist/trpc/routers/tracker.js +219 -0
  118. package/server/dist/trpc/routers/tv-library.js +145 -0
  119. package/server/dist/trpc/routers/tv-progress.js +171 -0
  120. package/server/dist/trpc/routers/tv.js +565 -0
  121. package/server/dist/trpc/routers/watchlist.js +564 -0
  122. package/server/dist/trpc/validation.js +159 -0
  123. package/server/dist/utils/security.utils.js +0 -18
  124. package/server/package.json +5 -10
  125. package/client/dist/assets/Asmr-C28TfSvX.js +0 -2
  126. package/client/dist/assets/AsmrResetProgressModal-6Xh71AgR.js +0 -1
  127. package/client/dist/assets/AvSyncCalibrator-BwCtRW0P.js +0 -10
  128. package/client/dist/assets/Home-BJdbHYja.js +0 -1
  129. package/client/dist/assets/Insights-CjRy42YE.js +0 -1
  130. package/client/dist/assets/MangaReadPage-YCETZRte.js +0 -1
  131. package/client/dist/assets/Mature-BGxRWkV5.js +0 -1
  132. package/client/dist/assets/MediaCard-CAzoxsRS.js +0 -1
  133. package/client/dist/assets/Music-Cgo-yQ7E.js +0 -1
  134. package/client/dist/assets/OptionListMenu--Owe9Jle.js +0 -1
  135. package/client/dist/assets/Player-yRXG2BrP.js +0 -56
  136. package/client/dist/assets/PlayerSettings-DrZLa0CC.js +0 -1
  137. package/client/dist/assets/QueueOptionsButton-BVZyPyKf.js +0 -1
  138. package/client/dist/assets/Radio-BFjxlzuF.js +0 -1
  139. package/client/dist/assets/Search-BOBo0Ogh.js +0 -1
  140. package/client/dist/assets/Settings-DOvNQ0gX.js +0 -21
  141. package/client/dist/assets/Trackers-DZ9KqOWv.js +0 -2
  142. package/client/dist/assets/Tv-5mATEU4p.js +0 -4
  143. package/client/dist/assets/Tv-C2SWcVzN.css +0 -1
  144. package/client/dist/assets/TvInfo-CF-5C5Fs.js +0 -1
  145. package/client/dist/assets/TvPopup-Dfqifj3s.js +0 -1
  146. package/client/dist/assets/Watchlist-B8MHNsAU.js +0 -1
  147. package/client/dist/assets/index-D2RkUJIN.js +0 -178
  148. package/client/dist/assets/useAnimeInfoData-BWShDHqE.js +0 -1
  149. package/client/dist/assets/useAsmr-FyfD9G7b.js +0 -1
  150. package/client/dist/assets/useMangaLibrary-BJU5x8Vz.js +0 -1
  151. package/client/dist/assets/useProviders-DDRxa8vl.js +0 -1
  152. package/client/dist/assets/useTv-Bh6WmNnZ.js +0 -1
  153. package/client/dist/assets/useTvLibrary-_Pqm8re1.js +0 -1
  154. package/server/dist/controllers/asmr-library.controller.js +0 -288
  155. package/server/dist/controllers/auth.controller.js +0 -225
  156. package/server/dist/controllers/data.controller.js +0 -767
  157. package/server/dist/controllers/insights.controller.js +0 -203
  158. package/server/dist/controllers/manga-library.controller.js +0 -350
  159. package/server/dist/controllers/settings.controller.js +0 -609
  160. package/server/dist/controllers/tv-library.controller.js +0 -314
  161. package/server/dist/controllers/watchlist.controller.js +0 -1069
  162. package/server/dist/request-logger.js +0 -16
  163. package/server/dist/routes/asmr-library.routes.js +0 -23
  164. package/server/dist/routes/asmr.routes.js +0 -81
  165. package/server/dist/routes/auth.routes.js +0 -25
  166. package/server/dist/routes/data.routes.js +0 -47
  167. package/server/dist/routes/discord-gateway.routes.js +0 -51
  168. package/server/dist/routes/insights.routes.js +0 -35
  169. package/server/dist/routes/lan-auth.routes.js +0 -46
  170. package/server/dist/routes/manga-library.routes.js +0 -25
  171. package/server/dist/routes/manga.routes.js +0 -141
  172. package/server/dist/routes/music.routes.js +0 -20
  173. package/server/dist/routes/providers.routes.js +0 -17
  174. package/server/dist/routes/proxy.routes.js +0 -12
  175. package/server/dist/routes/settings.routes.js +0 -24
  176. package/server/dist/routes/tracker.routes.js +0 -287
  177. package/server/dist/routes/translate.routes.js +0 -88
  178. package/server/dist/routes/tv-library.routes.js +0 -23
  179. package/server/dist/routes/tv.routes.js +0 -629
  180. package/server/dist/routes/watchlist.routes.js +0 -341
@@ -1,287 +0,0 @@
1
- import { Router } from 'express';
2
- import multer from 'multer';
3
- import { XMLParser } from 'fast-xml-parser';
4
- import { AniListTracker } from '../lib/tracker/anilist-tracker.js';
5
- import { syncAniList, importFromMalUsername, importFromUsername, } from '../lib/tracker/sync.service.js';
6
- import { syncAniListManga, importFromUsernameManga, importMangaFromMalUsername, importMangaFromMalXmlItems, } from '../lib/tracker/manga-sync.service.js';
7
- import { SettingsRepository } from '../repositories/settings.repository.js';
8
- import { performWriteTransaction } from '../sync.js';
9
- const TOKEN_KEY = 'tracker_anilist_token';
10
- const USER_KEY = 'tracker_anilist_user';
11
- const CLIENT_ID_KEY = 'tracker_anilist_client_id';
12
- const malMangaXmlParser = new XMLParser({
13
- isArray: (name) => name === 'manga',
14
- parseTagValue: false,
15
- trimValues: true,
16
- });
17
- function mangaXmlText(value) {
18
- if (Array.isArray(value))
19
- return String(value[0] ?? '');
20
- return String(value ?? '');
21
- }
22
- function mapMalMangaXmlStatus(status) {
23
- switch (status) {
24
- case 'Plan to Read':
25
- return 'Planned';
26
- case 'On Hold':
27
- return 'On-Hold';
28
- case 'Reading':
29
- case 'Completed':
30
- case 'Dropped':
31
- return status;
32
- default:
33
- return 'Planned';
34
- }
35
- }
36
- export function createTrackerRouter() {
37
- const router = Router();
38
- router.get('/tracker/anilist/callback', (_req, res) => {
39
- res.type('html').send(`<!doctype html>
40
- <html><head><meta charset="utf-8"><title>AniList — completing login</title></head>
41
- <body style="font-family:system-ui;padding:24px;background:#0b1426;color:#e5eefc"><p>Completing AniList login…</p>
42
- <script>
43
- (function(){
44
- var qs = new URLSearchParams(location.search);
45
- var state = qs.get('state');
46
- var hash = location.hash || '';
47
- var hp = new URLSearchParams(hash.slice(1));
48
- if (!state) state = hp.get('state');
49
- var token = hp.get('access_token');
50
- var error = hp.get('error') || qs.get('error');
51
- var frontend = location.origin + '/trackers';
52
- if (state) {
53
- try {
54
- var candidate = decodeURIComponent(state);
55
- if (candidate.startsWith('/') && !candidate.startsWith('//')) {
56
- frontend = location.origin + candidate;
57
- } else {
58
- var parsed = new URL(candidate, location.origin);
59
- if (parsed.origin === location.origin) {
60
- frontend = parsed.href;
61
- }
62
- }
63
- } catch(e) {}
64
- }
65
- if (frontend.indexOf('/api/tracker/anilist/callback') !== -1) {
66
- frontend = location.origin + '/trackers';
67
- }
68
- if (error) {
69
- location.replace(frontend + (frontend.indexOf('?') !== -1 ? '&' : '?') + 'anilist=error&reason=' + encodeURIComponent(error));
70
- return;
71
- }
72
- if (token) {
73
- location.replace(frontend + hash);
74
- return;
75
- }
76
- var code = qs.get('code');
77
- if (code) {
78
- location.replace(frontend + (frontend.indexOf('?') !== -1 ? '&' : '?') + 'anilist=error&reason=code_flow_removed');
79
- return;
80
- }
81
- location.replace(frontend + (frontend.indexOf('?') !== -1 ? '&' : '?') + 'anilist=error&reason=no_token');
82
- })();
83
- </script></body></html>`);
84
- });
85
- router.get('/tracker/status', async (req, res) => {
86
- try {
87
- const tokenRow = await SettingsRepository.getByKey(req.db, TOKEN_KEY);
88
- const userRow = await SettingsRepository.getByKey(req.db, USER_KEY);
89
- let user = null;
90
- if (userRow?.value) {
91
- try {
92
- user = JSON.parse(userRow.value);
93
- }
94
- catch {
95
- user = null;
96
- }
97
- }
98
- res.json({
99
- anilist: { connected: !!tokenRow?.value, user },
100
- });
101
- }
102
- catch {
103
- res.status(500).json({ error: 'Failed to read tracker status' });
104
- }
105
- });
106
- router.post('/tracker/anilist/auth', async (req, res) => {
107
- const { token } = req.body ?? {};
108
- const accessToken = typeof token === 'string' ? token.trim() : '';
109
- if (!accessToken) {
110
- return res.status(400).json({ error: 'Access token is required' });
111
- }
112
- try {
113
- const tracker = new AniListTracker(accessToken);
114
- const viewer = await tracker.getViewer();
115
- await performWriteTransaction(req.db, (tx) => {
116
- SettingsRepository.upsert(tx, TOKEN_KEY, accessToken);
117
- SettingsRepository.upsert(tx, USER_KEY, JSON.stringify(viewer));
118
- });
119
- res.json({ success: true, user: viewer });
120
- }
121
- catch (err) {
122
- const message = err instanceof Error ? err.message : 'Authentication failed';
123
- res.status(401).json({ error: message });
124
- }
125
- });
126
- router.post('/tracker/anilist/disconnect', async (req, res) => {
127
- try {
128
- await performWriteTransaction(req.db, (tx) => {
129
- SettingsRepository.upsert(tx, TOKEN_KEY, '');
130
- SettingsRepository.upsert(tx, USER_KEY, '');
131
- });
132
- res.json({ success: true });
133
- }
134
- catch {
135
- res.status(500).json({ error: 'Failed to disconnect' });
136
- }
137
- });
138
- router.post('/tracker/sync', async (req, res) => {
139
- const { provider = 'anilist' } = req.body ?? {};
140
- if (provider !== 'anilist') {
141
- return res.status(400).json({ error: `Provider "${provider}" is not supported yet` });
142
- }
143
- try {
144
- const summary = await syncAniList(req.db);
145
- res.json({ success: true, summary });
146
- }
147
- catch (err) {
148
- const message = err instanceof Error ? err.message : 'Sync failed';
149
- res.status(500).json({ error: message });
150
- }
151
- });
152
- router.post('/tracker/anilist/import', async (req, res) => {
153
- const { username, erase } = req.body ?? {};
154
- if (!username || typeof username !== 'string') {
155
- return res.status(400).json({ error: 'Username is required' });
156
- }
157
- try {
158
- const count = await importFromUsername(req.db, username.trim(), erase === true);
159
- res.json({ success: true, count });
160
- }
161
- catch (err) {
162
- const message = err instanceof Error ? err.message : 'Import failed';
163
- res.status(500).json({ error: message });
164
- }
165
- });
166
- router.post('/tracker/mal/import', async (req, res) => {
167
- const { username, erase, useOfflineDb, skipFallback } = req.body ?? {};
168
- if (!username || typeof username !== 'string') {
169
- return res.status(400).json({ error: 'MAL username is required' });
170
- }
171
- try {
172
- const count = await importFromMalUsername(req.db, username.trim(), {
173
- erase: erase === true,
174
- useOfflineDb: useOfflineDb !== false,
175
- skipFallback: skipFallback === true,
176
- });
177
- res.json({ success: true, count });
178
- }
179
- catch (err) {
180
- const message = err instanceof Error ? err.message : 'Import failed';
181
- if (message.includes('private') || message.includes('not found')) {
182
- return res.status(404).json({ error: message });
183
- }
184
- if (message.includes('blocked') || message.includes('HTTP 429')) {
185
- return res.status(429).json({ error: message });
186
- }
187
- res.status(500).json({ error: message });
188
- }
189
- });
190
- router.post('/tracker/manga/sync', async (req, res) => {
191
- const { direction } = req.body ?? {};
192
- try {
193
- if (!req.mangaDb || req.mangaDb.isClosedCheck()) {
194
- return res.status(503).json({ error: 'Manga database is not ready' });
195
- }
196
- const syncDirection = direction === 'pull-only' ? 'pull-only' : 'two-way';
197
- const result = await syncAniListManga(req.db, req.mangaDb, {
198
- direction: syncDirection,
199
- });
200
- res.json({
201
- success: true,
202
- summary: result.summary,
203
- details: result.details,
204
- direction: syncDirection,
205
- });
206
- }
207
- catch (err) {
208
- const message = err instanceof Error ? err.message : 'Manga sync failed';
209
- res.status(500).json({ error: message });
210
- }
211
- });
212
- router.post('/tracker/manga/import', async (req, res) => {
213
- const { username, erase } = req.body ?? {};
214
- if (!username || typeof username !== 'string') {
215
- return res.status(400).json({ error: 'Username is required' });
216
- }
217
- try {
218
- if (!req.mangaDb || req.mangaDb.isClosedCheck()) {
219
- return res.status(503).json({ error: 'Manga database is not ready' });
220
- }
221
- const count = await importFromUsernameManga(req.db, req.mangaDb, username.trim(), erase === true);
222
- res.json({ success: true, count });
223
- }
224
- catch (err) {
225
- const message = err instanceof Error ? err.message : 'Import failed';
226
- res.status(500).json({ error: message });
227
- }
228
- });
229
- router.post('/tracker/manga/mal-import', async (req, res) => {
230
- const { username, erase } = req.body ?? {};
231
- if (!username || typeof username !== 'string') {
232
- return res.status(400).json({ error: 'MAL username is required' });
233
- }
234
- try {
235
- if (!req.mangaDb || req.mangaDb.isClosedCheck()) {
236
- return res.status(503).json({ error: 'Manga database is not ready' });
237
- }
238
- const result = await importMangaFromMalUsername(req.db, req.mangaDb, username.trim(), erase === true);
239
- res.json({ success: true, ...result });
240
- }
241
- catch (err) {
242
- const message = err instanceof Error ? err.message : 'Import failed';
243
- if (message.includes('private') || message.includes('not found')) {
244
- return res.status(404).json({ error: message });
245
- }
246
- if (message.includes('blocked') || message.includes('HTTP 429')) {
247
- return res.status(429).json({ error: message });
248
- }
249
- res.status(500).json({ error: message });
250
- }
251
- });
252
- router.post('/import/mal-xml-manga', multer().single('xmlfile'), async (req, res) => {
253
- if (!req.file)
254
- return res.status(400).json({ error: 'No file' });
255
- const eraseRaw = req.body?.erase;
256
- const erase = eraseRaw === true || eraseRaw === 'true' || eraseRaw === '1';
257
- try {
258
- if (!req.mangaDb || req.mangaDb.isClosedCheck()) {
259
- return res.status(503).json({ error: 'Manga database is not ready' });
260
- }
261
- let result;
262
- try {
263
- result = malMangaXmlParser.parse(req.file.buffer.toString());
264
- }
265
- catch {
266
- return res.status(400).json({ error: 'Invalid XML' });
267
- }
268
- const mangaList = result?.myanimelist?.manga || [];
269
- if (mangaList.length === 0) {
270
- return res.status(400).json({ error: 'No manga found in XML' });
271
- }
272
- const items = mangaList.map((item) => ({
273
- malId: parseInt(mangaXmlText(item.series_mangadb_id), 10) || 0,
274
- title: mangaXmlText(item.series_title),
275
- status: mapMalMangaXmlStatus(mangaXmlText(item.my_status)),
276
- chapters: Math.max(parseInt(mangaXmlText(item.my_read_chapters), 10) || 0, 0),
277
- }));
278
- const { imported, skipped } = await importMangaFromMalXmlItems(req.db, req.mangaDb, items, erase);
279
- res.json({ success: true, imported, skipped });
280
- }
281
- catch (err) {
282
- const message = err instanceof Error ? err.message : 'Import failed';
283
- res.status(500).json({ error: message });
284
- }
285
- });
286
- return router;
287
- }
@@ -1,88 +0,0 @@
1
- import { Router } from 'express';
2
- import { AppCache } from '../utils/cache.utils.js';
3
- import { parseJsonBody } from '../utils/http.utils.js';
4
- const cache = new AppCache({
5
- ttlSeconds: 7 * 24 * 60 * 60,
6
- maxKeys: 5000,
7
- });
8
- function toTitleCase(s) {
9
- return s
10
- .split(' ')
11
- .map((w, i) => {
12
- if (i !== 0 &&
13
- ['x', 'and', 'or', 'the', 'a', 'an', 'of', 'in', 'on', 'with'].includes(w.toLowerCase())) {
14
- return w.toLowerCase();
15
- }
16
- return w.charAt(0).toUpperCase() + w.slice(1);
17
- })
18
- .join(' ')
19
- .replace(/\bX\b/g, 'x');
20
- }
21
- function normalize(s) {
22
- const t = s.trim();
23
- if (!t)
24
- return t;
25
- return toTitleCase(t);
26
- }
27
- export function createTranslateRouter() {
28
- const router = Router();
29
- router.post('/translate', async (req, res) => {
30
- const { texts, source = 'ja', target = 'en' } = req.body ?? {};
31
- if (!Array.isArray(texts) || texts.length === 0) {
32
- return res.json({ translations: {} });
33
- }
34
- const jobs = texts.map((t) => String(t).trim()).filter(Boolean);
35
- const result = {};
36
- const toFetch = [];
37
- for (const t of jobs) {
38
- const key = `${source}:${target}:${t}`;
39
- const cached = cache.get(key);
40
- if (cached)
41
- result[t] = cached;
42
- else
43
- toFetch.push(t);
44
- }
45
- if (toFetch.length > 0) {
46
- const chunks = [];
47
- for (let i = 0; i < toFetch.length; i += 15)
48
- chunks.push(toFetch.slice(i, i + 15));
49
- for (const chunk of chunks) {
50
- const q = chunk.join('\n');
51
- const url = `https://clients5.google.com/translate_a/t?client=dict-chrome-ex&sl=${encodeURIComponent(source)}&tl=${encodeURIComponent(target)}&dt=t&q=${encodeURIComponent(q)}`;
52
- try {
53
- const r = await fetch(url, {
54
- headers: { 'User-Agent': 'Mozilla/5.0' },
55
- signal: AbortSignal.timeout(5000),
56
- });
57
- if (!r.ok)
58
- throw new Error(`translate ${r.status}`);
59
- const data = (await parseJsonBody(r));
60
- let translated = '';
61
- if (Array.isArray(data) && typeof data[0] === 'string') {
62
- translated = data[0];
63
- }
64
- else if (Array.isArray(data) && Array.isArray(data[0])) {
65
- const outer = data;
66
- translated = outer[0]?.map((p) => p[0]).join('') || '';
67
- }
68
- const parts = translated.split('\n');
69
- chunk.forEach((orig, i) => {
70
- const trans = (parts[i] ?? '').trim();
71
- const norm = trans ? normalize(trans) : orig;
72
- result[orig] = norm;
73
- cache.set(`${source}:${target}:${orig}`, norm);
74
- });
75
- }
76
- catch {
77
- for (const orig of chunk) {
78
- if (!result[orig])
79
- result[orig] = orig;
80
- }
81
- }
82
- await new Promise((r) => setTimeout(r, 80));
83
- }
84
- }
85
- res.json({ translations: result });
86
- });
87
- return router;
88
- }
@@ -1,23 +0,0 @@
1
- import { Router } from 'express';
2
- import { TvLibraryController } from '../controllers/tv-library.controller.js';
3
- export function createTvLibraryRouter() {
4
- const router = Router();
5
- const controller = new TvLibraryController();
6
- router.get('/tv/library', controller.getLibrary);
7
- router.get('/tv/library/ids', controller.getLibraryIds);
8
- router.get('/tv/library/check/:id', controller.checkLibrary);
9
- router.post('/tv/library/add', controller.addToLibrary);
10
- router.post('/tv/library/remove', controller.removeFromLibrary);
11
- router.post('/tv/library/status', controller.updateStatus);
12
- router.post('/tv/library/batch-status', controller.batchUpdateStatus);
13
- router.post('/tv/library/remove-many', controller.batchRemove);
14
- router.post('/tv/progress/remove-many', controller.batchRemoveProgress);
15
- router.get('/tv/progress/:mediaId', controller.getProgress);
16
- router.get('/tv/progress/:mediaId/latest', controller.getLatestProgress);
17
- router.post('/tv/progress', controller.saveProgress);
18
- router.post('/tv/progress/remove', controller.removeProgress);
19
- router.get('/tv/continue-watching', controller.getContinueWatching);
20
- router.get('/tv/continue-watching/adult-count', controller.getAdultContinueWatchingCount);
21
- router.post('/tv/continue-watching/purge-adult', controller.purgeAdultContinueWatching);
22
- return router;
23
- }