@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,80 +1 @@
1
- import { protectedProcedure, router } from '../index.js';
2
- import logger from '../../logger.js';
3
- import { performWriteTransactionAsync } from '../../sync.js';
4
- import { WatchlistRepository } from '../../repositories/watchlist.repository.js';
5
- import { WatchedEpisodesRepository } from '../../repositories/watched-episodes.repository.js';
6
- import { NotificationsRepository } from '../../repositories/notifications.repository.js';
7
- import { notificationDismissInput } from '../validation.js';
8
- function clearAllInput() {
9
- return {
10
- '~standard': {
11
- version: 1,
12
- vendor: 'dango',
13
- types: undefined,
14
- validate(value) {
15
- try {
16
- if (value === undefined || value === null)
17
- return { value: {} };
18
- if (typeof value !== 'object' || value === null)
19
- throw new Error('Expected an object');
20
- const showId = value.showId;
21
- if (showId === undefined || showId === null)
22
- return { value: {} };
23
- if (typeof showId !== 'string' || showId.length === 0)
24
- throw new Error('showId must be a string');
25
- return { value: { showId } };
26
- }
27
- catch (err) {
28
- return { issues: [{ message: err instanceof Error ? err.message : 'Invalid input' }] };
29
- }
30
- },
31
- },
32
- };
33
- }
34
- export const notificationsRouter = router({
35
- list: protectedProcedure.query(async ({ ctx }) => {
36
- const watchingShows = await WatchlistRepository.getWatchingShows(ctx.db);
37
- const notifications = [];
38
- for (const show of watchingShows) {
39
- try {
40
- const [watchedEps, dismissedEps, discoveredEps] = await Promise.all([
41
- WatchedEpisodesRepository.getWatchedEpisodeNumbers(ctx.db, show.id),
42
- NotificationsRepository.getDismissedByShow(ctx.db, show.id),
43
- NotificationsRepository.getDiscoveredByShow(ctx.db, show.id),
44
- ]);
45
- const watchedSet = new Set(watchedEps.map((e) => e.toString()));
46
- const dismissedSet = new Set(dismissedEps.map((e) => e.episodeNumber.toString()));
47
- for (const discovered of discoveredEps) {
48
- if (!watchedSet.has(discovered.episodeNumber) &&
49
- !dismissedSet.has(discovered.episodeNumber)) {
50
- notifications.push({
51
- showId: show.id,
52
- name: show.name,
53
- nativeName: show.nativeName,
54
- englishName: show.englishName,
55
- thumbnail: show.thumbnail,
56
- episodeNumber: discovered.episodeNumber,
57
- id: `${show.id}-${discovered.episodeNumber}`,
58
- });
59
- }
60
- }
61
- }
62
- catch (e) {
63
- logger.error({ err: e, showId: show.id }, 'Failed to get notifications for show');
64
- }
65
- }
66
- return notifications.sort((a, b) => parseFloat(b.episodeNumber) - parseFloat(a.episodeNumber));
67
- }),
68
- dismiss: protectedProcedure.input(notificationDismissInput()).mutation(async ({ ctx, input }) => {
69
- await performWriteTransactionAsync(ctx.db, async (tx) => {
70
- await NotificationsRepository.addDismissed(tx, input.showId, input.episodeNumber);
71
- });
72
- return { success: true };
73
- }),
74
- clearAll: protectedProcedure.input(clearAllInput()).mutation(async ({ ctx, input }) => {
75
- await performWriteTransactionAsync(ctx.db, async (tx) => {
76
- await NotificationsRepository.dismissFromDiscovered(tx, input.showId);
77
- });
78
- return { success: true };
79
- }),
80
- });
1
+ import{protectedProcedure as a,router as l}from"../index.js";import h from"../../logger.js";import{performWriteTransactionAsync as d}from"../../sync.js";import{WatchlistRepository as f}from"../../repositories/watchlist.repository.js";import{WatchedEpisodesRepository as w}from"../../repositories/watched-episodes.repository.js";import{NotificationsRepository as n}from"../../repositories/notifications.repository.js";import{notificationDismissInput as g}from"../validation.js";function b(){return{"~standard":{version:1,vendor:"dango",types:void 0,validate(t){try{if(t==null)return{value:{}};if(typeof t!="object"||t===null)throw new Error("Expected an object");const e=t.showId;if(e==null)return{value:{}};if(typeof e!="string"||e.length===0)throw new Error("showId must be a string");return{value:{showId:e}}}catch(e){return{issues:[{message:e instanceof Error?e.message:"Invalid input"}]}}}}}}const C=l({list:a.query(async({ctx:t})=>{const e=await f.getWatchingShows(t.db),o=[];for(const i of e)try{const[r,u,m]=await Promise.all([w.getWatchedEpisodeNumbers(t.db,i.id),n.getDismissedByShow(t.db,i.id),n.getDiscoveredByShow(t.db,i.id)]),p=new Set(r.map(s=>s.toString())),c=new Set(u.map(s=>s.episodeNumber.toString()));for(const s of m)!p.has(s.episodeNumber)&&!c.has(s.episodeNumber)&&o.push({showId:i.id,name:i.name,nativeName:i.nativeName,englishName:i.englishName,thumbnail:i.thumbnail,episodeNumber:s.episodeNumber,id:`${i.id}-${s.episodeNumber}`})}catch(r){h.error({err:r,showId:i.id},"Failed to get notifications for show")}return o.sort((i,r)=>parseFloat(r.episodeNumber)-parseFloat(i.episodeNumber))}),dismiss:a.input(g()).mutation(async({ctx:t,input:e})=>(await d(t.db,async o=>{await n.addDismissed(o,e.showId,e.episodeNumber)}),{success:!0})),clearAll:a.input(b()).mutation(async({ctx:t,input:e})=>(await d(t.db,async o=>{await n.dismissFromDiscovered(o,e.showId)}),{success:!0}))});export{C as notificationsRouter};
@@ -1,109 +1 @@
1
- import { protectedProcedure, router } from '../index.js';
2
- import { WatchedEpisodesRepository } from '../../repositories/watched-episodes.repository.js';
3
- import { ShowsMetaRepository } from '../../repositories/shows-meta.repository.js';
4
- import { NotificationsRepository } from '../../repositories/notifications.repository.js';
5
- import { SettingsRepository } from '../../repositories/settings.repository.js';
6
- import { performWriteTransactionAsync } from '../../sync.js';
7
- import { getMigratedId } from '../../lib/migration.js';
8
- import { discordRPCService } from '../../discord-rpc.js';
9
- import { dbGet } from '../../utils/db-utils.js';
10
- import { episodeProgressInput, progressUpdateInput, showIdInput } from '../validation.js';
11
- async function showsMetaChanged(db, showId, candidate) {
12
- const existing = (await ShowsMetaRepository.getById(db, showId));
13
- if (!existing)
14
- return true;
15
- const differs = (incoming, stored) => {
16
- if (incoming === undefined || incoming === null || incoming === '')
17
- return false;
18
- return String(incoming) !== String(stored ?? '');
19
- };
20
- return (differs(candidate.name, existing.name) ||
21
- differs(candidate.thumbnail, existing.thumbnail) ||
22
- differs(candidate.nativeName, existing.nativeName) ||
23
- differs(candidate.englishName, existing.englishName) ||
24
- differs(candidate.genres, existing.genres) ||
25
- differs(candidate.popularityScore, existing.popularityScore) ||
26
- differs(candidate.status, existing.status) ||
27
- differs(candidate.episodeCount, existing.episodeCount) ||
28
- differs(candidate.type, existing.type) ||
29
- differs(candidate.anilistId, existing.anilistId) ||
30
- differs(candidate.isAdult, existing.isAdult) ||
31
- differs(candidate.episodeDuration, existing.episodeDuration));
32
- }
33
- export const progressRouter = router({
34
- getEpisode: protectedProcedure.input(episodeProgressInput()).query(async ({ ctx, input }) => {
35
- const showId = await getMigratedId(ctx.db, input.showId);
36
- const progress = await WatchedEpisodesRepository.getByShowAndEpisode(ctx.db, showId, input.episodeNumber);
37
- return progress || { currentTime: 0, duration: 0 };
38
- }),
39
- getWatchedEpisodes: protectedProcedure.input(showIdInput()).query(async ({ ctx, input }) => {
40
- const showId = await getMigratedId(ctx.db, input.showId);
41
- return WatchedEpisodesRepository.getWatchedEpisodeNumbers(ctx.db, showId);
42
- }),
43
- updateProgress: protectedProcedure
44
- .input(progressUpdateInput())
45
- .mutation(async ({ ctx, input }) => {
46
- const showId = await getMigratedId(ctx.db, input.showId);
47
- const { episodeNumber } = input;
48
- const currentTime = input.currentTime || 0;
49
- const duration = input.duration || 0;
50
- const titlePreferenceRow = await SettingsRepository.getByKey(ctx.db, 'titlePreference');
51
- const titlePreference = titlePreferenceRow ? titlePreferenceRow.value : 'englishName';
52
- let displayName = input.showName;
53
- if (titlePreference === 'englishName' && input.englishName) {
54
- displayName = input.englishName;
55
- }
56
- else if (titlePreference === 'nativeName' && input.nativeName) {
57
- displayName = input.nativeName;
58
- }
59
- discordRPCService.updatePresence({
60
- title: displayName,
61
- episode: String(episodeNumber),
62
- totalEpisodes: input.episodeCount ? String(input.episodeCount) : undefined,
63
- currentTime,
64
- duration,
65
- thumbnail: input.showThumbnail || '',
66
- isPlaying: input.isPlaying !== false,
67
- sessionId: input.sessionId,
68
- isAdult: input.isAdult,
69
- });
70
- const genresStr = Array.isArray(input.genres) ? JSON.stringify(input.genres) : input.genres;
71
- const anilistId = /^\d+$/.test(showId)
72
- ? (dbGet(ctx.db, 'SELECT anilistId FROM shows_meta WHERE id = ? AND anilistId IS NOT NULL', [showId])?.anilistId ?? parseInt(showId))
73
- : undefined;
74
- const metaCandidate = {
75
- name: input.showName,
76
- thumbnail: input.showThumbnail,
77
- nativeName: input.nativeName,
78
- englishName: input.englishName,
79
- genres: genresStr,
80
- popularityScore: input.popularityScore,
81
- status: input.status,
82
- episodeCount: input.episodeCount,
83
- type: input.type,
84
- anilistId,
85
- isAdult: typeof input.isAdult === 'boolean' ? (input.isAdult ? 1 : 0) : null,
86
- episodeDuration: duration > 0 ? Math.round(duration / 60) : undefined,
87
- };
88
- const metaChanged = await showsMetaChanged(ctx.db, showId, metaCandidate);
89
- if (metaChanged) {
90
- await performWriteTransactionAsync(ctx.db, async (tx) => {
91
- await ShowsMetaRepository.upsert(tx, {
92
- id: showId,
93
- ...metaCandidate,
94
- });
95
- });
96
- }
97
- await performWriteTransactionAsync(ctx.db, async (tx) => {
98
- await WatchedEpisodesRepository.upsert(tx, {
99
- showId,
100
- episodeNumber,
101
- currentTime,
102
- duration,
103
- });
104
- await NotificationsRepository.deleteSpecificDismissed(tx, showId, episodeNumber);
105
- await NotificationsRepository.deleteDiscovered(tx, showId, episodeNumber);
106
- });
107
- return { success: true };
108
- }),
109
- });
1
+ import{protectedProcedure as u,router as c}from"../index.js";import{WatchedEpisodesRepository as d}from"../../repositories/watched-episodes.repository.js";import{ShowsMetaRepository as h}from"../../repositories/shows-meta.repository.js";import{NotificationsRepository as b}from"../../repositories/notifications.repository.js";import{SettingsRepository as N}from"../../repositories/settings.repository.js";import{performWriteTransactionAsync as y}from"../../sync.js";import{getMigratedId as m}from"../../lib/migration.js";import{discordRPCService as I}from"../../discord-rpc.js";import{dbGet as S}from"../../utils/db-utils.js";import{episodeProgressInput as A,progressUpdateInput as C,showIdInput as v}from"../validation.js";async function P(n,e,s){const t=await h.getById(n,e);if(!t)return!0;const r=(i,a)=>i==null||i===""?!1:String(i)!==String(a??"");return r(s.name,t.name)||r(s.thumbnail,t.thumbnail)||r(s.nativeName,t.nativeName)||r(s.englishName,t.englishName)||r(s.genres,t.genres)||r(s.popularityScore,t.popularityScore)||r(s.status,t.status)||r(s.episodeCount,t.episodeCount)||r(s.type,t.type)||r(s.anilistId,t.anilistId)||r(s.isAdult,t.isAdult)||r(s.episodeDuration,t.episodeDuration)}const U=c({getEpisode:u.input(A()).query(async({ctx:n,input:e})=>{const s=await m(n.db,e.showId);return await d.getByShowAndEpisode(n.db,s,e.episodeNumber)||{currentTime:0,duration:0}}),getWatchedEpisodes:u.input(v()).query(async({ctx:n,input:e})=>{const s=await m(n.db,e.showId);return d.getWatchedEpisodeNumbers(n.db,s)}),updateProgress:u.input(C()).mutation(async({ctx:n,input:e})=>{const s=await m(n.db,e.showId),{episodeNumber:t}=e,r=e.currentTime||0,i=e.duration||0,a=await N.getByKey(n.db,"titlePreference"),g=a?a.value:"englishName";let l=e.showName;g==="englishName"&&e.englishName?l=e.englishName:g==="nativeName"&&e.nativeName&&(l=e.nativeName),I.updatePresence({title:l,episode:String(t),totalEpisodes:e.episodeCount?String(e.episodeCount):void 0,currentTime:r,duration:i,thumbnail:e.showThumbnail||"",isPlaying:e.isPlaying!==!1,sessionId:e.sessionId,isAdult:e.isAdult});const f=Array.isArray(e.genres)?JSON.stringify(e.genres):e.genres,w=/^\d+$/.test(s)?S(n.db,"SELECT anilistId FROM shows_meta WHERE id = ? AND anilistId IS NOT NULL",[s])?.anilistId??parseInt(s):void 0,p={name:e.showName,thumbnail:e.showThumbnail,nativeName:e.nativeName,englishName:e.englishName,genres:f,popularityScore:e.popularityScore,status:e.status,episodeCount:e.episodeCount,type:e.type,anilistId:w,isAdult:typeof e.isAdult=="boolean"?e.isAdult?1:0:null,episodeDuration:i>0?Math.round(i/60):void 0};return await P(n.db,s,p)&&await y(n.db,async o=>{await h.upsert(o,{id:s,...p})}),await y(n.db,async o=>{await d.upsert(o,{showId:s,episodeNumber:t,currentTime:r,duration:i}),await b.deleteSpecificDismissed(o,s,t),await b.deleteDiscovered(o,s,t)}),{success:!0}})});export{U as progressRouter};
@@ -1,93 +1 @@
1
- import { protectedProcedure, router } from '../index.js';
2
- import { QueueRepository } from '../../repositories/queue.repository.js';
3
- import { ShowsMetaRepository } from '../../repositories/shows-meta.repository.js';
4
- import { performWriteTransactionAsync } from '../../sync.js';
5
- import { getMigratedId } from '../../lib/migration.js';
6
- import { queueAddInput, queueBatchInput, queueRemoveInput, queueRemoveManyInput, queueReorderInput, } from '../validation.js';
7
- function applyMeta(meta) {
8
- return meta.showName || meta.showThumbnail || meta.nativeName || meta.englishName || meta.type;
9
- }
10
- export const queueRouter = router({
11
- list: protectedProcedure.query(async ({ ctx }) => {
12
- const rows = await QueueRepository.getAll(ctx.db);
13
- return rows.map((row) => ({
14
- ...row,
15
- _id: row.showId,
16
- id: row.id,
17
- thumbnail: row.thumbnail || '',
18
- }));
19
- }),
20
- add: protectedProcedure.input(queueAddInput()).mutation(async ({ ctx, input }) => {
21
- const showId = await getMigratedId(ctx.db, input.showId);
22
- const episodeNumber = input.episodeNumber;
23
- const existing = await QueueRepository.getByEpisode(ctx.db, showId, episodeNumber);
24
- await performWriteTransactionAsync(ctx.db, async (tx) => {
25
- if (applyMeta(input)) {
26
- await ShowsMetaRepository.upsert(tx, {
27
- id: showId,
28
- name: input.showName || '',
29
- thumbnail: input.showThumbnail || '',
30
- nativeName: input.nativeName,
31
- englishName: input.englishName,
32
- type: input.type,
33
- });
34
- }
35
- if (existing) {
36
- await QueueRepository.removeEpisode(tx, showId, episodeNumber);
37
- }
38
- else {
39
- await QueueRepository.addToEnd(tx, showId, episodeNumber);
40
- }
41
- });
42
- return { success: true, queued: !existing };
43
- }),
44
- addBatch: protectedProcedure.input(queueBatchInput()).mutation(async ({ ctx, input }) => {
45
- const showId = await getMigratedId(ctx.db, input.showId);
46
- const normalized = [...new Set(input.episodeNumbers)];
47
- await performWriteTransactionAsync(ctx.db, async (tx) => {
48
- if (applyMeta(input)) {
49
- await ShowsMetaRepository.upsert(tx, {
50
- id: showId,
51
- name: input.showName || '',
52
- thumbnail: input.showThumbnail || '',
53
- nativeName: input.nativeName,
54
- englishName: input.englishName,
55
- type: input.type,
56
- });
57
- }
58
- await QueueRepository.addManyToEnd(tx, normalized.map((episodeNumber) => ({ showId, episodeNumber })));
59
- });
60
- return { success: true, added: normalized.length };
61
- }),
62
- remove: protectedProcedure.input(queueRemoveInput()).mutation(async ({ ctx, input }) => {
63
- const showId = await getMigratedId(ctx.db, input.showId);
64
- await performWriteTransactionAsync(ctx.db, async (tx) => {
65
- await QueueRepository.removeEpisode(tx, showId, input.episodeNumber);
66
- });
67
- return { success: true };
68
- }),
69
- removeMany: protectedProcedure.input(queueRemoveManyInput()).mutation(async ({ ctx, input }) => {
70
- const showId = await getMigratedId(ctx.db, input.showId);
71
- let removed = [];
72
- await performWriteTransactionAsync(ctx.db, async (tx) => {
73
- removed = ((await QueueRepository.getByShow(tx, showId)) || []).map((ep) => ep.episodeNumber);
74
- const toRemove = Array.isArray(input.episodeNumbers) && input.episodeNumbers.length
75
- ? [...new Set(input.episodeNumbers)]
76
- : removed;
77
- await QueueRepository.removeMany(tx, showId, toRemove);
78
- });
79
- return { success: true, removed: removed.length };
80
- }),
81
- clear: protectedProcedure.mutation(async ({ ctx }) => {
82
- await performWriteTransactionAsync(ctx.db, async (tx) => {
83
- await QueueRepository.clear(tx);
84
- });
85
- return { success: true };
86
- }),
87
- reorder: protectedProcedure.input(queueReorderInput()).mutation(async ({ ctx, input }) => {
88
- await performWriteTransactionAsync(ctx.db, async (tx) => {
89
- await QueueRepository.reorder(tx, input);
90
- });
91
- return { success: true };
92
- }),
93
- });
1
+ import{protectedProcedure as n,router as h}from"../index.js";import{QueueRepository as o}from"../../repositories/queue.repository.js";import{ShowsMetaRepository as m}from"../../repositories/shows-meta.repository.js";import{performWriteTransactionAsync as d}from"../../sync.js";import{getMigratedId as u}from"../../lib/migration.js";import{queueAddInput as y,queueBatchInput as b,queueRemoveInput as p,queueRemoveManyInput as l,queueReorderInput as N}from"../validation.js";function w(a){return a.showName||a.showThumbnail||a.nativeName||a.englishName||a.type}const R=h({list:n.query(async({ctx:a})=>(await o.getAll(a.db)).map(t=>({...t,_id:t.showId,id:t.id,thumbnail:t.thumbnail||""}))),add:n.input(y()).mutation(async({ctx:a,input:e})=>{const t=await u(a.db,e.showId),s=e.episodeNumber,r=await o.getByEpisode(a.db,t,s);return await d(a.db,async i=>{w(e)&&await m.upsert(i,{id:t,name:e.showName||"",thumbnail:e.showThumbnail||"",nativeName:e.nativeName,englishName:e.englishName,type:e.type}),r?await o.removeEpisode(i,t,s):await o.addToEnd(i,t,s)}),{success:!0,queued:!r}}),addBatch:n.input(b()).mutation(async({ctx:a,input:e})=>{const t=await u(a.db,e.showId),s=[...new Set(e.episodeNumbers)];return await d(a.db,async r=>{w(e)&&await m.upsert(r,{id:t,name:e.showName||"",thumbnail:e.showThumbnail||"",nativeName:e.nativeName,englishName:e.englishName,type:e.type}),await o.addManyToEnd(r,s.map(i=>({showId:t,episodeNumber:i})))}),{success:!0,added:s.length}}),remove:n.input(p()).mutation(async({ctx:a,input:e})=>{const t=await u(a.db,e.showId);return await d(a.db,async s=>{await o.removeEpisode(s,t,e.episodeNumber)}),{success:!0}}),removeMany:n.input(l()).mutation(async({ctx:a,input:e})=>{const t=await u(a.db,e.showId);let s=[];return await d(a.db,async r=>{s=(await o.getByShow(r,t)||[]).map(c=>c.episodeNumber);const i=Array.isArray(e.episodeNumbers)&&e.episodeNumbers.length?[...new Set(e.episodeNumbers)]:s;await o.removeMany(r,t,i)}),{success:!0,removed:s.length}}),clear:n.mutation(async({ctx:a})=>(await d(a.db,async e=>{await o.clear(e)}),{success:!0})),reorder:n.input(N()).mutation(async({ctx:a,input:e})=>(await d(a.db,async t=>{await o.reorder(t,e)}),{success:!0}))});export{R as queueRouter};
@@ -1,145 +1 @@
1
- import { protectedProcedure, router } from '../index.js';
2
- import { defineSchema, optStr, reqObj } from '../validation.js';
3
- import logger from '../../logger.js';
4
- import { parseJsonBody } from '../../utils/http.utils.js';
5
- const LISTEN_MOE_STATIONS = [
6
- {
7
- id: 'listenmoe-jpop',
8
- name: 'LISTEN.moe JPOP',
9
- streamUrl: 'https://listen.moe/fallback',
10
- homepage: 'https://listen.moe/',
11
- tags: 'anime,j-pop,jpop',
12
- codec: 'MP3',
13
- source: 'listen.moe',
14
- gateway: 'wss://listen.moe/gateway_v2',
15
- },
16
- {
17
- id: 'listenmoe-kpop',
18
- name: 'LISTEN.moe KPOP',
19
- streamUrl: 'https://listen.moe/kpop/fallback',
20
- homepage: 'https://listen.moe/',
21
- tags: 'k-pop,kpop',
22
- codec: 'MP3',
23
- source: 'listen.moe',
24
- gateway: 'wss://listen.moe/kpop/gateway_v2',
25
- },
26
- ];
27
- const FEATURED_STATIONS = [
28
- {
29
- id: 'stereo-anime',
30
- name: 'stereo anime',
31
- streamUrl: 'https://radio.stereoanime.com/listen/stereoanime/128',
32
- tags: 'anime',
33
- codec: 'MP3',
34
- bitrate: 128,
35
- source: 'radio-browser',
36
- },
37
- {
38
- id: 'jpop-sakura',
39
- name: 'J-Pop Sakura (asia DREAM radio)',
40
- streamUrl: 'https://quincy.torontocast.com:2070/stream.mp3',
41
- tags: 'j-pop,jpop',
42
- codec: 'MP3',
43
- bitrate: 128,
44
- source: 'radio-browser',
45
- },
46
- {
47
- id: 'n-kpop',
48
- name: '0 N - K-Pop on Radio',
49
- streamUrl: 'https://0n-kpop.radionetz.de/0n-kpop.mp3',
50
- tags: 'k-pop,kpop',
51
- codec: 'MP3',
52
- bitrate: 128,
53
- source: 'radio-browser',
54
- },
55
- {
56
- id: 'bigb-kpop',
57
- name: 'Big B Radio - Kpop',
58
- streamUrl: 'https://antares.dribbcast.com/proxy/kpop?mp=/s',
59
- tags: 'k-pop,kpop',
60
- codec: 'MP3',
61
- bitrate: 192,
62
- source: 'radio-browser',
63
- },
64
- ];
65
- const RB_MIRRORS = ['https://de1.api.radio-browser.info', 'https://nl1.api.radio-browser.info'];
66
- async function rbFetch(path) {
67
- let lastErr = null;
68
- for (const base of RB_MIRRORS) {
69
- try {
70
- const res = await fetch(`${base}${path}`, {
71
- headers: { 'User-Agent': 'dango/1.0' },
72
- signal: AbortSignal.timeout(10000),
73
- });
74
- if (!res.ok)
75
- throw new Error(`radio-browser ${res.status}`);
76
- return parseJsonBody(res);
77
- }
78
- catch (err) {
79
- lastErr = err;
80
- }
81
- }
82
- throw lastErr;
83
- }
84
- function toStation(s) {
85
- const url = s.url_resolved || '';
86
- if (!url.startsWith('https://'))
87
- return null;
88
- return {
89
- id: s.stationuuid || url,
90
- name: s.name || 'Unknown station',
91
- streamUrl: url,
92
- homepage: s.homepage || undefined,
93
- favicon: s.favicon && s.favicon.startsWith('https://') ? s.favicon : undefined,
94
- tags: s.tags || undefined,
95
- codec: s.codec || undefined,
96
- bitrate: s.bitrate || undefined,
97
- source: 'radio-browser',
98
- };
99
- }
100
- const radioSearchInput = () => defineSchema((value) => {
101
- const obj = reqObj(value);
102
- const out = {};
103
- const q = optStr(obj, 'q');
104
- if (q !== undefined)
105
- out.q = q;
106
- return out;
107
- });
108
- export const radioRouter = router({
109
- stations: protectedProcedure.query(async ({ ctx }) => {
110
- try {
111
- const cached = ctx.apiCache.get('route-radio-stations');
112
- if (cached)
113
- return { stations: cached };
114
- const stations = [...LISTEN_MOE_STATIONS, ...FEATURED_STATIONS];
115
- ctx.apiCache.set('route-radio-stations', stations, 3600);
116
- return { stations };
117
- }
118
- catch (err) {
119
- logger.error({ err }, '[Radio] stations failed');
120
- return { stations: LISTEN_MOE_STATIONS };
121
- }
122
- }),
123
- search: protectedProcedure.input(radioSearchInput()).query(async ({ ctx, input }) => {
124
- try {
125
- const q = String(input.q || '').trim();
126
- if (!q)
127
- return { stations: [] };
128
- const cacheKey = `route-radio-search-${q.toLowerCase()}`;
129
- const cached = ctx.apiCache.get(cacheKey);
130
- if (cached)
131
- return { stations: cached };
132
- const raw = (await rbFetch(`/json/stations/search?name=${encodeURIComponent(q)}&hidebroken=true&order=clickcount&reverse=true&limit=25`));
133
- const stations = raw
134
- .map(toStation)
135
- .filter((s) => s !== null)
136
- .slice(0, 25);
137
- ctx.apiCache.set(cacheKey, stations, 300);
138
- return { stations };
139
- }
140
- catch (err) {
141
- logger.error({ err }, '[Radio] search failed');
142
- return { stations: [] };
143
- }
144
- }),
145
- });
1
+ import{protectedProcedure as i,router as d}from"../index.js";import{defineSchema as m,optStr as u,reqObj as g}from"../validation.js";import s from"../../logger.js";import{parseJsonBody as l}from"../../utils/http.utils.js";const c=[{id:"listenmoe-jpop",name:"LISTEN.moe JPOP",streamUrl:"https://listen.moe/fallback",homepage:"https://listen.moe/",tags:"anime,j-pop,jpop",codec:"MP3",source:"listen.moe",gateway:"wss://listen.moe/gateway_v2"},{id:"listenmoe-kpop",name:"LISTEN.moe KPOP",streamUrl:"https://listen.moe/kpop/fallback",homepage:"https://listen.moe/",tags:"k-pop,kpop",codec:"MP3",source:"listen.moe",gateway:"wss://listen.moe/kpop/gateway_v2"}],h=[{id:"stereo-anime",name:"stereo anime",streamUrl:"https://radio.stereoanime.com/listen/stereoanime/128",tags:"anime",codec:"MP3",bitrate:128,source:"radio-browser"},{id:"jpop-sakura",name:"J-Pop Sakura (asia DREAM radio)",streamUrl:"https://quincy.torontocast.com:2070/stream.mp3",tags:"j-pop,jpop",codec:"MP3",bitrate:128,source:"radio-browser"},{id:"n-kpop",name:"0 N - K-Pop on Radio",streamUrl:"https://0n-kpop.radionetz.de/0n-kpop.mp3",tags:"k-pop,kpop",codec:"MP3",bitrate:128,source:"radio-browser"},{id:"bigb-kpop",name:"Big B Radio - Kpop",streamUrl:"https://antares.dribbcast.com/proxy/kpop?mp=/s",tags:"k-pop,kpop",codec:"MP3",bitrate:192,source:"radio-browser"}],f=["https://de1.api.radio-browser.info","https://nl1.api.radio-browser.info"];async function b(t){let e=null;for(const o of f)try{const r=await fetch(`${o}${t}`,{headers:{"User-Agent":"dango/1.0"},signal:AbortSignal.timeout(1e4)});if(!r.ok)throw new Error(`radio-browser ${r.status}`);return l(r)}catch(r){e=r}throw e}function S(t){const e=t.url_resolved||"";return e.startsWith("https://")?{id:t.stationuuid||e,name:t.name||"Unknown station",streamUrl:e,homepage:t.homepage||void 0,favicon:t.favicon&&t.favicon.startsWith("https://")?t.favicon:void 0,tags:t.tags||void 0,codec:t.codec||void 0,bitrate:t.bitrate||void 0,source:"radio-browser"}:null}const w=()=>m(t=>{const e=g(t),o={},r=u(e,"q");return r!==void 0&&(o.q=r),o}),U=d({stations:i.query(async({ctx:t})=>{try{const e=t.apiCache.get("route-radio-stations");if(e)return{stations:e};const o=[...c,...h];return t.apiCache.set("route-radio-stations",o,3600),{stations:o}}catch(e){return s.error({err:e},"[Radio] stations failed"),{stations:c}}}),search:i.input(w()).query(async({ctx:t,input:e})=>{try{const o=String(e.q||"").trim();if(!o)return{stations:[]};const r=`route-radio-search-${o.toLowerCase()}`,a=t.apiCache.get(r);if(a)return{stations:a};const n=(await b(`/json/stations/search?name=${encodeURIComponent(o)}&hidebroken=true&order=clickcount&reverse=true&limit=25`)).map(S).filter(p=>p!==null).slice(0,25);return t.apiCache.set(r,n,300),{stations:n}}catch(o){return s.error({err:o},"[Radio] search failed"),{stations:[]}}})});export{U as radioRouter};
@@ -1,96 +1 @@
1
- import { TRPCError } from '@trpc/server';
2
- import { protectedProcedure, router } from '../index.js';
3
- import { SettingsRepository } from '../../repositories/settings.repository.js';
4
- import { performWriteTransactionAsync } from '../../sync.js';
5
- import { discordRPCService } from '../../discord-rpc.js';
6
- import logger from '../../logger.js';
7
- import { offlineDb } from '../../lib/offline-db.js';
8
- import { translateTexts } from '../../lib/translate.js';
9
- import { getMalImportStatus, requestMalImportCancel } from '../../lib/mal-import.js';
10
- import { defineSchema, reqObj } from '../validation.js';
11
- import { keyInput, setSettingInput } from '../validation.js';
12
- export const SETTING_DEFAULTS = {
13
- discordRPCEnabled: 'true',
14
- discordRPCHideMature: 'true',
15
- dangoGaze: 'true',
16
- ignoreAdultContent: 'true',
17
- mangaIgnoreAdultContent: 'true',
18
- tvIgnoreAdultContent: 'true',
19
- asmrIgnoreAdultContent: 'true',
20
- };
21
- const autoUpdateInput = () => defineSchema((value) => {
22
- const obj = reqObj(value);
23
- if (typeof obj.enabled !== 'boolean')
24
- throw new Error('enabled must be a boolean');
25
- return { enabled: obj.enabled };
26
- });
27
- const translateInput = () => defineSchema((value) => {
28
- if (value === undefined || value === null)
29
- return {};
30
- const obj = reqObj(value);
31
- return { texts: obj.texts, source: obj.source, target: obj.target };
32
- });
33
- export const settingsRouter = router({
34
- getByKey: protectedProcedure.input(keyInput()).query(async ({ ctx, input }) => {
35
- const row = await SettingsRepository.getByKey(ctx.db, input.key);
36
- const value = row ? row.value : (SETTING_DEFAULTS[input.key] ?? null);
37
- return { value };
38
- }),
39
- set: protectedProcedure.input(setSettingInput()).mutation(async ({ ctx, input }) => {
40
- const key = input.key;
41
- const value = String(input.value ?? '');
42
- const shouldDelete = value === '' && key === 'tracker_anilist_client_id';
43
- await performWriteTransactionAsync(ctx.db, async (tx) => {
44
- if (shouldDelete)
45
- await SettingsRepository.deleteByKey(tx, key);
46
- else
47
- await SettingsRepository.upsert(tx, key, value);
48
- });
49
- if (key === 'discordRPCEnabled') {
50
- discordRPCService.setEnabled(input.value === 'true' || input.value === true);
51
- }
52
- if (key === 'discordRPCHideMature') {
53
- discordRPCService.setHideMature(input.value === 'true' || input.value === true);
54
- }
55
- return { success: true };
56
- }),
57
- offlineDbInfo: protectedProcedure.query(async ({ ctx }) => {
58
- return await offlineDb.getOfflineDbInfo(ctx.db);
59
- }),
60
- setOfflineDbAutoUpdate: protectedProcedure
61
- .input(autoUpdateInput())
62
- .mutation(async ({ ctx, input }) => {
63
- await SettingsRepository.upsert(ctx.db, 'offlineDbAutoUpdateEnabled', String(input.enabled));
64
- return { success: true, enabled: input.enabled };
65
- }),
66
- triggerOfflineDbUpdate: protectedProcedure.mutation(async ({ ctx }) => {
67
- const info = await offlineDb.getOfflineDbInfo(ctx.db);
68
- if (info.isRefreshing) {
69
- throw new TRPCError({
70
- code: 'CONFLICT',
71
- message: 'Offline database refresh already in progress',
72
- });
73
- }
74
- offlineDb.refreshDatabase(ctx.db).catch((err) => {
75
- logger.warn({ err: err?.message }, 'Manual offline database update failed');
76
- });
77
- return { message: 'Offline database refresh started' };
78
- }),
79
- translate: protectedProcedure.input(translateInput()).mutation(async ({ input }) => {
80
- const texts = input.texts;
81
- const source = input.source ?? 'ja';
82
- const target = input.target ?? 'en';
83
- if (!Array.isArray(texts) || texts.length === 0) {
84
- return { translations: {} };
85
- }
86
- return {
87
- translations: await translateTexts(texts, String(source), String(target)),
88
- };
89
- }),
90
- malImportStatus: protectedProcedure.query(() => {
91
- return getMalImportStatus();
92
- }),
93
- malImportCancel: protectedProcedure.mutation(() => {
94
- return requestMalImportCancel();
95
- }),
96
- });
1
+ import{TRPCError as c}from"@trpc/server";import{protectedProcedure as n,router as m}from"../index.js";import{SettingsRepository as o}from"../../repositories/settings.repository.js";import{performWriteTransactionAsync as b}from"../../sync.js";import{discordRPCService as i}from"../../discord-rpc.js";import g from"../../logger.js";import{offlineDb as s}from"../../lib/offline-db.js";import{translateTexts as p}from"../../lib/translate.js";import{getMalImportStatus as y,requestMalImportCancel as I}from"../../lib/mal-import.js";import{defineSchema as l,reqObj as d}from"../validation.js";import{keyInput as w,setSettingInput as C}from"../validation.js";const S={discordRPCEnabled:"true",discordRPCHideMature:"true",dangoGaze:"true",ignoreAdultContent:"true",mangaIgnoreAdultContent:"true",tvIgnoreAdultContent:"true",asmrIgnoreAdultContent:"true"},h=()=>l(t=>{const e=d(t);if(typeof e.enabled!="boolean")throw new Error("enabled must be a boolean");return{enabled:e.enabled}}),A=()=>l(t=>{if(t==null)return{};const e=d(t);return{texts:e.texts,source:e.source,target:e.target}}),U=m({getByKey:n.input(w()).query(async({ctx:t,input:e})=>{const r=await o.getByKey(t.db,e.key);return{value:r?r.value:S[e.key]??null}}),set:n.input(C()).mutation(async({ctx:t,input:e})=>{const r=e.key,a=String(e.value??""),f=a===""&&r==="tracker_anilist_client_id";return await b(t.db,async u=>{f?await o.deleteByKey(u,r):await o.upsert(u,r,a)}),r==="discordRPCEnabled"&&i.setEnabled(e.value==="true"||e.value===!0),r==="discordRPCHideMature"&&i.setHideMature(e.value==="true"||e.value===!0),{success:!0}}),offlineDbInfo:n.query(async({ctx:t})=>await s.getOfflineDbInfo(t.db)),setOfflineDbAutoUpdate:n.input(h()).mutation(async({ctx:t,input:e})=>(await o.upsert(t.db,"offlineDbAutoUpdateEnabled",String(e.enabled)),{success:!0,enabled:e.enabled})),triggerOfflineDbUpdate:n.mutation(async({ctx:t})=>{if((await s.getOfflineDbInfo(t.db)).isRefreshing)throw new c({code:"CONFLICT",message:"Offline database refresh already in progress"});return s.refreshDatabase(t.db).catch(r=>{g.warn({err:r?.message},"Manual offline database update failed")}),{message:"Offline database refresh started"}}),translate:n.input(A()).mutation(async({input:t})=>{const e=t.texts,r=t.source??"ja",a=t.target??"en";return!Array.isArray(e)||e.length===0?{translations:{}}:{translations:await p(e,String(r),String(a))}}),malImportStatus:n.query(()=>y()),malImportCancel:n.mutation(()=>I())});export{S as SETTING_DEFAULTS,U as settingsRouter};