@serifpersia/dango 3.6.7 → 3.6.9

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 (156) hide show
  1. package/README.md +1 -1
  2. package/client/dist/assets/{AnimeCard-C3P9zWi9.js → AnimeCard-DKDJK8Mp.js} +1 -1
  3. package/client/dist/assets/{AnimeInfo-Et6687Tl.js → AnimeInfo-CTcvfjva.js} +1 -1
  4. package/client/dist/assets/{AnimeInfoPage-DX4eNnlH.js → AnimeInfoPage-B_UiacIN.js} +2 -2
  5. package/client/dist/assets/{Asmr-CCdEWrFi.js → Asmr-DMSN2vxl.js} +2 -2
  6. package/client/dist/assets/{AsmrResetProgressModal-DgEWBJOw.js → AsmrResetProgressModal-CRwfOatX.js} +1 -1
  7. package/client/dist/assets/{AvSyncCalibrator-CQ2fjf2E.js → AvSyncCalibrator-CRJsi-vz.js} +1 -1
  8. package/client/dist/assets/{Home-DUkUTTTy.js → Home-pwmK9_Eh.js} +1 -1
  9. package/client/dist/assets/{Insights-QCh4xW12.js → Insights-m7kFzg2A.js} +1 -1
  10. package/client/dist/assets/{LibraryListPage-BoTxRqHz.js → LibraryListPage-C1wpPvu6.js} +1 -1
  11. package/client/dist/assets/ListeningList-OzfEI1Ga.js +1 -0
  12. package/client/dist/assets/{Manga-HydR-L7o.js → Manga-ChRCExhK.js} +1 -1
  13. package/client/dist/assets/{MangaInfoPage-CBakEI9a.js → MangaInfoPage-DSHJPo2A.js} +1 -1
  14. package/client/dist/assets/{MangaReadPage-DwAR3iPy.js → MangaReadPage-CzOVY1wA.js} +1 -1
  15. package/client/dist/assets/{MangaResetProgressModal-DGzl8vsO.js → MangaResetProgressModal-CJfzdUBb.js} +1 -1
  16. package/client/dist/assets/{Mature-Pi0TxKuF.js → Mature-CifQMfhs.js} +1 -1
  17. package/client/dist/assets/{MediaCard--cuFxOCS.js → MediaCard-CM10yB9V.js} +1 -1
  18. package/client/dist/assets/{OptionListMenu-B3D39dGS.js → OptionListMenu-6Kovbsqw.js} +1 -1
  19. package/client/dist/assets/{Player-CsH4x_I_.js → Player-CK0hT-u4.js} +2 -2
  20. package/client/dist/assets/{PlayerSettings-DGxBsi3F.js → PlayerSettings-Bb66uqT9.js} +1 -1
  21. package/client/dist/assets/{QueueOptionsButton-Djq4Cpex.js → QueueOptionsButton-C_nTj8_q.js} +1 -1
  22. package/client/dist/assets/{QueueRail-DOrYgNVX.js → QueueRail-DUK7URAE.js} +1 -1
  23. package/client/dist/assets/{ReadingList-DwYoBrNk.js → ReadingList-Ck9LJThU.js} +1 -1
  24. package/client/dist/assets/{ResetProgressModal-CMF86mX6.js → ResetProgressModal-BUphIv0k.js} +1 -1
  25. package/client/dist/assets/{Search-DFkhuk-x.js → Search-zJyP15h6.js} +1 -1
  26. package/client/dist/assets/{Settings-DBB2r0VQ.js → Settings-BYdoSEUh.js} +1 -1
  27. package/client/dist/assets/{SkeletonGrid-DJelJgAv.js → SkeletonGrid-CqgbOhjX.js} +1 -1
  28. package/client/dist/assets/{Trackers-DGYXFhYZ.js → Trackers-BVjyv6CM.js} +1 -1
  29. package/client/dist/assets/{Tv-C5MDGD2Z.js → Tv-L13ylswR.js} +1 -1
  30. package/client/dist/assets/{TvInfo-DrKuK13u.js → TvInfo-Cvcm5U2D.js} +1 -1
  31. package/client/dist/assets/{TvPopup-DtHilRDT.js → TvPopup-DhxfKvYm.js} +1 -1
  32. package/client/dist/assets/{TvSearch-CzNxdu_w.js → TvSearch-P8bFE6_X.js} +1 -1
  33. package/client/dist/assets/{TvWatchlist-D7p4br6S.js → TvWatchlist-D5gVPjtb.js} +1 -1
  34. package/client/dist/assets/{Watchlist-DrOzswl2.js → Watchlist-Cjq8l3rU.js} +1 -1
  35. package/client/dist/assets/{hls-BGCX7mJy.js → hls-BkzuANAg.js} +1 -1
  36. package/client/dist/assets/{index-CnfZwsds.js → index-CLVgzKLk.js} +19 -19
  37. package/client/dist/assets/{useAnimeInfoData-DYXWBPF-.js → useAnimeInfoData-DZJZu-so.js} +1 -1
  38. package/client/dist/assets/{useAsmr-BbhQ59bk.js → useAsmr-CvrMGHuo.js} +1 -1
  39. package/client/dist/assets/useAsmrPopup-Bimy8Ouw.js +1 -0
  40. package/client/dist/assets/{useMangaLibrary-DuesWQaE.js → useMangaLibrary--MiN11uR.js} +1 -1
  41. package/client/dist/assets/{useMangaPopup-D57OVGUr.js → useMangaPopup-CR5XhKKS.js} +1 -1
  42. package/client/dist/assets/{useProviders-mpQb_dix.js → useProviders-DqdT4fA5.js} +1 -1
  43. package/client/dist/assets/{useTv-DGflCsSI.js → useTv-BL0HMDyP.js} +1 -1
  44. package/client/dist/assets/{useTvLibrary-BqeSmyc4.js → useTvLibrary-D4U4iqN8.js} +1 -1
  45. package/client/dist/index.html +1 -1
  46. package/package.json +3 -5
  47. package/server/dist/config.js +1 -174
  48. package/server/dist/server.js +1068 -289
  49. package/server/dist/shipped-defaults.js +1 -6
  50. package/server/package.json +4 -5
  51. package/client/dist/assets/Asmr.module-CdWlioTY.js +0 -1
  52. package/client/dist/assets/ListeningList-DguKjI_t.js +0 -1
  53. package/client/dist/assets/Music-BGH_0voi.css +0 -1
  54. package/client/dist/assets/Music-CQpyXTLH.js +0 -1
  55. package/client/dist/assets/Radio-CCHNFN7R.css +0 -1
  56. package/client/dist/assets/Radio-DF6g6odd.js +0 -1
  57. package/client/dist/assets/Radio.module-DGSZLpKM.js +0 -1
  58. package/client/dist/assets/useAsmrPopup-SymV1J-b.js +0 -1
  59. package/server/dist/app-auth.js +0 -157
  60. package/server/dist/app-hono.js +0 -598
  61. package/server/dist/db/drizzle.js +0 -16
  62. package/server/dist/db/schema-anime.js +0 -61
  63. package/server/dist/db/schema-asmr.js +0 -13
  64. package/server/dist/db/schema-manga.js +0 -31
  65. package/server/dist/db/schema-tv.js +0 -18
  66. package/server/dist/db.js +0 -217
  67. package/server/dist/discord-gateway.js +0 -385
  68. package/server/dist/discord-rpc.js +0 -413
  69. package/server/dist/github-sync.js +0 -508
  70. package/server/dist/google.js +0 -741
  71. package/server/dist/hono/asmr.js +0 -10
  72. package/server/dist/hono/auth.js +0 -49
  73. package/server/dist/hono/music.js +0 -209
  74. package/server/dist/hono/proxy.js +0 -827
  75. package/server/dist/hono/tv.js +0 -200
  76. package/server/dist/hono/watchlist.js +0 -341
  77. package/server/dist/lib/anilist.js +0 -1583
  78. package/server/dist/lib/anischedule.js +0 -191
  79. package/server/dist/lib/insights.js +0 -185
  80. package/server/dist/lib/ipc.js +0 -23
  81. package/server/dist/lib/kitsu.js +0 -517
  82. package/server/dist/lib/mal-import.js +0 -291
  83. package/server/dist/lib/mal.js +0 -957
  84. package/server/dist/lib/migration.js +0 -310
  85. package/server/dist/lib/offline-db.js +0 -395
  86. package/server/dist/lib/potoken.js +0 -135
  87. package/server/dist/lib/recommendations.service.js +0 -216
  88. package/server/dist/lib/temp-ids.js +0 -3
  89. package/server/dist/lib/tmdb.js +0 -72
  90. package/server/dist/lib/tracker/anilist-tracker.js +0 -308
  91. package/server/dist/lib/tracker/manga-sync.service.js +0 -773
  92. package/server/dist/lib/tracker/sync-state.js +0 -21
  93. package/server/dist/lib/tracker/sync.service.js +0 -476
  94. package/server/dist/lib/translate.js +0 -79
  95. package/server/dist/lib/ump.js +0 -106
  96. package/server/dist/lib/ytmusic.js +0 -162
  97. package/server/dist/logger.js +0 -16
  98. package/server/dist/providers/manga/manga.types.js +0 -1
  99. package/server/dist/providers/provider.interface.js +0 -1
  100. package/server/dist/providers/remote-loader.js +0 -614
  101. package/server/dist/providers/remote-types.js +0 -159
  102. package/server/dist/providers/title-matching.js +0 -226
  103. package/server/dist/providers/tv.types.js +0 -1
  104. package/server/dist/rclone.js +0 -120
  105. package/server/dist/repositories/asmr.repository.js +0 -77
  106. package/server/dist/repositories/insights.repository.js +0 -106
  107. package/server/dist/repositories/library.repository.js +0 -94
  108. package/server/dist/repositories/mal-cache.repository.js +0 -52
  109. package/server/dist/repositories/manga.repository.js +0 -95
  110. package/server/dist/repositories/notifications.repository.js +0 -60
  111. package/server/dist/repositories/queue.repository.js +0 -72
  112. package/server/dist/repositories/settings.repository.js +0 -30
  113. package/server/dist/repositories/shows-meta.repository.js +0 -58
  114. package/server/dist/repositories/temp-show-ids.repository.js +0 -63
  115. package/server/dist/repositories/tv.repository.js +0 -93
  116. package/server/dist/repositories/watched-episodes.repository.js +0 -82
  117. package/server/dist/repositories/watchlist.repository.js +0 -60
  118. package/server/dist/scripts/backfill-episode-duration.js +0 -42
  119. package/server/dist/sync-payload.js +0 -91
  120. package/server/dist/sync.js +0 -657
  121. package/server/dist/trpc/context.js +0 -7
  122. package/server/dist/trpc/errors.js +0 -15
  123. package/server/dist/trpc/index.js +0 -18
  124. package/server/dist/trpc/router.js +0 -49
  125. package/server/dist/trpc/routers/asmr-library.js +0 -112
  126. package/server/dist/trpc/routers/asmr-progress.js +0 -121
  127. package/server/dist/trpc/routers/asmr.js +0 -87
  128. package/server/dist/trpc/routers/auth.js +0 -194
  129. package/server/dist/trpc/routers/continue-watching.js +0 -81
  130. package/server/dist/trpc/routers/data.js +0 -1026
  131. package/server/dist/trpc/routers/discord.js +0 -285
  132. package/server/dist/trpc/routers/discovery.js +0 -11
  133. package/server/dist/trpc/routers/insights.js +0 -14
  134. package/server/dist/trpc/routers/manga-library.js +0 -263
  135. package/server/dist/trpc/routers/manga-progress.js +0 -169
  136. package/server/dist/trpc/routers/manga.js +0 -201
  137. package/server/dist/trpc/routers/music.js +0 -655
  138. package/server/dist/trpc/routers/notifications.js +0 -80
  139. package/server/dist/trpc/routers/progress.js +0 -109
  140. package/server/dist/trpc/routers/queue.js +0 -93
  141. package/server/dist/trpc/routers/radio.js +0 -145
  142. package/server/dist/trpc/routers/settings.js +0 -96
  143. package/server/dist/trpc/routers/tracker.js +0 -213
  144. package/server/dist/trpc/routers/tv-library.js +0 -135
  145. package/server/dist/trpc/routers/tv-progress.js +0 -159
  146. package/server/dist/trpc/routers/tv.js +0 -551
  147. package/server/dist/trpc/routers/watchlist.js +0 -550
  148. package/server/dist/trpc/validation.js +0 -258
  149. package/server/dist/utils/cache.utils.js +0 -25
  150. package/server/dist/utils/cookie.utils.js +0 -13
  151. package/server/dist/utils/db-utils.js +0 -17
  152. package/server/dist/utils/env.utils.js +0 -77
  153. package/server/dist/utils/http.utils.js +0 -82
  154. package/server/dist/utils/request-context.js +0 -2
  155. package/server/dist/utils/security.utils.js +0 -76
  156. /package/client/dist/assets/{Asmr-D1KuQwQy.css → useAsmrPopup-D1KuQwQy.css} +0 -0
@@ -1,285 +0,0 @@
1
- import { protectedProcedure, router } from '../index.js';
2
- import { badRequest, defineSchema, failed, optStr, reqObj } from '../validation.js';
3
- import { discordRPCService } from '../../discord-rpc.js';
4
- import { discordGatewayService } from '../../discord-gateway.js';
5
- import { getDlsitePoster, getMangadexCover } from '../../hono/watchlist.js';
6
- import { updateEnvFile } from '../../utils/env.utils.js';
7
- import logger from '../../logger.js';
8
- function maskToken(t) {
9
- if (!t)
10
- return 'none';
11
- return t.slice(0, 8) + '...' + t.slice(-4);
12
- }
13
- const gatewayLog = logger.child({ module: 'DiscordGatewayRoutes' });
14
- function optUnknown(obj, name) {
15
- return obj[name];
16
- }
17
- function cleanProxyThumb(value) {
18
- let thumb = String(value || '');
19
- if (thumb.includes('/api/proxy') || thumb.includes('/api/image-proxy')) {
20
- const match = thumb.match(/url=([^&]+)/);
21
- if (match) {
22
- try {
23
- thumb = decodeURIComponent(match[1]);
24
- }
25
- catch {
26
- return '';
27
- }
28
- }
29
- }
30
- if (!thumb.startsWith('https://'))
31
- return '';
32
- if (thumb.includes('localhost') || thumb.includes('127.0.0.1'))
33
- return '';
34
- return thumb;
35
- }
36
- const presenceInput = () => defineSchema((value) => {
37
- if (value === undefined || value === null)
38
- return {};
39
- return { ...reqObj(value) };
40
- });
41
- export const discordRouter = router({
42
- pageStatus: protectedProcedure.input(presenceInput()).mutation(async ({ input }) => {
43
- const page = optUnknown(input, 'page');
44
- if (typeof page !== 'string' || !discordRPCService.isServiceEnabled) {
45
- return { success: true };
46
- }
47
- discordRPCService.setIdleStatus(page);
48
- return { success: true };
49
- }),
50
- heartbeat: protectedProcedure.input(presenceInput()).mutation(async ({ input }) => {
51
- const sessionId = optUnknown(input, 'sessionId');
52
- const bye = optUnknown(input, 'bye');
53
- if (typeof sessionId !== 'string' || !discordRPCService.isServiceEnabled) {
54
- return { success: true };
55
- }
56
- if (bye) {
57
- discordRPCService.removeHeartbeat(sessionId);
58
- }
59
- else {
60
- discordRPCService.heartbeat(sessionId);
61
- }
62
- return { success: true };
63
- }),
64
- clear: protectedProcedure.input(presenceInput()).mutation(async ({ input }) => {
65
- const sessionId = optUnknown(input, 'sessionId');
66
- if (!discordRPCService.isServiceEnabled) {
67
- return { success: true };
68
- }
69
- discordRPCService.clearPresence(typeof sessionId === 'string' ? sessionId : undefined);
70
- return { success: true };
71
- }),
72
- asmrPresence: protectedProcedure.input(presenceInput()).mutation(async ({ input }) => {
73
- if (!discordRPCService.isServiceEnabled)
74
- return { success: true };
75
- const sessionId = optUnknown(input, 'sessionId');
76
- if (typeof sessionId === 'string')
77
- discordRPCService.heartbeat(sessionId);
78
- const thumbnail = optUnknown(input, 'thumbnail');
79
- const thumbnails = optUnknown(input, 'thumbnails');
80
- const rjCode = optUnknown(input, 'rjCode');
81
- let thumb = String(thumbnail || '');
82
- const thumbs = Array.isArray(thumbnails) ? thumbnails.map(String).filter(Boolean) : undefined;
83
- const needsDlsite = !thumb || thumb.includes('weeabo0.xyz') || thumb.includes('japaneseasmr.com');
84
- if (needsDlsite && rjCode) {
85
- const dlsiteThumb = await getDlsitePoster(String(rjCode));
86
- if (dlsiteThumb)
87
- thumb = dlsiteThumb;
88
- }
89
- if (thumb.includes('/api/image-proxy') || thumb.includes('weeabo0.xyz'))
90
- thumb = '';
91
- discordRPCService.updatePresence({
92
- title: String(optUnknown(input, 'title') || 'ASMR').slice(0, 128),
93
- episode: String(optUnknown(input, 'trackLabel') || '').slice(0, 64),
94
- totalEpisodes: '',
95
- currentTime: Number(optUnknown(input, 'currentTime')) || 0,
96
- duration: Number(optUnknown(input, 'duration')) || 0,
97
- thumbnail: thumb,
98
- thumbnails: thumbs,
99
- isPlaying: !!optUnknown(input, 'isPlaying'),
100
- providerName: 'ASMR',
101
- sessionId: typeof sessionId === 'string' ? sessionId : undefined,
102
- isAdult: !!optUnknown(input, 'isAdult'),
103
- });
104
- return { success: true };
105
- }),
106
- radioPresence: protectedProcedure.input(presenceInput()).mutation(async ({ input }) => {
107
- if (!discordRPCService.isServiceEnabled)
108
- return { success: true };
109
- const sessionId = optUnknown(input, 'sessionId');
110
- if (typeof sessionId === 'string')
111
- discordRPCService.heartbeat(sessionId);
112
- let thumb = String(optUnknown(input, 'thumbnail') || '');
113
- if (thumb.includes('/api/image-proxy')) {
114
- const match = thumb.match(/url=([^&]+)/);
115
- if (match)
116
- thumb = decodeURIComponent(match[1]);
117
- }
118
- if (thumb && !thumb.startsWith('https://'))
119
- thumb = '';
120
- if (thumb.includes('localhost') || thumb.includes('127.0.0.1'))
121
- thumb = '';
122
- const stationLabel = String(optUnknown(input, 'stationLabel') || '');
123
- discordRPCService.updatePresence({
124
- title: String(optUnknown(input, 'title') || 'Radio').slice(0, 128),
125
- episode: stationLabel.slice(0, 64),
126
- totalEpisodes: '',
127
- stateLine: (stationLabel || 'Radio').slice(0, 64),
128
- currentTime: Number(optUnknown(input, 'currentTime')) || 0,
129
- duration: 0,
130
- thumbnail: thumb,
131
- isPlaying: !!optUnknown(input, 'isPlaying'),
132
- providerName: 'Radio',
133
- sessionId: typeof sessionId === 'string' ? sessionId : undefined,
134
- isAdult: false,
135
- });
136
- return { success: true };
137
- }),
138
- musicPresence: protectedProcedure.input(presenceInput()).mutation(async ({ input }) => {
139
- if (!discordRPCService.isServiceEnabled)
140
- return { success: true };
141
- const sessionId = optUnknown(input, 'sessionId');
142
- if (typeof sessionId === 'string')
143
- discordRPCService.heartbeat(sessionId);
144
- let thumb = String(optUnknown(input, 'thumbnail') || '');
145
- if (thumb.includes('/api/image-proxy')) {
146
- const match = thumb.match(/url=([^&]+)/);
147
- if (match)
148
- thumb = decodeURIComponent(match[1]);
149
- }
150
- if (thumb && !thumb.startsWith('https://'))
151
- thumb = '';
152
- if (thumb.includes('localhost') || thumb.includes('127.0.0.1'))
153
- thumb = '';
154
- const artistLabel = String(optUnknown(input, 'artistLabel') || '');
155
- discordRPCService.updatePresence({
156
- title: String(optUnknown(input, 'title') || 'Music').slice(0, 128),
157
- episode: artistLabel.slice(0, 64),
158
- totalEpisodes: '',
159
- stateLine: (artistLabel || 'Music').slice(0, 64),
160
- currentTime: Number(optUnknown(input, 'currentTime')) || 0,
161
- duration: Number(optUnknown(input, 'duration')) || 0,
162
- thumbnail: thumb,
163
- isPlaying: !!optUnknown(input, 'isPlaying'),
164
- providerName: 'Music',
165
- sessionId: typeof sessionId === 'string' ? sessionId : undefined,
166
- isAdult: false,
167
- });
168
- return { success: true };
169
- }),
170
- mangaPresence: protectedProcedure.input(presenceInput()).mutation(async ({ input }) => {
171
- if (!discordRPCService.isServiceEnabled)
172
- return { success: true };
173
- const sessionId = optUnknown(input, 'sessionId');
174
- if (typeof sessionId === 'string')
175
- discordRPCService.heartbeat(sessionId);
176
- const thumbnails = optUnknown(input, 'thumbnails');
177
- const title = optUnknown(input, 'title');
178
- let thumb = cleanProxyThumb(optUnknown(input, 'thumbnail'));
179
- let thumbs = Array.isArray(thumbnails)
180
- ? thumbnails.map(cleanProxyThumb).filter(Boolean)
181
- : undefined;
182
- if (!thumb && title) {
183
- const fallback = await getMangadexCover(String(title));
184
- if (fallback) {
185
- thumb = fallback;
186
- thumbs = [fallback, ...(thumbs || [])];
187
- }
188
- }
189
- const chapterLabel = String(optUnknown(input, 'chapterLabel') || '');
190
- discordRPCService.updatePresence({
191
- title: String(title || 'Manga').slice(0, 128),
192
- episode: chapterLabel.slice(0, 64),
193
- totalEpisodes: '',
194
- stateLine: (chapterLabel ? `Reading ${chapterLabel}` : 'Reading').slice(0, 64),
195
- currentTime: 0,
196
- duration: 0,
197
- thumbnail: thumb,
198
- thumbnails: thumbs,
199
- isPlaying: optUnknown(input, 'isPlaying') !== false,
200
- providerName: 'Manga',
201
- sessionId: typeof sessionId === 'string' ? sessionId : undefined,
202
- isAdult: optUnknown(input, 'isAdult') === true,
203
- });
204
- return { success: true };
205
- }),
206
- tvPresence: protectedProcedure.input(presenceInput()).mutation(async ({ input }) => {
207
- if (!discordRPCService.isServiceEnabled)
208
- return { success: true };
209
- const sessionId = optUnknown(input, 'sessionId');
210
- if (typeof sessionId === 'string')
211
- discordRPCService.heartbeat(sessionId);
212
- const thumbnail = optUnknown(input, 'thumbnail');
213
- let thumb = String(thumbnail || '');
214
- if (thumb.includes('/api/image-proxy')) {
215
- const match = thumb.match(/url=([^&]+)/);
216
- if (match)
217
- thumb = decodeURIComponent(match[1]);
218
- }
219
- if (thumb.includes('localhost') || thumb.includes('127.0.0.1'))
220
- thumb = '';
221
- const episodeLabel = String(optUnknown(input, 'episodeLabel') || '');
222
- discordRPCService.updatePresence({
223
- title: String(optUnknown(input, 'title') || 'TV').slice(0, 128),
224
- episode: episodeLabel.slice(0, 64),
225
- totalEpisodes: '',
226
- stateLine: (episodeLabel || 'Movie').slice(0, 64),
227
- currentTime: Number(optUnknown(input, 'currentTime')) || 0,
228
- duration: Number(optUnknown(input, 'duration')) || 0,
229
- thumbnail: thumb,
230
- isPlaying: !!optUnknown(input, 'isPlaying'),
231
- providerName: 'TV',
232
- sessionId: typeof sessionId === 'string' ? sessionId : undefined,
233
- isAdult: optUnknown(input, 'isAdult') === true,
234
- });
235
- return { success: true };
236
- }),
237
- gatewayStatus: protectedProcedure.query(() => {
238
- const hasToken = discordGatewayService.hasToken();
239
- return {
240
- hasToken,
241
- masked: hasToken ? maskToken(process.env.DISCORD_GATEWAY_TOKEN) : null,
242
- enabled: discordGatewayService.serviceEnabled,
243
- };
244
- }),
245
- gatewaySave: protectedProcedure
246
- .input(defineSchema((value) => {
247
- if (value === undefined || value === null)
248
- return {};
249
- const obj = reqObj(value);
250
- const out = {};
251
- const token = optStr(obj, 'token');
252
- if (token !== undefined)
253
- out.token = token;
254
- return out;
255
- }))
256
- .mutation(async ({ input }) => {
257
- let token = (input.token || '').trim();
258
- if (token.startsWith('"') && token.endsWith('"'))
259
- token = token.slice(1, -1);
260
- if (token.length < 30)
261
- throw badRequest('Token too short');
262
- try {
263
- await updateEnvFile({ DISCORD_GATEWAY_TOKEN: token });
264
- discordGatewayService.reloadToken();
265
- gatewayLog.info(`Discord Gateway token saved ${maskToken(token)} (not logged)`);
266
- return { ok: true, masked: maskToken(token) };
267
- }
268
- catch (e) {
269
- gatewayLog.error({ err: e }, 'Failed to save Discord Gateway token');
270
- throw failed('Failed to save token');
271
- }
272
- }),
273
- gatewayRemove: protectedProcedure.mutation(async () => {
274
- try {
275
- await updateEnvFile({ DISCORD_GATEWAY_TOKEN: '' });
276
- discordGatewayService.reloadToken();
277
- gatewayLog.info('Discord Gateway token removed');
278
- return { ok: true };
279
- }
280
- catch (e) {
281
- gatewayLog.error({ err: e }, 'Failed to remove Discord Gateway token');
282
- throw failed('Failed to remove token');
283
- }
284
- }),
285
- });
@@ -1,11 +0,0 @@
1
- import { protectedProcedure, router } from '../index.js';
2
- import { getWatchlistDiscoveryStatus, triggerWatchlistDiscovery } from '../../hono/watchlist.js';
3
- export const discoveryRouter = router({
4
- status: protectedProcedure.query(() => {
5
- return getWatchlistDiscoveryStatus();
6
- }),
7
- refresh: protectedProcedure.mutation(() => {
8
- const started = triggerWatchlistDiscovery();
9
- return { success: true, started, ...getWatchlistDiscoveryStatus() };
10
- }),
11
- });
@@ -1,14 +0,0 @@
1
- import { protectedProcedure, router } from '../index.js';
2
- import { getWatchInsights, getGenreCards } from '../../lib/insights.js';
3
- import { getCachedRecommendations } from '../../lib/recommendations.service.js';
4
- export const insightsRouter = router({
5
- summary: protectedProcedure.query(async ({ ctx }) => {
6
- return getWatchInsights(ctx.db);
7
- }),
8
- genreCards: protectedProcedure.query(async ({ ctx }) => {
9
- return getGenreCards(ctx.db);
10
- }),
11
- recommendations: protectedProcedure.query(async ({ ctx }) => {
12
- return { candidates: await getCachedRecommendations(ctx.db) };
13
- }),
14
- });
@@ -1,263 +0,0 @@
1
- import { TRPCError } from '@trpc/server';
2
- import { protectedProcedure, router } from '../index.js';
3
- import { performMangaWriteTransactionAsync } from '../../sync.js';
4
- import { buildMangaId, MANGA_STATUSES, MangaLibraryRepository, MangaProgressRepository, } from '../../repositories/manga.repository.js';
5
- import { badRequest, defineSchema, failed, optStr, readCount, reqObj, reqStr, } from '../validation.js';
6
- function mangaDb(ctx) {
7
- const db = ctx.mangaDb;
8
- if (!db || db.isClosedCheck())
9
- throw new Error('Manga database is not ready');
10
- return db;
11
- }
12
- function normalizeId(provider, mangaId, idRaw) {
13
- if (idRaw && idRaw.includes(':'))
14
- return idRaw;
15
- return buildMangaId(provider, mangaId || idRaw || '');
16
- }
17
- const mangaLibraryListInput = () => defineSchema((value) => {
18
- const obj = reqObj(value);
19
- const status = optStr(obj, 'status');
20
- const page = Math.max(readCount(obj, 'page', 1), 1);
21
- const limit = Math.min(Math.max(readCount(obj, 'limit', 24), 1), 100);
22
- return { status, page, limit };
23
- });
24
- const mangaLibraryIdInput = () => defineSchema((value) => ({
25
- id: reqStr(reqObj(value), 'id'),
26
- }));
27
- const mangaLibraryAddInput = () => defineSchema((value) => {
28
- const obj = reqObj(value);
29
- const out = {
30
- provider: optStr(obj, 'provider'),
31
- mangaId: optStr(obj, 'mangaId'),
32
- id: optStr(obj, 'id'),
33
- title: optStr(obj, 'title'),
34
- cover: optStr(obj, 'cover'),
35
- status: optStr(obj, 'status'),
36
- author: optStr(obj, 'author'),
37
- altTitle: optStr(obj, 'altTitle'),
38
- contentRating: optStr(obj, 'contentRating'),
39
- };
40
- if (typeof obj.silent === 'boolean')
41
- out.silent = obj.silent;
42
- return out;
43
- });
44
- const mangaLibraryRemoveInput = () => defineSchema((value) => {
45
- if (typeof value !== 'string')
46
- throw new Error('id must be a string');
47
- return value;
48
- });
49
- const mangaLibraryStatusInput = () => defineSchema((value) => {
50
- const obj = reqObj(value);
51
- return { id: optStr(obj, 'id'), status: optStr(obj, 'status') };
52
- });
53
- const mangaLibraryLinkInput = () => defineSchema((value) => {
54
- const obj = reqObj(value);
55
- return { id: optStr(obj, 'id'), anilistId: obj.anilistId };
56
- });
57
- const mangaLibraryBatchStatusInput = () => defineSchema((value) => {
58
- const obj = reqObj(value);
59
- const idsRaw = obj.ids;
60
- const ids = Array.isArray(idsRaw) ? idsRaw.map((id) => String(id)) : [];
61
- return { ids, status: optStr(obj, 'status') };
62
- });
63
- const mangaLibraryRemoveManyInput = () => defineSchema((value) => {
64
- if (!Array.isArray(value))
65
- throw new Error('ids must be a non-empty array');
66
- return value.map((id) => String(id));
67
- });
68
- export const mangaLibraryRouter = router({
69
- list: protectedProcedure.input(mangaLibraryListInput()).query(async ({ ctx, input }) => {
70
- try {
71
- const offset = (input.page - 1) * input.limit;
72
- const db = mangaDb(ctx);
73
- const rows = await MangaLibraryRepository.getAll(db, input.status, input.limit, offset);
74
- const total = await MangaLibraryRepository.getCount(db, input.status);
75
- return { data: rows, total, page: input.page, limit: input.limit };
76
- }
77
- catch (err) {
78
- if (err instanceof TRPCError)
79
- throw err;
80
- throw failed('Failed to load manga library');
81
- }
82
- }),
83
- ids: protectedProcedure.query(async ({ ctx }) => {
84
- try {
85
- return { ids: await MangaLibraryRepository.getIds(mangaDb(ctx)) };
86
- }
87
- catch {
88
- return { ids: [] };
89
- }
90
- }),
91
- check: protectedProcedure.input(mangaLibraryIdInput()).query(async ({ ctx, input }) => {
92
- try {
93
- const item = await MangaLibraryRepository.getById(mangaDb(ctx), input.id);
94
- return { inLibrary: !!item, status: item?.status ?? null };
95
- }
96
- catch {
97
- return { inLibrary: false, status: null };
98
- }
99
- }),
100
- entry: protectedProcedure.input(mangaLibraryIdInput()).query(async ({ ctx, input }) => {
101
- try {
102
- const item = await MangaLibraryRepository.getById(mangaDb(ctx), input.id);
103
- if (!item)
104
- throw new TRPCError({ code: 'NOT_FOUND', message: 'Not in library' });
105
- return { item };
106
- }
107
- catch (err) {
108
- if (err instanceof TRPCError)
109
- throw err;
110
- throw failed('Failed to load entry');
111
- }
112
- }),
113
- add: protectedProcedure.input(mangaLibraryAddInput()).mutation(async ({ ctx, input }) => {
114
- const provider = input.provider || '';
115
- const mangaId = input.mangaId || '';
116
- const idRaw = input.id || '';
117
- if (!provider || (!mangaId && !idRaw)) {
118
- throw badRequest('provider and mangaId are required');
119
- }
120
- const id = normalizeId(String(provider), String(mangaId || ''), idRaw ? String(idRaw) : undefined);
121
- const cleanStatus = typeof input.status === 'string' && MANGA_STATUSES.includes(input.status)
122
- ? input.status
123
- : 'Reading';
124
- try {
125
- await performMangaWriteTransactionAsync(mangaDb(ctx), async (tx) => {
126
- await MangaLibraryRepository.upsert(tx, {
127
- id,
128
- provider: String(provider).toLowerCase(),
129
- mangaId: String(mangaId || idRaw || ''),
130
- title: String(input.title || ''),
131
- cover: String(input.cover || ''),
132
- status: cleanStatus,
133
- author: input.author ? String(input.author) : undefined,
134
- altTitle: input.altTitle ? String(input.altTitle) : undefined,
135
- contentRating: input.contentRating ? String(input.contentRating) : undefined,
136
- });
137
- });
138
- return { success: true, id };
139
- }
140
- catch (err) {
141
- if (err instanceof TRPCError)
142
- throw err;
143
- throw failed('Failed to bookmark manga');
144
- }
145
- }),
146
- remove: protectedProcedure.input(mangaLibraryRemoveInput()).mutation(async ({ ctx, input }) => {
147
- if (!input)
148
- throw badRequest('id is required');
149
- try {
150
- await performMangaWriteTransactionAsync(mangaDb(ctx), async (tx) => {
151
- await MangaLibraryRepository.delete(tx, String(input));
152
- await MangaProgressRepository.deleteByManga(tx, String(input));
153
- });
154
- return { success: true };
155
- }
156
- catch (err) {
157
- if (err instanceof TRPCError)
158
- throw err;
159
- throw failed('Failed to remove bookmark');
160
- }
161
- }),
162
- setStatus: protectedProcedure
163
- .input(mangaLibraryStatusInput())
164
- .mutation(async ({ ctx, input }) => {
165
- if (!input.id || !input.status || !MANGA_STATUSES.includes(input.status)) {
166
- throw badRequest('id and a valid status are required');
167
- }
168
- try {
169
- await performMangaWriteTransactionAsync(mangaDb(ctx), async (tx) => {
170
- await MangaLibraryRepository.updateStatus(tx, String(input.id), String(input.status));
171
- });
172
- return { success: true };
173
- }
174
- catch (err) {
175
- if (err instanceof TRPCError)
176
- throw err;
177
- throw failed('Failed to update status');
178
- }
179
- }),
180
- link: protectedProcedure.input(mangaLibraryLinkInput()).mutation(async ({ ctx, input }) => {
181
- const targetId = typeof input.id === 'string' ? input.id : '';
182
- const mediaId = Number(input.anilistId);
183
- if (!targetId || !Number.isInteger(mediaId) || mediaId <= 0) {
184
- throw badRequest('id and a numeric anilistId are required');
185
- }
186
- try {
187
- const db = mangaDb(ctx);
188
- const row = await MangaLibraryRepository.getById(db, targetId);
189
- if (!row)
190
- throw new TRPCError({ code: 'NOT_FOUND', message: 'Library entry not found' });
191
- if (String(row.provider).toLowerCase() === 'anilist') {
192
- throw badRequest('Link a provider bookmark to the AniList entry, not the reverse');
193
- }
194
- let progressMigrated = false;
195
- await performMangaWriteTransactionAsync(db, async (tx) => {
196
- await MangaLibraryRepository.setAnilistId(tx, targetId, mediaId, 'manual');
197
- const orphanId = `anilist:${mediaId}`;
198
- if (orphanId !== targetId) {
199
- const orphan = await MangaLibraryRepository.getById(tx, orphanId);
200
- const target = await MangaLibraryRepository.getById(tx, targetId);
201
- const targetProgress = await MangaProgressRepository.getByManga(tx, targetId);
202
- if (orphan?.lastChapterNumber &&
203
- !target?.lastChapterNumber &&
204
- targetProgress.length === 0) {
205
- await MangaLibraryRepository.setProgressPointer(tx, targetId, orphan.lastChapterNumber);
206
- await MangaProgressRepository.moveSyntheticChapters(tx, orphanId, targetId);
207
- progressMigrated = true;
208
- }
209
- await MangaLibraryRepository.delete(tx, orphanId);
210
- await MangaProgressRepository.deleteByManga(tx, orphanId);
211
- }
212
- });
213
- return { success: true, id: targetId, progressMigrated };
214
- }
215
- catch (err) {
216
- if (err instanceof TRPCError)
217
- throw err;
218
- throw failed('Failed to link entry');
219
- }
220
- }),
221
- batchStatus: protectedProcedure
222
- .input(mangaLibraryBatchStatusInput())
223
- .mutation(async ({ ctx, input }) => {
224
- if (!Array.isArray(input.ids) || input.ids.length === 0) {
225
- throw badRequest('ids must be a non-empty array');
226
- }
227
- if (!input.status || !MANGA_STATUSES.includes(input.status)) {
228
- throw badRequest('a valid status is required');
229
- }
230
- const ids = input.ids.map((id) => String(id));
231
- try {
232
- await performMangaWriteTransactionAsync(mangaDb(ctx), async (tx) => {
233
- await MangaLibraryRepository.updateStatusMany(tx, ids, String(input.status));
234
- });
235
- return { success: true, updated: ids.length };
236
- }
237
- catch (err) {
238
- if (err instanceof TRPCError)
239
- throw err;
240
- throw failed('Failed to update statuses');
241
- }
242
- }),
243
- removeMany: protectedProcedure
244
- .input(mangaLibraryRemoveManyInput())
245
- .mutation(async ({ ctx, input }) => {
246
- if (!Array.isArray(input) || input.length === 0) {
247
- throw badRequest('ids must be a non-empty array');
248
- }
249
- const ids = input.map((id) => String(id));
250
- try {
251
- await performMangaWriteTransactionAsync(mangaDb(ctx), async (tx) => {
252
- await MangaLibraryRepository.deleteMany(tx, ids);
253
- await MangaProgressRepository.deleteMany(tx, ids);
254
- });
255
- return { success: true, removed: ids.length };
256
- }
257
- catch (err) {
258
- if (err instanceof TRPCError)
259
- throw err;
260
- throw failed('Failed to remove bookmarks');
261
- }
262
- }),
263
- });