@serifpersia/dango 3.6.8 → 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 (154) hide show
  1. package/README.md +1 -1
  2. package/client/dist/assets/{AnimeCard-DraA_TF9.js → AnimeCard-DKDJK8Mp.js} +1 -1
  3. package/client/dist/assets/{AnimeInfo-BJ7pzs-P.js → AnimeInfo-CTcvfjva.js} +1 -1
  4. package/client/dist/assets/{AnimeInfoPage-B_esUr5C.js → AnimeInfoPage-B_UiacIN.js} +2 -2
  5. package/client/dist/assets/{Asmr-Ckuh2xfW.js → Asmr-DMSN2vxl.js} +2 -2
  6. package/client/dist/assets/{AsmrResetProgressModal-BS6UFJN4.js → AsmrResetProgressModal-CRwfOatX.js} +1 -1
  7. package/client/dist/assets/{AvSyncCalibrator-DwHCd9yB.js → AvSyncCalibrator-CRJsi-vz.js} +1 -1
  8. package/client/dist/assets/{Home-DuM1EuvN.js → Home-pwmK9_Eh.js} +1 -1
  9. package/client/dist/assets/{Insights-D3Pd9c9V.js → Insights-m7kFzg2A.js} +1 -1
  10. package/client/dist/assets/{LibraryListPage-Dm2yVn0-.js → LibraryListPage-C1wpPvu6.js} +1 -1
  11. package/client/dist/assets/ListeningList-OzfEI1Ga.js +1 -0
  12. package/client/dist/assets/{Manga-JwprMKBk.js → Manga-ChRCExhK.js} +1 -1
  13. package/client/dist/assets/{MangaInfoPage-DaeyJl2p.js → MangaInfoPage-DSHJPo2A.js} +1 -1
  14. package/client/dist/assets/{MangaReadPage-COg0fHI9.js → MangaReadPage-CzOVY1wA.js} +1 -1
  15. package/client/dist/assets/{MangaResetProgressModal-kguXv0_M.js → MangaResetProgressModal-CJfzdUBb.js} +1 -1
  16. package/client/dist/assets/{Mature-IHDaasrs.js → Mature-CifQMfhs.js} +1 -1
  17. package/client/dist/assets/{MediaCard-CpwsrXo5.js → MediaCard-CM10yB9V.js} +1 -1
  18. package/client/dist/assets/{OptionListMenu-11CcAsy8.js → OptionListMenu-6Kovbsqw.js} +1 -1
  19. package/client/dist/assets/{Player-DQoEQkHL.js → Player-CK0hT-u4.js} +2 -2
  20. package/client/dist/assets/{PlayerSettings-D4Ud4kv4.js → PlayerSettings-Bb66uqT9.js} +1 -1
  21. package/client/dist/assets/{QueueOptionsButton-B8PlPKjC.js → QueueOptionsButton-C_nTj8_q.js} +1 -1
  22. package/client/dist/assets/{QueueRail-B4Dok7U3.js → QueueRail-DUK7URAE.js} +1 -1
  23. package/client/dist/assets/{ReadingList-BCsxIz57.js → ReadingList-Ck9LJThU.js} +1 -1
  24. package/client/dist/assets/{ResetProgressModal-BXHxSfy-.js → ResetProgressModal-BUphIv0k.js} +1 -1
  25. package/client/dist/assets/{Search-PK1ds07O.js → Search-zJyP15h6.js} +1 -1
  26. package/client/dist/assets/{Settings-Bxk_x55l.js → Settings-BYdoSEUh.js} +1 -1
  27. package/client/dist/assets/{SkeletonGrid-CpJCZSRN.js → SkeletonGrid-CqgbOhjX.js} +1 -1
  28. package/client/dist/assets/{Trackers-RieCh_84.js → Trackers-BVjyv6CM.js} +1 -1
  29. package/client/dist/assets/{Tv-DLytPJUj.js → Tv-L13ylswR.js} +1 -1
  30. package/client/dist/assets/{TvInfo-C8djKebR.js → TvInfo-Cvcm5U2D.js} +1 -1
  31. package/client/dist/assets/{TvPopup-40JGGJDk.js → TvPopup-DhxfKvYm.js} +1 -1
  32. package/client/dist/assets/{TvSearch-A3hbospf.js → TvSearch-P8bFE6_X.js} +1 -1
  33. package/client/dist/assets/{TvWatchlist-DuXjqmZM.js → TvWatchlist-D5gVPjtb.js} +1 -1
  34. package/client/dist/assets/{Watchlist-B9wGBh0u.js → Watchlist-Cjq8l3rU.js} +1 -1
  35. package/client/dist/assets/{hls-CZ5X3Ya-.js → hls-BkzuANAg.js} +1 -1
  36. package/client/dist/assets/{index-dRhc9-ol.js → index-CLVgzKLk.js} +19 -19
  37. package/client/dist/assets/{useAnimeInfoData-ZzDtLfvD.js → useAnimeInfoData-DZJZu-so.js} +1 -1
  38. package/client/dist/assets/{useAsmr-CKOdHgol.js → useAsmr-CvrMGHuo.js} +1 -1
  39. package/client/dist/assets/useAsmrPopup-Bimy8Ouw.js +1 -0
  40. package/client/dist/assets/{useMangaLibrary-CKATe31P.js → useMangaLibrary--MiN11uR.js} +1 -1
  41. package/client/dist/assets/{useMangaPopup-C75QuYgY.js → useMangaPopup-CR5XhKKS.js} +1 -1
  42. package/client/dist/assets/{useProviders-CMtvZbEi.js → useProviders-DqdT4fA5.js} +1 -1
  43. package/client/dist/assets/{useTv-DUX4zYMr.js → useTv-BL0HMDyP.js} +1 -1
  44. package/client/dist/assets/{useTvLibrary-By4Gt-R_.js → useTvLibrary-D4U4iqN8.js} +1 -1
  45. package/client/dist/index.html +1 -1
  46. package/package.json +3 -5
  47. package/server/dist/server.js +1089 -1
  48. package/server/package.json +2 -4
  49. package/client/dist/assets/Asmr.module-CdWlioTY.js +0 -1
  50. package/client/dist/assets/ListeningList-D-zt6EXZ.js +0 -1
  51. package/client/dist/assets/Music-BGH_0voi.css +0 -1
  52. package/client/dist/assets/Music-Dks4tPeK.js +0 -1
  53. package/client/dist/assets/Radio-BHK6LSk4.js +0 -1
  54. package/client/dist/assets/Radio-CCHNFN7R.css +0 -1
  55. package/client/dist/assets/Radio.module-DGSZLpKM.js +0 -1
  56. package/client/dist/assets/useAsmrPopup-SFHPhpcl.js +0 -1
  57. package/server/dist/app-auth.js +0 -1
  58. package/server/dist/app-hono.js +0 -48
  59. package/server/dist/db/drizzle.js +0 -1
  60. package/server/dist/db/schema-anime.js +0 -1
  61. package/server/dist/db/schema-asmr.js +0 -1
  62. package/server/dist/db/schema-manga.js +0 -1
  63. package/server/dist/db/schema-tv.js +0 -1
  64. package/server/dist/db.js +0 -1
  65. package/server/dist/discord-gateway.js +0 -1
  66. package/server/dist/discord-rpc.js +0 -1
  67. package/server/dist/github-sync.js +0 -1
  68. package/server/dist/google.js +0 -1
  69. package/server/dist/hono/asmr.js +0 -1
  70. package/server/dist/hono/auth.js +0 -26
  71. package/server/dist/hono/music.js +0 -1
  72. package/server/dist/hono/proxy.js +0 -80
  73. package/server/dist/hono/tv.js +0 -5
  74. package/server/dist/hono/watchlist.js +0 -5
  75. package/server/dist/lib/anilist.js +0 -151
  76. package/server/dist/lib/anischedule.js +0 -1
  77. package/server/dist/lib/insights.js +0 -1
  78. package/server/dist/lib/ipc.js +0 -1
  79. package/server/dist/lib/kitsu.js +0 -1
  80. package/server/dist/lib/mal-import.js +0 -1
  81. package/server/dist/lib/mal.js +0 -1
  82. package/server/dist/lib/migration.js +0 -1
  83. package/server/dist/lib/offline-db.js +0 -15
  84. package/server/dist/lib/potoken.js +0 -1
  85. package/server/dist/lib/recommendations.service.js +0 -1
  86. package/server/dist/lib/temp-ids.js +0 -1
  87. package/server/dist/lib/tmdb.js +0 -1
  88. package/server/dist/lib/tracker/anilist-tracker.js +0 -71
  89. package/server/dist/lib/tracker/manga-sync.service.js +0 -1
  90. package/server/dist/lib/tracker/sync-state.js +0 -1
  91. package/server/dist/lib/tracker/sync.service.js +0 -1
  92. package/server/dist/lib/translate.js +0 -3
  93. package/server/dist/lib/ump.js +0 -1
  94. package/server/dist/lib/ytmusic.js +0 -1
  95. package/server/dist/logger.js +0 -1
  96. package/server/dist/providers/manga/manga.types.js +0 -0
  97. package/server/dist/providers/provider.interface.js +0 -0
  98. package/server/dist/providers/remote-loader.js +0 -2
  99. package/server/dist/providers/remote-types.js +0 -1
  100. package/server/dist/providers/title-matching.js +0 -1
  101. package/server/dist/providers/tv.types.js +0 -0
  102. package/server/dist/rclone.js +0 -2
  103. package/server/dist/repositories/asmr.repository.js +0 -42
  104. package/server/dist/repositories/insights.repository.js +0 -92
  105. package/server/dist/repositories/library.repository.js +0 -3
  106. package/server/dist/repositories/mal-cache.repository.js +0 -1
  107. package/server/dist/repositories/manga.repository.js +0 -51
  108. package/server/dist/repositories/notifications.repository.js +0 -14
  109. package/server/dist/repositories/queue.repository.js +0 -19
  110. package/server/dist/repositories/settings.repository.js +0 -3
  111. package/server/dist/repositories/shows-meta.repository.js +0 -5
  112. package/server/dist/repositories/temp-show-ids.repository.js +0 -3
  113. package/server/dist/repositories/tv.repository.js +0 -50
  114. package/server/dist/repositories/watched-episodes.repository.js +0 -33
  115. package/server/dist/repositories/watchlist.repository.js +0 -9
  116. package/server/dist/scripts/backfill-episode-duration.js +0 -1
  117. package/server/dist/sync-payload.js +0 -1
  118. package/server/dist/sync.js +0 -13
  119. package/server/dist/trpc/context.js +0 -1
  120. package/server/dist/trpc/errors.js +0 -1
  121. package/server/dist/trpc/index.js +0 -1
  122. package/server/dist/trpc/router.js +0 -1
  123. package/server/dist/trpc/routers/asmr-library.js +0 -1
  124. package/server/dist/trpc/routers/asmr-progress.js +0 -1
  125. package/server/dist/trpc/routers/asmr.js +0 -1
  126. package/server/dist/trpc/routers/auth.js +0 -1
  127. package/server/dist/trpc/routers/continue-watching.js +0 -25
  128. package/server/dist/trpc/routers/data.js +0 -1
  129. package/server/dist/trpc/routers/discord.js +0 -1
  130. package/server/dist/trpc/routers/discovery.js +0 -1
  131. package/server/dist/trpc/routers/insights.js +0 -1
  132. package/server/dist/trpc/routers/manga-library.js +0 -1
  133. package/server/dist/trpc/routers/manga-progress.js +0 -1
  134. package/server/dist/trpc/routers/manga.js +0 -1
  135. package/server/dist/trpc/routers/music.js +0 -1
  136. package/server/dist/trpc/routers/notifications.js +0 -1
  137. package/server/dist/trpc/routers/progress.js +0 -1
  138. package/server/dist/trpc/routers/queue.js +0 -1
  139. package/server/dist/trpc/routers/radio.js +0 -1
  140. package/server/dist/trpc/routers/settings.js +0 -1
  141. package/server/dist/trpc/routers/tracker.js +0 -1
  142. package/server/dist/trpc/routers/tv-library.js +0 -1
  143. package/server/dist/trpc/routers/tv-progress.js +0 -1
  144. package/server/dist/trpc/routers/tv.js +0 -1
  145. package/server/dist/trpc/routers/watchlist.js +0 -3
  146. package/server/dist/trpc/validation.js +0 -1
  147. package/server/dist/utils/cache.utils.js +0 -1
  148. package/server/dist/utils/cookie.utils.js +0 -1
  149. package/server/dist/utils/db-utils.js +0 -5
  150. package/server/dist/utils/env.utils.js +0 -5
  151. package/server/dist/utils/http.utils.js +0 -1
  152. package/server/dist/utils/request-context.js +0 -1
  153. package/server/dist/utils/security.utils.js +0 -1
  154. /package/client/dist/assets/{Asmr-D1KuQwQy.css → useAsmrPopup-D1KuQwQy.css} +0 -0
@@ -1 +0,0 @@
1
- import{protectedProcedure as s,router as p}from"../index.js";import{performAsmrWriteTransactionAsync as m}from"../../sync.js";import{adultNonListIds as w}from"../../utils/db-utils.js";import{SettingsRepository as b}from"../../repositories/settings.repository.js";import{AsmrLibraryRepository as y,AsmrProgressRepository as o}from"../../repositories/asmr.repository.js";import{defineSchema as i,reqObj as u,reqStr as l,reqStrArray as A,strOrNull as c}from"../validation.js";const I=t=>w(t,"asmr_progress","asmr_library","workId","COALESCE(l.isAdult, p.isAdult) = 1"),f=()=>i(t=>({ids:A(u(t),"ids",!0)})),k=()=>i(t=>({workId:l(u(t),"workId")})),h=()=>i(t=>{const r=u(t);return{workId:l(r,"workId"),trackIndex:r.trackIndex,trackLabel:r.trackLabel,currentTime:r.currentTime,duration:r.duration,title:c(r,"title"),thumbnail:c(r,"thumbnail"),rjCode:c(r,"rjCode"),isAdult:r.isAdult??null}}),L=()=>i(t=>{const r=u(t);return{workId:l(r,"workId"),trackIndex:r.trackIndex??void 0}}),P=()=>i(t=>{const r=u(t),e={};if(r.limit!==void 0&&r.limit!==null&&r.limit!==""){const n=Number(r.limit);Number.isFinite(n)&&(e.limit=n)}return e}),D=p({getByWork:s.input(k()).query(async({ctx:t,input:r})=>({progress:await o.getByWork(t.asmrDb,r.workId)})),latest:s.input(k()).query(async({ctx:t,input:r})=>await o.getLatest(t.asmrDb,r.workId)||{currentTime:0,duration:0}),save:s.input(h()).mutation(async({ctx:t,input:r})=>{const e=Math.max(Math.round(Number(r.trackIndex))||0,0),n=Math.max(Number(r.currentTime)||0,0),d=Math.max(Number(r.duration)||0,0);return await m(t.asmrDb,async a=>{await o.upsert(a,{workId:String(r.workId),trackIndex:e,trackLabel:String(r.trackLabel||""),currentTime:n,duration:d,title:r.title??null,thumbnail:r.thumbnail??null,rjCode:r.rjCode??null,isAdult:r.isAdult!=null?Number(r.isAdult):null}),await y.touchProgress(a,String(r.workId),{trackIndex:e,trackLabel:String(r.trackLabel||""),position:n})}),{success:!0}}),remove:s.input(L()).mutation(async({ctx:t,input:r})=>(await m(t.asmrDb,async e=>{r.trackIndex!==void 0&&r.trackIndex!==null?await o.deleteTrack(e,String(r.workId),Number(r.trackIndex)):await o.deleteByWork(e,String(r.workId))}),{success:!0})),removeMany:s.input(f()).mutation(async({ctx:t,input:r})=>{const e=r.ids.map(n=>String(n));return await m(t.asmrDb,async n=>{await o.deleteMany(n,e)}),{success:!0,removed:e.length}}),continueListening:s.input(P()).query(async({ctx:t,input:r})=>{const e=Math.min(Math.max(parseInt(String(r.limit??24))||24,1),100),{ignoreAdult:n,listOnly:d}=await b.readContinueFlags(t.db,"asmrIgnoreAdultContent","asmrCwWatchlistOnly"),a=(await o.getContinueListening(t.asmrDb,e)).filter(g=>!(n&&g.isAdult===1||d&&g.watchlistStatus!=="Listening"));return{data:a,total:a.length}}),adultCount:s.query(async({ctx:t})=>({count:(await I(t.asmrDb)).length})),purgeAdult:s.mutation(async({ctx:t})=>{const r=await I(t.asmrDb);return r.length>0&&await m(t.asmrDb,async e=>{for(const n of r)await o.deleteByWork(e,n)}),{success:!0,removed:r.length}})});export{D as asmrProgressRouter};
@@ -1 +0,0 @@
1
- import{protectedProcedure as c,router as h}from"../index.js";import{defineSchema as p,optStr as i,reqObj as m,reqStr as j}from"../validation.js";import{authRequiredError as u}from"../errors.js";import d from"../../logger.js";async function g(s,e,r,t){const o=s.apiCache.get(e);if(o)return o;const n=await t();return r!==void 0?s.apiCache.set(e,n,r):s.apiCache.set(e,n),n}const I=()=>p(s=>{if(s==null)return{};const e=m(s),r={},t=i(e,"q");t!==void 0&&(r.q=t),e.page!==void 0&&(r.page=parseInt(String(e.page),10)||1);const o=i(e,"sort");o!==void 0&&(r.sort=o);const n=i(e,"rating");return n!==void 0&&(r.rating=n),r}),q=()=>p(s=>({rj:j(m(s),"rj")})),k=h({browse:c.input(I()).query(async({ctx:s,input:e})=>{const r=e.q||"",t=e.page||1,o=e.sort||"",n=e.rating||"";return g(s,`route-asmr-browse-${r}-${t}-${o}-${n}-${o==="random"?Date.now():""}`,300,async()=>{try{const a=s.getJasmr();return a?await a.browse({query:r,page:t,sort:o,rating:n}):{shows:[],hasNext:!1}}catch(a){if(a.message==="AUTH_REQUIRED")throw u("jasmr");return d.error({err:a},"[Asmr] browse failed"),{shows:[],hasNext:!1}}})}),work:c.input(q()).query(async({ctx:s,input:e})=>{const r=e.rj;return g(s,`route-asmr-work-${r}`,1800,async()=>{try{const t=s.getJasmr();if(!t)return{rjCode:r,description:"",tracks:[],images:[],chapters:[]};const o=String(r).trim().toUpperCase(),n=await t.getEpisodes(o),[a,f,w]=await Promise.all([t.getStreamUrls(o,"1"),t.getImages(o),t.getChapters(o)]);return{rjCode:o,description:n?.description||"",tracks:a?.[0]?.links||[],images:f,chapters:w}}catch(t){if(t.message==="AUTH_REQUIRED")throw u("jasmr");return d.error({err:t,rj:r},"[Asmr] work fetch failed"),{rjCode:r,description:"",tracks:[],images:[],chapters:[]}}})})});export{k as asmrRouter};
@@ -1 +0,0 @@
1
- import p from"../../logger.js";import{TRPCError as _}from"@trpc/server";import{googleDriveService as s}from"../../google.js";import{githubSyncService as u}from"../../github-sync.js";import{initSyncProvider as E,getActiveProvider as v,runFullSyncSequence as I}from"../../sync.js";import{CONFIG as o}from"../../config.js";import{rcloneService as d}from"../../rclone.js";import{updateEnvFile as a}from"../../utils/env.utils.js";import{protectedProcedure as r,router as O}from"../index.js";import{defineSchema as c,reqObj as l}from"../validation.js";async function m(e,t,n){const i=o.IS_DEV?o.REMOTE_FOLDER_DEV:o.REMOTE_FOLDER_PROD;await I({db:t,mangaDb:e.mangaDb,tvDb:e.tvDb,asmrDb:e.asmrDb},{remoteFolder:i,preferredProvider:n})}const R=()=>c(e=>{const t=l(e);return{clientId:t.clientId,clientSecret:t.clientSecret,workerUrl:t.workerUrl}}),y=()=>c(e=>{const t=l(e);if(typeof t.clientId!="string")throw new Error("clientId required");return{clientId:t.clientId}}),w=()=>c(e=>({provider:l(e).provider})),f=()=>c(e=>({remote:l(e).remote})),C=()=>c(e=>({port:l(e).port})),A=O({configStatus:r.query(async()=>{const e=!!o.GOOGLE_AUTH_WORKER_URL,t=!!process.env.GOOGLE_CLIENT_ID&&!!process.env.GOOGLE_CLIENT_SECRET;return{hasConfig:e||t,useWorker:e}}),googleAuth:r.query(async()=>({useWorker:!!o.GOOGLE_AUTH_WORKER_URL,hasCustomWorkerUrl:!!process.env.GOOGLE_AUTH_WORKER_URL,hasCustomClientId:!!process.env.GOOGLE_CLIENT_ID,hasClientSecret:!!process.env.GOOGLE_CLIENT_SECRET})),saveGoogleAuth:r.input(R()).mutation(async({input:e})=>{const{clientId:t,clientSecret:n,workerUrl:i}=e,g={};return typeof t=="string"&&(g.GOOGLE_CLIENT_ID=t),typeof n=="string"&&(g.GOOGLE_CLIENT_SECRET=n),typeof i=="string"&&(g.GOOGLE_AUTH_WORKER_URL=i),await a(g),typeof i=="string"&&(o.GOOGLE_AUTH_WORKER_URL=i),typeof t=="string"&&(o.GOOGLE_CLIENT_ID=t||void 0),typeof n=="string"&&(o.GOOGLE_CLIENT_SECRET=n||void 0),await E(),{success:!0}}),githubAuth:r.query(async()=>({hasCustomClientId:!!process.env.GITHUB_CLIENT_ID})),saveGithubAuth:r.input(y()).mutation(async({input:e})=>(await a({GITHUB_CLIENT_ID:e.clientId}),{success:!0})),rcloneSettings:r.query(async()=>{const e=await d.listRemotes();return{remote:o.RCLONE_REMOTE||"",availableRemotes:e,activeRemote:d.isActive()?d.getRemoteName():null}}),syncSettings:r.query(async()=>({activeProvider:process.env.SYNC_PROVIDER||"default",actualActiveProvider:v(),authenticatedProviders:{github:u.isAuthenticated(),google:s.isAuthenticated(),rclone:d.isActive()}})),saveSyncProvider:r.input(w()).mutation(async({input:e})=>{const{provider:t}=e;return await a({SYNC_PROVIDER:t==="default"?"":t}),await E(),{success:!0,activeProvider:process.env.SYNC_PROVIDER||"default"}}),githubStatus:r.query(async()=>{try{const e=await u.getUserProfile();return{authenticated:!!e,user:e,device:u.getDeviceState(),hasCustomClientId:!!process.env.GITHUB_CLIENT_ID}}catch(e){return p.error({err:e},"Failed to fetch GitHub auth status"),{authenticated:!1,user:null,device:u.getDeviceState(),hasCustomClientId:!!process.env.GITHUB_CLIENT_ID}}}),githubStart:r.mutation(async({ctx:e})=>await u.startDeviceAuth(e.db,(n,i)=>m(e,n,i))),githubPoll:r.query(async()=>u.getDeviceState()),githubLogout:r.mutation(async()=>(await u.logout(),await a({SYNC_PROVIDER:""}),await E(),{success:!0})),saveRclone:r.input(f()).mutation(async({ctx:e,input:t})=>{const{remote:n}=t;return await a({RCLONE_REMOTE:n,SYNC_PROVIDER:"rclone"}),await m(e,e.db,"rclone"),{success:!0}}),setServerPort:r.input(C()).mutation(async({input:e})=>{const t=Number(e.port);if(!Number.isInteger(t)||t<1||t>65535)throw new _({code:"BAD_REQUEST",message:"Port must be an integer between 1 and 65535"});return await a({PORT:String(t)}),p.info({port:t},"Server port saved. Restart required to apply."),{success:!0,port:t}}),googleAuthUrl:r.query(async()=>({url:await s.getAuthUrl()})),googleLogin:r.mutation(async({ctx:e})=>{if(s.isAuthenticated()){if(await s.getUserProfile())return await a({SYNC_PROVIDER:"google"}),await m(e,e.db,"google"),{url:null,authenticated:!0};p.warn("Google tokens found but invalid. Clearing and requesting new auth."),await s.logout()}return{url:await s.getAuthUrl(),authenticated:!1}}),user:r.query(async()=>await s.getUserProfile()),logout:r.mutation(async()=>(await s.logout(),await a({SYNC_PROVIDER:""}),await E(),{success:!0}))});export{A as authRouter};
@@ -1,25 +0,0 @@
1
- import{protectedProcedure as s,router as w}from"../index.js";import{performWriteTransactionAsync as n}from"../../sync.js";import{WatchedEpisodesRepository as o}from"../../repositories/watched-episodes.repository.js";import{NotificationsRepository as a}from"../../repositories/notifications.repository.js";import{getMigratedId as m}from"../../lib/migration.js";import{getAdultNonWatchlistShowIds as u}from"../../hono/watchlist.js";import{dbAll as c}from"../../utils/db-utils.js";import{idsInput as p,showIdInput as h}from"../validation.js";const A=w({remove:s.input(h()).mutation(async({ctx:t,input:d})=>{const e=await m(t.db,d.showId);return await n(t.db,async i=>{await o.deleteByShow(i,e),await a.deleteByShow(i,e)}),{success:!0}}),removeMany:s.input(p()).mutation(async({ctx:t,input:d})=>{const e=await Promise.all(d.ids.map(i=>m(t.db,i)));return await n(t.db,async i=>{for(const r of e)await o.deleteByShow(i,r),await a.deleteByShow(i,r)}),{success:!0,removed:e.length}}),adultCount:s.query(async({ctx:t})=>({count:(await u(t.db)).length})),purgeAdult:s.mutation(async({ctx:t})=>{const d=await u(t.db);return d.length>0&&await n(t.db,async e=>{for(const i of d)await o.deleteByShow(e,i),await a.deleteByShow(e,i)}),{success:!0,removed:d.length}}),thisWeek:s.query(async({ctx:t})=>(await c(t.db,`SELECT
2
- w.id, w.name, w.thumbnail, w.nativeName, w.englishName, w.type,
3
- dn.episodeNumber, dn.discoveredAt
4
- FROM discovered_notifications dn
5
- JOIN watchlist w ON dn.showId = w.id
6
- WHERE w.status = 'Watching'
7
- AND EXISTS (
8
- SELECT 1 FROM discovered_notifications dn_recent
9
- WHERE dn_recent.showId = dn.showId
10
- AND dn_recent.discoveredAt >= datetime('now', '-7 days')
11
- )
12
- AND CAST(dn.episodeNumber AS INTEGER) = (
13
- SELECT MIN(CAST(dn2.episodeNumber AS INTEGER))
14
- FROM discovered_notifications dn2
15
- WHERE dn2.showId = dn.showId
16
- AND NOT EXISTS (
17
- SELECT 1 FROM watched_episodes we2
18
- WHERE we2.showId = dn2.showId AND we2.episodeNumber = dn2.episodeNumber
19
- )
20
- )
21
- AND NOT EXISTS (
22
- SELECT 1 FROM watched_episodes we
23
- WHERE we.showId = dn.showId AND we.episodeNumber = dn.episodeNumber
24
- )
25
- ORDER BY CAST(dn.episodeNumber AS INTEGER) ASC`)).map(e=>({_id:e.id,id:e.id,name:e.name,thumbnail:e.thumbnail||"",nativeName:e.nativeName,englishName:e.englishName,type:e.type,episodeNumber:parseInt(e.episodeNumber)||e.episodeNumber})))});export{A as continueWatchingRouter};
@@ -1 +0,0 @@
1
- import{TRPCError as $}from"@trpc/server";import{protectedProcedure as h,router as re}from"../index.js";import{badRequest as L,defineSchema as b,failed as P,optNum as M,optStr as l,readCount as N,reqObj as S,reqStr as V}from"../validation.js";import{parseJsonBody as se}from"../../utils/http.utils.js";import{getTrending as H,getLatestReleases as ne,getSeasonal as ae,getSchedule as ie,searchAnilist as Q,searchAnilistByTitle as oe,getSpotlightBanners as de,getBatchedHomeData as ue,getGenreTagLists as ce,anilistUnavailable as J,wasAnilistDownAtBoot as F,checkAnilistStatus as le,getShowMetaById as U,getAnilistEpisodes as fe,parseMalId as j,getShowMetaByMalId as Y,fromAnilistMedia as me}from"../../lib/anilist.js";import{malAnimeDetail as ge,malSearchMedia as pe,malSearchTitle as he}from"../../lib/mal.js";import{malCacheStore as O}from"../../repositories/mal-cache.repository.js";import{getMigratedId as K}from"../../lib/migration.js";import{isTempShowId as z}from"../../lib/temp-ids.js";import{TempShowIdsRepository as k}from"../../repositories/temp-show-ids.repository.js";import{pickBestMatch as ye}from"../../providers/title-matching.js";import{authRequiredError as we}from"../errors.js";import{ShowsMetaRepository as B}from"../../repositories/shows-meta.repository.js";import{WatchlistRepository as W}from"../../repositories/watchlist.repository.js";import{dbRun as Ie}from"../../utils/db-utils.js";import g from"../../logger.js";async function D(r,e,t,s,n){const i=r.apiCache.get(e);if(i)return i;const a=await n();return s(a)&&(t!==void 0?r.apiCache.set(e,a,t):r.apiCache.set(e,a)),a}const E=r=>Array.isArray(r)&&r.length>0,ve=()=>b(r=>{const e=S(r),t={date:V(e,"date")},s=l(e,"format");return s!==void 0&&(t.format=s),t}),be=()=>b(r=>{const e=S(r),t={},s=l(e,"format");return s!==void 0&&(t.format=s),e.page!==void 0&&(t.page=N(e,"page",1)),e.size!==void 0&&(t.size=N(e,"size",12)),t}),Se=()=>b(r=>{const e=S(r),t={},s=l(e,"query");s!==void 0&&(t.query=s),e.page!==void 0&&(t.page=N(e,"page",1)),e.limit!==void 0&&(t.limit=N(e,"limit",14));const n=l(e,"sortBy");n!==void 0&&(t.sortBy=n);const i=l(e,"type");i!==void 0&&(t.type=i);const a=l(e,"status");a!==void 0&&(t.status=a);const o=l(e,"season");o!==void 0&&(t.season=o);const f=M(e,"year");f!==void 0&&(t.year=f);const p=l(e,"country");p!==void 0&&(t.country=p);const y=l(e,"genres");y!==void 0&&(t.genres=y);const d=l(e,"excludeGenres");d!==void 0&&(t.excludeGenres=d);const v=l(e,"excludeTags");v!==void 0&&(t.excludeTags=v);const m=M(e,"minScore");m!==void 0&&(t.minScore=m);const u=M(e,"minEpisodes");u!==void 0&&(t.minEpisodes=u);const c=l(e,"adult");return c!==void 0&&(t.adult=c),t}),De=()=>b(r=>{const e=S(r),t={},s=l(e,"title");return s!==void 0&&(t.title=s),t}),Ne=()=>b(r=>{const e=S(r),t={},s=l(e,"showId");s!==void 0&&(t.showId=s);const n=l(e,"episodeNumber");return n!==void 0&&(t.episodeNumber=n),t}),Ee=()=>b(r=>{const e=S(r),t={};e.page!==void 0&&(t.page=N(e,"page",1)),e.size!==void 0&&(t.size=N(e,"size",14));const s=l(e,"format");return s!==void 0&&(t.format=s),t}),Te=()=>b(r=>{const e=S(r),t={},s=l(e,"sort");return s!==void 0&&(t.sort=s),e.page!==void 0&&(t.page=N(e,"page",1)),e.size!==void 0&&(t.size=N(e,"size",20)),t}),Re=()=>b(r=>{if(r==null)return{};const e=S(r),t={},s=l(e,"format");return s!==void 0&&(t.format=s),t}),Ae=()=>b(r=>{if(r==null)return{};const e=S(r),t={},s=l(e,"showId");s!==void 0&&(t.showId=s);const n=l(e,"mode");(n==="sub"||n==="dub")&&(t.mode=n);const i=l(e,"provider");return i!==void 0&&(t.provider=i),t}),Me=()=>b(r=>({id:V(S(r),"id")})),qe=()=>b(r=>{if(r==null)return{};const e=S(r),t={},s=l(e,"showId");s!==void 0&&(t.showId=s);const n=l(e,"episodeNumber");n!==void 0&&(t.episodeNumber=n);const i=l(e,"mode");(i==="sub"||i==="dub")&&(t.mode=i);const a=l(e,"provider");return a!==void 0&&(t.provider=a),t}),Ce=()=>b(r=>{const e=S(r),t={};for(const a of["provider","query","type","status","season","country","genres","genre","excludeGenres","sortBy","order","studio","blacklist"]){const o=l(e,a);o!==void 0&&(t[a]=o)}e.page!==void 0&&(t.page=N(e,"page",1)),e.limit!==void 0&&(t.limit=N(e,"limit",14));const s=M(e,"year");s!==void 0&&(t.year=s);const n=M(e,"minScore");n!==void 0&&(t.minScore=n);const i=M(e,"minEpisodes");return i!==void 0&&(t.minEpisodes=i),t}),Le=()=>b(r=>{const e=S(r),t={},s=l(e,"provider");s!==void 0&&(t.provider=s);const n=l(e,"nativeId");n!==void 0&&(t.nativeId=n);const i=l(e,"title");i!==void 0&&(t.title=i);const a=l(e,"thumbnail");return a!==void 0&&(t.thumbnail=a),t});function Pe(r,e){const t=e?.toLowerCase();return t&&r.getProviders()[t]||null}function q(r,e){const t=(e||"").toLowerCase(),s=r.getProviders();if(!t||!s[t]||t==="mal"||t==="anilist")return!1;const n=Z(r);return n?n.has(t):!0}function Z(r){const e=r.getCatalog();return!e||e.length===0?null:new Set(e.filter(t=>t.mature&&(t.kind??"anime")==="anime"&&t.loaded).map(t=>t.id.toLowerCase()))}async function X(r,e,t){const s=r.getProviders(),n=Z(r),i=n?[...n]:Object.keys(s),a=[t,...i].filter(f=>!!f&&f!=="mal"&&f!=="anilist"&&!!s[f]),o=new Set;for(const f of a)if(!o.has(f)){o.add(f);try{const p=await s[f].resolveShowId?.(e);if(p)return{provider:s[f],nativeId:p}}catch{}}return null}function _(r){if(typeof r!="string"||!r.trim())return;const e=r.split(",").map(t=>t.trim()).filter(Boolean);return e.length>0?e:void 0}const Ke=re({schedule:h.input(ve()).query(async({ctx:r,input:e})=>{const t=e.date,s=e.format||"TV";return D(r,`schedule-${t}-${s}`,1800,E,async()=>{try{const n=new Date(t+"T00:00:00.000Z"),i=s==="ADULT";return await ie(n,i?void 0:s,i)}catch(n){return g.error({err:n,date:t},"Schedule fetch failed"),[]}})}),latestReleases:h.input(be()).query(async({ctx:r,input:e})=>{const t=e.format||"TV",s=e.page||1,n=e.size||12;return D(r,`latest-releases-${t}-${s}-${n}`,300,E,async()=>{try{return await ne(t,s,n)}catch(i){return g.error({err:i},"Latest releases fetch failed"),[]}})}),search:h.input(Se()).query(async({ctx:r,input:e})=>{const t={};return e.query!==void 0&&(t.query=e.query),e.page!==void 0&&(t.page=String(e.page)),e.limit!==void 0&&(t.limit=String(e.limit)),e.sortBy!==void 0&&(t.sortBy=e.sortBy),e.type!==void 0&&(t.type=e.type),e.status!==void 0&&(t.status=e.status),e.season!==void 0&&(t.season=e.season),e.year!==void 0&&(t.year=String(e.year)),e.country!==void 0&&(t.country=e.country),e.genres!==void 0&&(t.genres=e.genres),e.excludeGenres!==void 0&&(t.excludeGenres=e.excludeGenres),e.excludeTags!==void 0&&(t.excludeTags=e.excludeTags),e.minScore!==void 0&&(t.minScore=String(e.minScore)),e.minEpisodes!==void 0&&(t.minEpisodes=String(e.minEpisodes)),e.adult!==void 0&&(t.adult=e.adult),D(r,`search-${JSON.stringify(t)}`,1800,E,async()=>{try{return await Q({query:e.query||"",page:e.page||1,perPage:e.limit||14,format:e.type,status:e.status,season:e.season,seasonYear:e.year,countryOfOrigin:e.country,genre:e.genres,genre_not_in:_(e.excludeGenres),tag_not_in:_(e.excludeTags),averageScore_greater:e.minScore,episodes_greater:e.minEpisodes,isAdult:e.adult==="true"?!0:e.adult==="false"?!1:void 0,sort:e.sortBy})}catch(s){return g.error({err:s},"search failed"),[]}})}),matureResolve:h.input(De()).query(async({ctx:r,input:e})=>D(r,`mature-resolve-${e.title||""}`,3600,E,async()=>{try{const t=(e.title||"").trim();if(!t)throw L("title is required");const s=await oe(t);if(s){const i=s.id;return{id:typeof i=="number"&&i<0?`mal-${Math.abs(i)}`:i}}const n=await he(O(),t);if(n)return{id:n,provider:"mal"};throw new $({code:"NOT_FOUND",message:"No match found"})}catch(t){throw t instanceof $?t:(g.error({err:t},"mature resolve failed"),P("Resolve failed"))}})),skipTimes:h.input(Ne()).query(async({input:r})=>{try{const e=r.showId||"",t=r.episodeNumber||"";if(/^\d+$/.test(e)){const s=await fetch(`https://api.aniskip.com/v1/skip-times/${e}/${t}?types=op&types=ed`);if(s.ok)return await se(s)}return{found:!1,results:[]}}catch{return{found:!1,results:[]}}}),seasonal:h.input(Ee()).query(async({ctx:r,input:e})=>{const t=e.page||1,s=e.size||14,n=e.format;return D(r,`seasonal-${n||"ALL"}-${t}-${s}`,300,E,async()=>{try{return await ae(t,s,n)}catch(i){return g.error({err:i},"Seasonal fetch failed"),[]}})}),popularList:h.input(Te()).query(async({ctx:r,input:e})=>{const t=e.sort==="POPULARITY_DESC"?"POPULARITY_DESC":"TRENDING_DESC",s=e.page||1,n=e.size||20;return D(r,`popular-list-${e.sort||"TRENDING_DESC"}-${s}-${n}`,300,E,async()=>{try{return await H(s,n,t)}catch(i){return g.error({err:i},"Popular list fetch failed"),[]}})}),trending:h.query(async({ctx:r})=>D(r,"trending",300,E,async()=>{try{return await H(1,20,"TRENDING_DESC","RELEASING")}catch(e){return g.error({err:e},"Trending fetch failed"),[]}})),spotlight:h.query(async({ctx:r})=>D(r,"spotlight",300,E,async()=>{try{return await de(1,20)}catch(e){return g.error({err:e},"Spotlight fetch failed"),[]}})),home:h.input(Re()).query(async({ctx:r,input:e})=>{const t=e.format;return D(r,`home-${t||"TV"}`,300,E,async()=>{try{return await ue(t)}catch(s){return g.error({err:s},"Batched home fetch failed"),{trending:[],seasonal:[],spotlight:[]}}})}),genresAndTags:h.query(async()=>{try{return await ce()}catch(r){throw g.error({err:r},"genres-and-tags failed"),P("Failed to load genres")}}),anilistStatus:h.query(async()=>{const r=!J();return r||le().catch(()=>{}),{available:r,wasDownAtBoot:F()}}),systemNotifications:h.query(async()=>{const r=[];return F()&&J()&&r.push({id:"system-anilist-down",type:"system",title:"AniList API",message:"AniList metadata API is currently down. dango is experiencing degraded performance. MAL + Kitsu is being used as a fallback in the meantime. Some features may be limited until service is restored.",icon:"warning",createdAt:Date.now()}),r}),episodes:h.input(Ae()).query(async({ctx:r,input:e})=>{const t=e.showId;return D(r,`episodes-${t||""}`,3600,E,async()=>{const s=t;if(!s)return{episodes:[]};const n=r.db,i=r.getProviders();let a=await K(n,s);const o=j(a);if(o)try{const d=await Y(o);d?.anilistId&&d.anilistId>0&&(a=String(d.anilistId))}catch{}if(z(a)){try{const d=await k.getById(n,a);if(!d||!q(r,d.provider))return{episodes:[]};const v=String(e.provider||"").toLowerCase(),m=i[d.provider];if(m&&(!v||v===d.provider)){const c=await m.getEpisodes(d.nativeId,e.mode);return c?.episodes?.length?c:{episodes:[]}}const u=await X(r,d.title,v);if(u){const c=await u.provider.getEpisodes(u.nativeId,e.mode);if(c?.episodes?.length)return c}}catch(d){if(d.message==="AUTH_REQUIRED")throw d}return{episodes:[]}}if(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(a))try{if(i.animepahe)return await i.animepahe.getEpisodes(a,e.mode)||{episodes:[]}}catch(d){if(d.message==="AUTH_REQUIRED")throw d;return{episodes:[]}}if(/^(mal-\d+|-?\d+)$/i.test(a)){let d=[];try{d=await fe(a)}catch(v){g.error({err:v,showId:a},"Episodes fetch failed")}if(d.length===0){const v=Math.abs(parseInt(a,10));if(!Number.isFinite(v))return{episodes:d};try{const u=(await ge(O(),v)).detail?.episodes;u&&u>0&&u<=500&&(d=Array.from({length:u},(c,I)=>(I+1).toString()))}catch{}}if(d.length===0){const v=e.provider?.toLowerCase(),m=v?i[v]:void 0;if(m?.isDirectId?.(a))try{const u=await m.getEpisodes(a,e.mode);if(u?.episodes?.length)return u}catch(u){if(u.message==="AUTH_REQUIRED")throw u}}return{episodes:d}}const p=e.provider?.toLowerCase(),y=p?i[p]:void 0;if(y)try{const d=await y.getEpisodes(a,e.mode);if(d?.episodes?.length)return d}catch(d){if(d.message==="AUTH_REQUIRED")throw d}return{episodes:[]}})}),showMeta:h.input(Me()).query(async({ctx:r,input:e})=>{const t=e.id;return D(r,`meta-${t}`,3600,s=>!!s,async()=>{const s=r.db,n=await K(s,t);if(z(n)){const a=await k.getById(s,n);return!a||!q(r,a.provider)?{}:{_id:a.id,id:a.id,name:a.title,englishName:a.title,thumbnail:a.thumbnail||"",type:"TV",isAdult:!0,status:"UNKNOWN",provider:a.provider,nativeId:a.nativeId}}if(/^(mal-\d+|-?\d+)$/i.test(n)){let a=null;try{a=await U(n)}catch(o){g.warn({err:o,id:n},"AniList show-meta fetch failed, trying local cache");const f=await B.getById(s,n);if(f){if(typeof f.genres=="string")try{f.genres=JSON.parse(f.genres)}catch{f.genres=[]}return f}}if(a){const o=a.thumbnail?.trim()?a.thumbnail:void 0,f=!!o;try{await B.upsert(s,{id:n,name:a.name,thumbnail:o,nativeName:a.nativeName,englishName:a.englishName,popularityScore:a.score??(a.averageScore!=null?a.averageScore/10:void 0),genres:a.genres?JSON.stringify(a.genres.map(p=>typeof p=="string"?p:p?.name).filter(Boolean)):void 0,status:a.status,episodeCount:a.episodeCount!=null?Number(a.episodeCount):void 0,type:a.type,anilistId:a.anilistId})}catch{}if(f&&o){const p=await W.getById(s,n);if(p&&p.thumbnail!==o)try{await W.updateThumbnail(s,n,o)}catch{}}}if(/^(mal-\d+|-\d+)$/i.test(n)&&a?.anilistId&&a.anilistId>0)try{Ie(s,"INSERT OR REPLACE INTO legacy_id_mapping (legacyId, numericId) VALUES (?, ?)",[n,String(a.anilistId)])}catch{}return a||{}}return{}})}),matureSearch:h.input(Ce()).query(async({ctx:r,input:e})=>{const t=(e.provider||"anilist").toLowerCase();if(t!=="anilist"&&t!=="mal"&&!q(r,t))throw L("Unknown mature provider");const s={};for(const[n,i]of Object.entries(e))i!==void 0&&(s[n]=String(i));return D(r,`mature-search-${JSON.stringify(s)}`,600,n=>!!n&&typeof n=="object"&&Array.isArray(n.data),async()=>{try{const n=e.query||"",i=e.page||1,a=e.limit||14;if(t==="anilist"){const o=await Q({query:n,page:i,perPage:a,format:e.type,status:e.status,season:e.season,seasonYear:e.year,countryOfOrigin:e.country,genre:e.genres,genre_not_in:_(e.excludeGenres),isAdult:!0,sort:e.sortBy});return{data:o,hasMore:o.length>=a}}if(q(r,t)){const o=r.getProviders()[t];if(o?.browse){const p=e.genre||e.genres||void 0,y=await o.browse({query:n,page:i,limit:a,pageSize:a,genre:p,genres:e.genres||p,order:e.order||e.sortBy||void 0,sort:e.sortBy||e.order||void 0,studio:e.studio||void 0,blacklist:e.blacklist||void 0});return{data:y.shows,hasMore:y.hasMore,...y.total!==void 0?{total:y.total}:{},...y.genres?{genres:y.genres}:{}}}return{data:(await o.search({query:n})).map(p=>({...p,isAdult:!0})),hasMore:!1}}if(t==="mal"){let o=[];try{o=await pe(O(),{query:n,page:i,perPage:50,format:e.type,status:e.status,genre:e.genres||void 0,genre_not_in:_(e.excludeGenres),isAdult:!0,sort:e.sortBy,averageScore_greater:e.minScore,episodes_greater:e.minEpisodes})}catch(y){g.error({err:y},"mal mature search failed")}const f=o.map(y=>({...me(y),isAdult:!0}));return{data:f.slice(0,a),hasMore:f.length>=a}}return{data:[],hasMore:!1}}catch(n){return g.error({err:n},"mature search failed"),{data:[],hasMore:!1}}})}),matureFilters:h.query(async({ctx:r})=>{try{const e=r.getCatalog()??[],t={};for(const s of e)!s.mature||(s.kind??"anime")!=="anime"||!s.loaded||(t[s.id]={label:s.label,...s.browse?{browse:s.browse}:{},...s.facets?.genres?.length?{genres:s.facets.genres}:{},...s.facets?.orders?.length?{orders:s.facets.orders}:{}});return{providers:t}}catch(e){throw g.error({err:e},"mature filters failed"),P("Failed to load filters")}}),matureAllocate:h.input(Le()).mutation(async({ctx:r,input:e})=>{try{const t=String(e.provider||"").toLowerCase(),s=String(e.nativeId||"").trim(),n=String(e.title||"").trim(),i=String(e.thumbnail||"");if(!t||!q(r,t))throw L("Unknown provider");if(!s||!n)throw L("nativeId and title are required");return{id:(await k.allocate(r.db,{provider:t,nativeId:s,title:n,thumbnail:i})).id}}catch(t){throw t instanceof $?t:(g.error({err:t},"temp show allocate failed"),P("Allocate failed"))}}),video:h.input(qe()).query(async({ctx:r,input:e})=>{const t=r.db,s=r.getProviders();try{let n=e.showId;const i=j(n);if(i)try{const m=await Y(i);m?.anilistId&&m.anilistId>0&&(n=String(m.anilistId))}catch{}if(z(n)){try{const m=await k.getById(t,n);if(!m||!q(r,m.provider))return[];const u=String(e.provider||"").toLowerCase(),c=s[m.provider];if(c&&(!u||u===m.provider))return await c.getStreamUrls(m.nativeId,e.episodeNumber,e.mode)||[];const I=await X(r,m.title,u);if(I)return await I.provider.getStreamUrls(I.nativeId,e.episodeNumber,e.mode)||[]}catch(m){g.error({err:m,showId:n},"Temp show video fetch failed")}return[]}const o=e.provider?.toLowerCase(),f=j(n)!==null,y=!!!(o&&s[o]?.isDirectId?.(n))&&(/^\d+$/.test(n)||f);if(o&&y){const m=await B.getById(t,n);let u=m?.englishName||m?.name,c=null;if(!u)try{c=await U(n),u=c?.englishName||c?.name,c&&u&&await B.upsert(t,{id:n,name:c.name,thumbnail:c.thumbnail,nativeName:c.nativeName,englishName:c.englishName,genres:c.genres?JSON.stringify(c.genres.map(I=>I.name)):void 0,status:c.status,episodeCount:c.episodeCount!=null?Number(c.episodeCount):void 0,type:c.type,anilistId:c.anilistId})}catch(I){g.warn({err:I,provider:o,showId:n},"[Video] AniList metadata lookup failed while resolving numeric showId")}if(u){let I=c?.names?.romaji,R=c?.names?.native,x=c?.names?.synonyms;if(!I||!R)try{const w=await U(n);I=I||w?.names?.romaji,R=R||w?.names?.native,x=x||w?.names?.synonyms}catch{}const ee=[u,I,R,...(x??[]).slice(0,2)].filter(w=>!!w&&w.trim().length>0),G=[...new Set(ee.map(w=>w.trim()))].filter((w,T,A)=>A.findIndex(te=>te.toLowerCase()===w.toLowerCase())===T);let C=null;for(const w of G){try{C=await s[o]?.resolveShowId?.(w,I,e.mode)??null}catch(T){if(T.message==="AUTH_REQUIRED")throw T;C=null}if(C)break}if(C)n=C;else{g.warn({provider:o,showId:n,title:u,romaji:I},"[Video] resolveShowId failed, attempting fallback provider search");try{const w=await s[o]?.search?.({query:u}),T=ye((w||[]).map(A=>({title:A.name||A.englishName||"",id:A.id||A._id||""})),G);if(T)n=T.item.id;else return g.warn({provider:o,showId:n,title:u},"[Video] fallback provider search returned no results"),[]}catch(w){if(w.message==="AUTH_REQUIRED")throw w;return g.error({err:w,provider:o,showId:n,title:u},"[Video] fallback provider search failed"),[]}}}else return g.warn({provider:o,showId:n},"[Video] numeric showId passed to provider but local meta missing title"),[]}const d=Pe(r,e.provider);return d?await d.getStreamUrls(n,e.episodeNumber,e.mode)||[]:[]}catch(n){if(n.message==="AUTH_REQUIRED")throw we(n.provider||e.provider||"animepahe");return g.error({err:n,provider:e.provider},"Provider video fetch failed"),[]}})});export{Ke as dataRouter};
@@ -1 +0,0 @@
1
- import{protectedProcedure as c,router as I}from"../index.js";import{badRequest as P,defineSchema as h,failed as y,optStr as v,reqObj as p}from"../validation.js";import{discordRPCService as r}from"../../discord-rpc.js";import{discordGatewayService as m}from"../../discord-gateway.js";import{getDlsitePoster as T,getMangadexCover as w}from"../../hono/watchlist.js";import{updateEnvFile as S}from"../../utils/env.utils.js";import E from"../../logger.js";function g(e){return e?e.slice(0,8)+"..."+e.slice(-4):"none"}const f=E.child({module:"DiscordGatewayRoutes"});function s(e,t){return e[t]}function k(e){let t=String(e||"");if(t.includes("/api/proxy")||t.includes("/api/image-proxy")){const i=t.match(/url=([^&]+)/);if(i)try{t=decodeURIComponent(i[1])}catch{return""}}return!t.startsWith("https://")||t.includes("localhost")||t.includes("127.0.0.1")?"":t}const u=()=>h(e=>e==null?{}:{...p(e)}),G=I({pageStatus:c.input(u()).mutation(async({input:e})=>{const t=s(e,"page");return typeof t!="string"||!r.isServiceEnabled?{success:!0}:(r.setIdleStatus(t),{success:!0})}),heartbeat:c.input(u()).mutation(async({input:e})=>{const t=s(e,"sessionId"),i=s(e,"bye");return typeof t!="string"||!r.isServiceEnabled?{success:!0}:(i?r.removeHeartbeat(t):r.heartbeat(t),{success:!0})}),clear:c.input(u()).mutation(async({input:e})=>{const t=s(e,"sessionId");return r.isServiceEnabled?(r.clearPresence(typeof t=="string"?t:void 0),{success:!0}):{success:!0}}),asmrPresence:c.input(u()).mutation(async({input:e})=>{if(!r.isServiceEnabled)return{success:!0};const t=s(e,"sessionId");typeof t=="string"&&r.heartbeat(t);const i=s(e,"thumbnail"),n=s(e,"thumbnails"),o=s(e,"rjCode");let a=String(i||"");const d=Array.isArray(n)?n.map(String).filter(Boolean):void 0;if((!a||a.includes("weeabo0.xyz")||a.includes("japaneseasmr.com"))&&o){const b=await T(String(o));b&&(a=b)}return(a.includes("/api/image-proxy")||a.includes("weeabo0.xyz"))&&(a=""),r.updatePresence({title:String(s(e,"title")||"ASMR").slice(0,128),episode:String(s(e,"trackLabel")||"").slice(0,64),totalEpisodes:"",currentTime:Number(s(e,"currentTime"))||0,duration:Number(s(e,"duration"))||0,thumbnail:a,thumbnails:d,isPlaying:!!s(e,"isPlaying"),providerName:"ASMR",sessionId:typeof t=="string"?t:void 0,isAdult:!!s(e,"isAdult")}),{success:!0}}),radioPresence:c.input(u()).mutation(async({input:e})=>{if(!r.isServiceEnabled)return{success:!0};const t=s(e,"sessionId");typeof t=="string"&&r.heartbeat(t);let i=String(s(e,"thumbnail")||"");if(i.includes("/api/image-proxy")){const o=i.match(/url=([^&]+)/);o&&(i=decodeURIComponent(o[1]))}i&&!i.startsWith("https://")&&(i=""),(i.includes("localhost")||i.includes("127.0.0.1"))&&(i="");const n=String(s(e,"stationLabel")||"");return r.updatePresence({title:String(s(e,"title")||"Radio").slice(0,128),episode:n.slice(0,64),totalEpisodes:"",stateLine:(n||"Radio").slice(0,64),currentTime:Number(s(e,"currentTime"))||0,duration:0,thumbnail:i,isPlaying:!!s(e,"isPlaying"),providerName:"Radio",sessionId:typeof t=="string"?t:void 0,isAdult:!1}),{success:!0}}),musicPresence:c.input(u()).mutation(async({input:e})=>{if(!r.isServiceEnabled)return{success:!0};const t=s(e,"sessionId");typeof t=="string"&&r.heartbeat(t);let i=String(s(e,"thumbnail")||"");if(i.includes("/api/image-proxy")){const o=i.match(/url=([^&]+)/);o&&(i=decodeURIComponent(o[1]))}i&&!i.startsWith("https://")&&(i=""),(i.includes("localhost")||i.includes("127.0.0.1"))&&(i="");const n=String(s(e,"artistLabel")||"");return r.updatePresence({title:String(s(e,"title")||"Music").slice(0,128),episode:n.slice(0,64),totalEpisodes:"",stateLine:(n||"Music").slice(0,64),currentTime:Number(s(e,"currentTime"))||0,duration:Number(s(e,"duration"))||0,thumbnail:i,isPlaying:!!s(e,"isPlaying"),providerName:"Music",sessionId:typeof t=="string"?t:void 0,isAdult:!1}),{success:!0}}),mangaPresence:c.input(u()).mutation(async({input:e})=>{if(!r.isServiceEnabled)return{success:!0};const t=s(e,"sessionId");typeof t=="string"&&r.heartbeat(t);const i=s(e,"thumbnails"),n=s(e,"title");let o=k(s(e,"thumbnail")),a=Array.isArray(i)?i.map(k).filter(Boolean):void 0;if(!o&&n){const l=await w(String(n));l&&(o=l,a=[l,...a||[]])}const d=String(s(e,"chapterLabel")||"");return r.updatePresence({title:String(n||"Manga").slice(0,128),episode:d.slice(0,64),totalEpisodes:"",stateLine:(d?`Reading ${d}`:"Reading").slice(0,64),currentTime:0,duration:0,thumbnail:o,thumbnails:a,isPlaying:s(e,"isPlaying")!==!1,providerName:"Manga",sessionId:typeof t=="string"?t:void 0,isAdult:s(e,"isAdult")===!0}),{success:!0}}),tvPresence:c.input(u()).mutation(async({input:e})=>{if(!r.isServiceEnabled)return{success:!0};const t=s(e,"sessionId");typeof t=="string"&&r.heartbeat(t);const i=s(e,"thumbnail");let n=String(i||"");if(n.includes("/api/image-proxy")){const a=n.match(/url=([^&]+)/);a&&(n=decodeURIComponent(a[1]))}(n.includes("localhost")||n.includes("127.0.0.1"))&&(n="");const o=String(s(e,"episodeLabel")||"");return r.updatePresence({title:String(s(e,"title")||"TV").slice(0,128),episode:o.slice(0,64),totalEpisodes:"",stateLine:(o||"Movie").slice(0,64),currentTime:Number(s(e,"currentTime"))||0,duration:Number(s(e,"duration"))||0,thumbnail:n,isPlaying:!!s(e,"isPlaying"),providerName:"TV",sessionId:typeof t=="string"?t:void 0,isAdult:s(e,"isAdult")===!0}),{success:!0}}),gatewayStatus:c.query(()=>{const e=m.hasToken();return{hasToken:e,masked:e?g(process.env.DISCORD_GATEWAY_TOKEN):null,enabled:m.serviceEnabled}}),gatewaySave:c.input(h(e=>{if(e==null)return{};const t=p(e),i={},n=v(t,"token");return n!==void 0&&(i.token=n),i})).mutation(async({input:e})=>{let t=(e.token||"").trim();if(t.startsWith('"')&&t.endsWith('"')&&(t=t.slice(1,-1)),t.length<30)throw P("Token too short");try{return await S({DISCORD_GATEWAY_TOKEN:t}),m.reloadToken(),f.info(`Discord Gateway token saved ${g(t)} (not logged)`),{ok:!0,masked:g(t)}}catch(i){throw f.error({err:i},"Failed to save Discord Gateway token"),y("Failed to save token")}}),gatewayRemove:c.mutation(async()=>{try{return await S({DISCORD_GATEWAY_TOKEN:""}),m.reloadToken(),f.info("Discord Gateway token removed"),{ok:!0}}catch(e){throw f.error({err:e},"Failed to remove Discord Gateway token"),y("Failed to remove token")}})});export{G as discordRouter};
@@ -1 +0,0 @@
1
- import{protectedProcedure as t,router as e}from"../index.js";import{getWatchlistDiscoveryStatus as r,triggerWatchlistDiscovery as s}from"../../hono/watchlist.js";const i=e({status:t.query(()=>r()),refresh:t.mutation(()=>({success:!0,started:s(),...r()}))});export{i as discoveryRouter};
@@ -1 +0,0 @@
1
- import{protectedProcedure as e,router as t}from"../index.js";import{getWatchInsights as n,getGenreCards as o}from"../../lib/insights.js";import{getCachedRecommendations as a}from"../../lib/recommendations.service.js";const c=t({summary:e.query(async({ctx:r})=>n(r.db)),genreCards:e.query(async({ctx:r})=>o(r.db)),recommendations:e.query(async({ctx:r})=>({candidates:await a(r.db)}))});export{c as insightsRouter};
@@ -1 +0,0 @@
1
- import{TRPCError as d}from"@trpc/server";import{protectedProcedure as c,router as k}from"../index.js";import{performMangaWriteTransactionAsync as h}from"../../sync.js";import{buildMangaId as R,MANGA_STATUSES as I,MangaLibraryRepository as i,MangaProgressRepository as f}from"../../repositories/manga.repository.js";import{badRequest as g,defineSchema as m,failed as y,optStr as s,readCount as v,reqObj as w,reqStr as q}from"../validation.js";function u(r){const t=r.mangaDb;if(!t||t.isClosedCheck())throw new Error("Manga database is not ready");return t}function T(r,t,a){return a&&a.includes(":")?a:R(r,t||a||"")}const C=()=>m(r=>{const t=w(r),a=s(t,"status"),n=Math.max(v(t,"page",1),1),e=Math.min(Math.max(v(t,"limit",24),1),100);return{status:a,page:n,limit:e}}),L=()=>m(r=>({id:q(w(r),"id")})),N=()=>m(r=>{const t=w(r),a={provider:s(t,"provider"),mangaId:s(t,"mangaId"),id:s(t,"id"),title:s(t,"title"),cover:s(t,"cover"),status:s(t,"status"),author:s(t,"author"),altTitle:s(t,"altTitle"),contentRating:s(t,"contentRating")};return typeof t.silent=="boolean"&&(a.silent=t.silent),a}),F=()=>m(r=>{if(typeof r!="string")throw new Error("id must be a string");return r}),B=()=>m(r=>{const t=w(r);return{id:s(t,"id"),status:s(t,"status")}}),D=()=>m(r=>{const t=w(r);return{id:s(t,"id"),anilistId:t.anilistId}}),j=()=>m(r=>{const t=w(r),a=t.ids;return{ids:Array.isArray(a)?a.map(e=>String(e)):[],status:s(t,"status")}}),P=()=>m(r=>{if(!Array.isArray(r))throw new Error("ids must be a non-empty array");return r.map(t=>String(t))}),z=k({list:c.input(C()).query(async({ctx:r,input:t})=>{try{const a=(t.page-1)*t.limit,n=u(r),e=await i.getAll(n,t.status,t.limit,a),l=await i.getCount(n,t.status);return{data:e,total:l,page:t.page,limit:t.limit}}catch(a){throw a instanceof d?a:y("Failed to load manga library")}}),ids:c.query(async({ctx:r})=>{try{return{ids:await i.getIds(u(r))}}catch{return{ids:[]}}}),check:c.input(L()).query(async({ctx:r,input:t})=>{try{const a=await i.getById(u(r),t.id);return{inLibrary:!!a,status:a?.status??null}}catch{return{inLibrary:!1,status:null}}}),entry:c.input(L()).query(async({ctx:r,input:t})=>{try{const a=await i.getById(u(r),t.id);if(!a)throw new d({code:"NOT_FOUND",message:"Not in library"});return{item:a}}catch(a){throw a instanceof d?a:y("Failed to load entry")}}),add:c.input(N()).mutation(async({ctx:r,input:t})=>{const a=t.provider||"",n=t.mangaId||"",e=t.id||"";if(!a||!n&&!e)throw g("provider and mangaId are required");const l=T(String(a),String(n||""),e?String(e):void 0),p=typeof t.status=="string"&&I.includes(t.status)?t.status:"Reading";try{return await h(u(r),async o=>{await i.upsert(o,{id:l,provider:String(a).toLowerCase(),mangaId:String(n||e||""),title:String(t.title||""),cover:String(t.cover||""),status:p,author:t.author?String(t.author):void 0,altTitle:t.altTitle?String(t.altTitle):void 0,contentRating:t.contentRating?String(t.contentRating):void 0})}),{success:!0,id:l}}catch(o){throw o instanceof d?o:y("Failed to bookmark manga")}}),remove:c.input(F()).mutation(async({ctx:r,input:t})=>{if(!t)throw g("id is required");try{return await h(u(r),async a=>{await i.delete(a,String(t)),await f.deleteByManga(a,String(t))}),{success:!0}}catch(a){throw a instanceof d?a:y("Failed to remove bookmark")}}),setStatus:c.input(B()).mutation(async({ctx:r,input:t})=>{if(!t.id||!t.status||!I.includes(t.status))throw g("id and a valid status are required");try{return await h(u(r),async a=>{await i.updateStatus(a,String(t.id),String(t.status))}),{success:!0}}catch(a){throw a instanceof d?a:y("Failed to update status")}}),link:c.input(D()).mutation(async({ctx:r,input:t})=>{const a=typeof t.id=="string"?t.id:"",n=Number(t.anilistId);if(!a||!Number.isInteger(n)||n<=0)throw g("id and a numeric anilistId are required");try{const e=u(r),l=await i.getById(e,a);if(!l)throw new d({code:"NOT_FOUND",message:"Library entry not found"});if(String(l.provider).toLowerCase()==="anilist")throw g("Link a provider bookmark to the AniList entry, not the reverse");let p=!1;return await h(e,async o=>{await i.setAnilistId(o,a,n,"manual");const b=`anilist:${n}`;if(b!==a){const S=await i.getById(o,b),M=await i.getById(o,a),A=await f.getByManga(o,a);S?.lastChapterNumber&&!M?.lastChapterNumber&&A.length===0&&(await i.setProgressPointer(o,a,S.lastChapterNumber),await f.moveSyntheticChapters(o,b,a),p=!0),await i.delete(o,b),await f.deleteByManga(o,b)}}),{success:!0,id:a,progressMigrated:p}}catch(e){throw e instanceof d?e:y("Failed to link entry")}}),batchStatus:c.input(j()).mutation(async({ctx:r,input:t})=>{if(!Array.isArray(t.ids)||t.ids.length===0)throw g("ids must be a non-empty array");if(!t.status||!I.includes(t.status))throw g("a valid status is required");const a=t.ids.map(n=>String(n));try{return await h(u(r),async n=>{await i.updateStatusMany(n,a,String(t.status))}),{success:!0,updated:a.length}}catch(n){throw n instanceof d?n:y("Failed to update statuses")}}),removeMany:c.input(P()).mutation(async({ctx:r,input:t})=>{if(!Array.isArray(t)||t.length===0)throw g("ids must be a non-empty array");const a=t.map(n=>String(n));try{return await h(u(r),async n=>{await i.deleteMany(n,a),await f.deleteMany(n,a)}),{success:!0,removed:a.length}}catch(n){throw n instanceof d?n:y("Failed to remove bookmarks")}})});export{z as mangaLibraryRouter};
@@ -1 +0,0 @@
1
- import{protectedProcedure as r,router as b}from"../index.js";import{defineSchema as p,optStr as g,reqObj as d,reqStr as m,reqStrArray as f}from"../validation.js";import{performMangaWriteTransactionAsync as l}from"../../sync.js";import{adultNonListIds as M}from"../../utils/db-utils.js";import{SettingsRepository as N}from"../../repositories/settings.repository.js";import{MangaLibraryRepository as R,MangaProgressRepository as i}from"../../repositories/manga.repository.js";const v=["erotica","pornographic"],C=n=>!!n.contentRating&&v.includes(n.contentRating),h=n=>M(n,"manga_progress","manga_library","mangaId","COALESCE(l.contentRating, p.contentRating) IN ('erotica', 'pornographic')"),y=()=>p(n=>({mangaId:m(d(n),"mangaId")})),S=()=>p(n=>{const t=d(n),a={mangaId:m(t,"mangaId"),chapterId:m(t,"chapterId")};if(t.chapterNumber!==void 0&&t.chapterNumber!==null&&(a.chapterNumber=String(t.chapterNumber)),t.page!==void 0&&t.page!==null){const c=Number(t.page);Number.isNaN(c)||(a.page=c)}if(t.pageCount!==void 0&&t.pageCount!==null){const c=Number(t.pageCount);Number.isNaN(c)||(a.pageCount=c)}const e=g(t,"title");e!==void 0&&(a.title=e);const o=g(t,"cover");o!==void 0&&(a.cover=o);const s=g(t,"provider");s!==void 0&&(a.provider=s);const u=g(t,"altTitle");u!==void 0&&(a.altTitle=u);const I=g(t,"contentRating");return I!==void 0&&(a.contentRating=I),a}),w=()=>p(n=>{const t=d(n),a={mangaId:m(t,"mangaId")},e=g(t,"chapterId");return e!==void 0&&(a.chapterId=e),a}),P=()=>p(n=>({ids:f(d(n),"ids",!0)})),A=()=>p(n=>{const t=d(n),a={};if(t.limit!==void 0&&t.limit!==null){const e=Number(t.limit);Number.isNaN(e)||(a.limit=e)}return a}),O=b({byManga:r.input(y()).query(async({ctx:n,input:t})=>{try{return{progress:await i.getByManga(n.mangaDb,t.mangaId)}}catch{return{progress:[]}}}),latest:r.input(y()).query(async({ctx:n,input:t})=>{try{return await i.getLatest(n.mangaDb,t.mangaId)||{page:0,pageCount:0}}catch{return{page:0,pageCount:0}}}),save:r.input(S()).mutation(async({ctx:n,input:t})=>{const a=Math.max(Number(t.page)||0,0),e=Math.max(Number(t.pageCount)||0,0);return await l(n.mangaDb,async o=>{await i.upsert(o,{mangaId:String(t.mangaId),chapterId:String(t.chapterId),chapterNumber:String(t.chapterNumber||""),page:a,pageCount:e,title:t.title??null,cover:t.cover??null,provider:t.provider??null,altTitle:t.altTitle??null,contentRating:t.contentRating??null}),await R.touchProgress(o,String(t.mangaId),{chapterId:String(t.chapterId),chapterNumber:String(t.chapterNumber||""),page:a})}),{success:!0}}),remove:r.input(w()).mutation(async({ctx:n,input:t})=>(await l(n.mangaDb,async a=>{t.chapterId?await i.deleteChapter(a,String(t.mangaId),String(t.chapterId)):await i.deleteByManga(a,String(t.mangaId))}),{success:!0})),removeMany:r.input(P()).mutation(async({ctx:n,input:t})=>{const a=t.ids.map(e=>String(e));return await l(n.mangaDb,async e=>{await i.deleteMany(e,a)}),{success:!0,removed:a.length}}),continueReading:r.input(A()).query(async({ctx:n,input:t})=>{try{const a=Math.min(Math.max(parseInt(String(t.limit))||24,1),100),{ignoreAdult:e,listOnly:o}=await N.readContinueFlags(n.db,"mangaIgnoreAdultContent","mangaCwWatchlistOnly"),s=(await i.getContinueReading(n.mangaDb,a)).filter(u=>!(e&&C(u)||o&&u.watchlistStatus!=="Reading"));return{data:s,total:s.length}}catch{return{data:[],total:0}}}),adultCount:r.query(async({ctx:n})=>{try{return{count:(await h(n.mangaDb)).length}}catch{return{count:0}}}),purgeAdult:r.mutation(async({ctx:n})=>{const t=await h(n.mangaDb);return t.length>0&&await l(n.mangaDb,async a=>{await i.deleteMany(a,t)}),{success:!0,removed:t.length}})});export{O as mangaProgressRouter};
@@ -1 +0,0 @@
1
- import{TRPCError as $}from"@trpc/server";import{protectedProcedure as g,router as w}from"../index.js";import{parseJsonBody as P}from"../../utils/http.utils.js";import{defineSchema as f,optStr as u,reqObj as y}from"../validation.js";import c from"../../logger.js";const v=["safe"];function l(a){const t=a.mature==="1",r=String(a.rating||"safe").split(",").map(e=>e.trim().toLowerCase()).filter(e=>["safe","suggestive","erotica","pornographic"].includes(e));if(!t)return v;const n=[...new Set(r)];return n.length>0?n:v}function d(a,t){return a.getMangaProvider(t.toLowerCase())}async function p(a,t,r,n){const e=a.apiCache.get(t);if(e)return e;const s=await n();if((s.status??200)>=200&&(s.status??200)<400&&(r!==void 0?a.apiCache.set(t,s.body,r):a.apiCache.set(t,s.body)),s.status!==void 0&&s.status!==200)throw new $({code:s.status===400?"BAD_REQUEST":s.status===404?"NOT_FOUND":"INTERNAL_SERVER_ERROR",message:s.body?.error||"Request failed"});return s.body}const C=()=>f(a=>{const t=y(a),r={};for(const n of["provider","q","sort","status","type","rating","mature"]){const e=u(t,n);e!==void 0&&(r[n]=e)}return t.page!==void 0&&(r.page=parseInt(String(t.page),10)||1),t.limit!==void 0&&(r.limit=Math.min(parseInt(String(t.limit),10)||24,50)),r}),M=()=>f(a=>{const t=y(a),r={};for(const n of["provider","id","rating","mature"]){const e=u(t,n);e!==void 0&&(r[n]=e)}return r}),N=()=>f(a=>{const t=y(a),r={},n=u(t,"provider");n!==void 0&&(r.provider=n);const e=u(t,"id");return e!==void 0&&(r.id=e),r}),E=w({trending:g.query(async({ctx:a})=>{const t="route-manga-trending",r=a.apiCache.get(t);if(r)return r;try{const e=await fetch("https://api.mangadex.org/manga?limit=6&order%5BfollowedCount%5D=desc&includes%5B%5D=cover_art&contentRating%5B%5D=safe&contentRating%5B%5D=suggestive",{headers:{"User-Agent":"dango/3.1.9"}});if(!e.ok)return[];const i=((await P(e)).data||[]).map(o=>{const h=o.attributes?.title?.en||Object.values(o.attributes?.title||{})[0]||"",I=o.attributes?.description?.en||"",S=(o.attributes?.tags||[]).map(m=>m.attributes?.name?.en).filter(Boolean),b=o.relationships?.find(m=>m.type==="cover_art")?.attributes?.fileName,R=b?`https://uploads.mangadex.org/covers/${o.id}/${b}.512.jpg`:"";return{id:o.id,title:h,description:I,tags:S,year:o.attributes?.year||null,status:o.attributes?.status||null,cover:R}});return a.apiCache.set(t,i,600),i}catch{return[]}}),search:g.input(C()).query(async({ctx:a,input:t})=>{const r=t.provider||"",n=t.q||"",e=t.page||1;return p(a,`route-manga-search-${r}-${n}-${e}-${t.sort||""}-${t.status||""}-${t.type||""}-${t.rating||""}-${t.mature||""}`,300,async()=>{try{const s=l({rating:t.rating,mature:t.mature}),i=t.type||"",o=i==="doujinshi"&&t.mature!=="1"?"":i;return{body:await d(a,r)?.search({query:n,page:e,limit:t.limit||24,sort:t.sort||"popular",status:t.status||"",type:o,ratings:s})??{items:[],hasNext:!1}}}catch(s){return c.error({err:s},"[Manga] search failed"),{body:{items:[],hasNext:!1}}}})}),info:g.input(M()).query(async({ctx:a,input:t})=>{const r=String(t.provider||""),n=String(t.id||""),e=String(t.rating||""),s=String(t.mature||"");return p(a,`route-manga-info-${r}-${n}-${e}-${s}`,600,async()=>{try{if(!n)return{status:400,body:{error:"Missing id"}};const i=await d(a,r)?.getDetail(n,l({rating:e,mature:s}));return i?{body:i}:{status:404,body:{error:"Not found"}}}catch(i){return c.error({err:i},"[Manga] info failed"),{status:500,body:{error:"Failed to load manga"}}}})}),chapters:g.input(M()).query(async({ctx:a,input:t})=>{const r=String(t.provider||""),n=String(t.id||""),e=String(t.rating||""),s=String(t.mature||"");return p(a,`route-manga-chapters-${r}-${n}-${e}-${s}`,600,async()=>{try{return n?{body:{chapters:await d(a,r)?.getChapters(n,l({rating:e,mature:s}))??[]}}:{status:400,body:{error:"Missing id"}}}catch(i){return c.error({err:i},"[Manga] chapters failed"),{body:{chapters:[]}}}})}),pages:g.input(N()).query(async({ctx:a,input:t})=>{const r=String(t.provider||""),n=String(t.id||"");return p(a,`route-manga-pages-${r}-${n}`,300,async()=>{try{return n?{body:{pages:await d(a,r)?.getPages(n)??[]}}:{status:400,body:{error:"Missing id"}}}catch(e){return c.error({err:e},"[Manga] pages failed"),{body:{pages:[]}}}})})});export{E as mangaRouter};
@@ -1 +0,0 @@
1
- import{TRPCError as N}from"@trpc/server";import{protectedProcedure as y,router as $}from"../index.js";import{badRequest as I,defineSchema as A,failed as x,optStr as R,reqObj as U}from"../validation.js";import u from"../../logger.js";import{getInnertube as M,getAuthedInnertube as m,refreshAuthedInnertube as T,saveMusicCookie as j,getMusicAuthStatus as F,signOutMusic as H,isParserVariantError as Y,verifyMusicSession as V}from"../../lib/ytmusic.js";const Q=[/\s*(- topic)$/i,/\s*vevo$/i,/\s*[(|[]official(.*?)[)|\]]/i,/\s*[(|[]((lyrics?|visualizer|audio)\s*(video)?)[)|\]]/i,/\s*[(|[](performance video)[)|\]]/i,/\s*[(|[](clip official)[)|\]]/i,/\s*[(|[](video version)[)|\]]/i,/\s*[(|[](HD|HQ)\s*?(?:audio)?[)|\]]$/i,/\s*[(|[](live)[)|\]]$/i,/\s*[(|[]4K\s*?(?:upgrade)?[)|\]]$/i];function h(i){if(!i)return i;let n=i;for(const t of Q)n=n.replace(t,"");return n.trim()}function O(i){const n=i,t=n.video_id||n.videoId||n.id||"";if(!t||typeof t!="string")return null;const r=n.title,a=typeof r=="string"?r:r?.text??"Unknown title",s=n.artists??n.authors??n.subtitle;let c="";Array.isArray(s)?c=s.map(d=>{const f=d;return f.name||f.text||f.title||""}).filter(Boolean).join(", "):typeof s=="string"?c=s:s&&typeof s=="object"&&(c=s.text??"");const e=n.thumbnails??n.thumbnail,o=Array.isArray(e)?e:Array.isArray(e?.thumbnails)?e.thumbnails:[],l=E(n.album),k=E(n.duration);return{id:t,title:h(String(a)),artists:h(c),album:l?h(l):void 0,duration:k||void 0,thumbnails:o}}function E(i){if(!i)return"";if(typeof i=="string")return i;const n=i;return typeof n.text=="string"?n.text:Array.isArray(n.runs)?n.runs.map(t=>t.text||"").join(""):""}function _(i){if(!i)return[];if(Array.isArray(i))return i.filter(t=>t&&typeof t.url=="string").map(t=>({url:t.url,width:t.width,height:t.height}));const n=i;return Array.isArray(n.thumbnails)?_(n.thumbnails):[]}function P(i){const n=i;return typeof n?.type=="string"?n.type:n?.constructor?.name??""}function K(i,n=!1){const t=P(i);if(t==="AutomixPreviewVideo")return null;const r=i,a=t==="PlaylistPanelVideoWrapper"?r.primary??null:r;if(!a||typeof a!="object")return null;const s=a.video_id;if(typeof s!="string"||!s)return null;const c=a.artists;let e="";Array.isArray(c)&&(e=c.map(f=>f?.name||"").filter(Boolean).join(", ")),!e&&typeof a.author=="string"&&(e=a.author);const o=a.album,l=a.duration,k=typeof l=="string"?l:l?.text??"";let d=null;if(n)for(const f of[a.menu,i.menu]){let w=null;for(const b of D(f)){if(P(b)!=="ToggleMenuServiceItem")continue;const g=b.icon_type;if(!(g!=="FAVORITE"&&g!=="UNFAVORITE")){w=g==="UNFAVORITE";break}}if(w!==null){d=w;break}}return{id:s,title:h(E(a.title)||"Unknown title"),artists:h(e||"Unknown artist"),album:o?.name?h(o.name):void 0,duration:k||void 0,thumbnails:_(a.thumbnail??a.thumbnails),...d!==null?{liked:d}:{}}}function B(i){return!!i?.payload&&"signInEndpoint"in i.payload}function C(i,n){const t=[],r=i,a=r.contents??[];for(const c of a){const e=K(c,n);if(e&&t.push(e),t.length>=50)break}const s=r.playlist_id??null;return{tracks:t,playlistId:s}}function D(i){if(!i||typeof i!="object")return[];const n=i.items;return Array.isArray(n)?n:[]}function X(i,n){const t={like:null,unlike:null},r=P(i),a=i,s=r==="PlaylistPanelVideoWrapper"?a.primary??null:a;if(!s||s.video_id!==n)return t;const c=[s.menu,a.menu];for(const e of c)for(const o of D(e)){if(P(o)!=="ToggleMenuServiceItem")continue;const l=o.icon_type;if(!(l!=="FAVORITE"&&l!=="UNFAVORITE")){for(const k of["default_endpoint","toggled_endpoint"]){const d=o[k];if(typeof d?.call!="function")continue;const f=d.payload?.status;f==="LIKE"&&!t.like?t.like=d:f==="INDIFFERENT"&&!t.unlike&&(t.unlike=d)}if(t.like&&t.unlike)return t}}return t}async function S(i,n,t=500){const r=[];let a=await i.music.getPlaylist(n);for(let s=0;s<6;s++){for(const c of(a.items??a.contents??[]).slice(0,t-r.length)){const e=O(c);if(e&&r.push(e),r.length>=t)break}if(r.length>=t||!a.has_continuation||typeof a.getContinuation!="function")break;try{a=await a.getContinuation()}catch(c){u.warn({err:c},"[music] playlist continuation failed");break}}return r}async function v(i){let n;try{n=await i.music.getLibrary()}catch(e){if(!Y(e))throw e;u.warn("[music] library landing hit a layout variant, falling back to Liked playlist");try{return{tracks:await S(i,"LM"),playlists:[]}}catch(o){return u.warn({err:o},"[music] liked playlist fallback failed"),{tracks:[],playlists:[]}}}const t=[];for(const e of n.contents??[]){const o=e.items??e.contents??[];t.push(...o)}const r=[],a=new Set;for(const e of t){const o=e,l=typeof o.playlist_id=="string"&&o.playlist_id||typeof o.playlistId=="string"&&o.playlistId||typeof o.id=="string"&&o.id||"",k=E(o.title),d=E(o.subtitle);!l||!k||a.has(l)||!/playlist|mix/i.test(d)&&k!=="Liked Music"||(a.add(l),r.push({id:l,title:k,subtitle:d||void 0,thumbnails:_(o.thumbnails??o.thumbnail)}))}const s=[],c=r.find(e=>e.title==="Liked Music");if(c)try{s.push(...await S(i,c.id))}catch(e){u.warn({err:e},"[music] liked playlist fetch failed")}return u.info(`[music] library parsed tracks=${s.length} playlists=${r.length} gridItems=${t.length}`),{tracks:s,playlists:r}}function p(i){return new N({code:"UNAUTHORIZED",message:i})}const z=()=>A(i=>{const n=U(i),t={},r=R(n,"q");return r!==void 0&&(t.q=r),t}),q=()=>A(i=>{const n=U(i),t={},r=R(n,"id");return r!==void 0&&(t.id=r),t}),J=()=>A(i=>{const n=U(i),t={},r=R(n,"cookie");return r!==void 0&&(t.cookie=r),t}),W=()=>A(i=>{const n=U(i),t={},r=R(n,"id");return r!==void 0&&(t.id=r),(typeof n.like=="boolean"||typeof n.like=="string")&&(t.like=n.like),t});function L(i){const n=String(i.id||"").trim();if(!n)throw I("id is required");return n}const et=$({authStatus:y.query(()=>F()),authStart:y.input(J()).mutation(async({input:i})=>{let n=String(i.cookie||"").trim();if(!n)throw I("Cookie is required");if(n=n.replace(/^cookie\s*:\s*/i,"").replace(/^["']+|["']+$/g,"").trim(),n.includes("\u2026")||n.includes("..."))throw I(`Cookie looks truncated (${n.length} chars). In DevTools, right-click the Cookie header \u2192 Copy value (do not copy from the wrapped preview).`);if(!n.includes("SID")&&!n.includes("SAPISID"))throw I('That does not look like a YouTube cookie at all. Copy the full Cookie request header from a "browse" call on music.youtube.com in DevTools \u2192 Network.');try{await j(n);const t=await m();if(t)try{const r=await v(t);return{success:!0,tracks:r.tracks.length,playlists:r.playlists.length}}catch{}return{success:!0,tracks:0,playlists:0}}catch(t){throw t instanceof N?t:(u.error({err:t},"[music] cookie sign-in failed"),p("Cookie rejected by YouTube Music. Copy a fresh Cookie header from music.youtube.com."))}}),signOut:y.mutation(async()=>{try{return await H(),{success:!0}}catch(i){throw u.error({err:i},"[music] sign-out failed"),x("Sign-out failed")}}),search:y.input(z()).query(async({ctx:i,input:n})=>{const t=String(n.q||"").trim();if(!t)return{tracks:[]};const r=`music-search-${t.toLowerCase()}`,a=i.apiCache.get(r);if(a)return{tracks:a};try{const c=await(await M()).music.search(t,{type:"song"}),e=[],o=c.contents??[];for(const l of o){const k=l.contents??[l];for(const d of k){const f=O(d);if(f&&e.push(f),e.length>=25)break}if(e.length>=25)break}return i.apiCache.set(r,e,300),{tracks:e}}catch(s){return u.error({err:s},"[music] search failed"),{tracks:[]}}}),track:y.input(q()).query(async({ctx:i,input:n})=>{const t=L(n),r=`music-track-${t.toLowerCase()}`,a=i.apiCache.get(r);if(a)return{track:a};try{const e=(await(await M()).music.getInfo(t)).basic_info;if(!e?.title)return{track:null};const o=typeof e.duration=="number"&&Number.isFinite(e.duration)?Math.max(Math.round(e.duration),0):0,l={id:t,title:h(String(e.title)),artists:h(String(e.author||"Unknown artist")),duration:o?`${Math.floor(o/60)}:${String(o%60).padStart(2,"0")}`:void 0,thumbnails:Array.isArray(e.thumbnail)?e.thumbnail:[]};return i.apiCache.set(r,l,300),{track:l}}catch(s){return u.error({err:s},"[music] track lookup failed"),{track:null}}}),playlist:y.input(q()).query(async({input:i})=>{const n=L(i),t=await m().catch(()=>null);if(!t)throw p("MUSIC_AUTH_REQUIRED");try{return{tracks:await S(t,n)}}catch(r){u.warn({err:r},"[music] playlist failed, retrying with a fresh saved-cookie session");const a=await T();if(!a)return{tracks:[]};try{return{tracks:await S(a,n)}}catch(s){return u.warn({err:s},"[music] playlist retry failed, returning empty"),{tracks:[]}}}}),library:y.query(async()=>{const i=await m().catch(()=>null);if(!i)throw p("MUSIC_AUTH_REQUIRED");try{const t=await v(i);if(t.tracks.length>0||t.playlists.length>0)return t;u.warn("[music] library came back empty, retrying with a fresh saved-cookie session")}catch(t){u.warn({err:t},"[music] library failed, retrying with a fresh saved-cookie session")}const n=await T();if(!n)return{tracks:[],playlists:[]};try{return await v(n)}catch(t){return u.warn({err:t},"[music] library retry failed, returning empty"),{tracks:[],playlists:[]}}}),home:y.query(async()=>{try{const n=await(await M()).music.getHomeFeed();return JSON.parse(JSON.stringify(n))}catch(i){return u.error({err:i},"[music] home feed failed"),{contents:[]}}}),upnext:y.input(q()).query(async({ctx:i,input:n})=>{const t=L(n),r=await m().catch(()=>null);if(!r){const s=`music-upnext-${t.toLowerCase()}`,c=i.apiCache.get(s);if(c)return c;try{const e=await M(),o=C(await e.music.getUpNext(t),!1);return i.apiCache.set(s,o,300),o}catch(e){return u.error({err:e},"[music] upnext failed"),{tracks:[],playlistId:null}}}try{return C(await r.music.getUpNext(t),!0)}catch(s){u.warn({err:s},"[music] authed upnext failed, retrying with a fresh saved-cookie session")}const a=await T();if(a)try{return C(await a.music.getUpNext(t),!0)}catch(s){u.warn({err:s},"[music] upnext retry failed, falling back to public")}try{const s=await M();return C(await s.music.getUpNext(t),!1)}catch(s){return u.error({err:s},"[music] upnext failed"),{tracks:[],playlistId:null}}}),likes:y.query(async({ctx:i})=>{const n=await m().catch(()=>null);if(!n)throw p("MUSIC_AUTH_REQUIRED");const t="music-liked-ids",r=i.apiCache.get(t);if(r)return{likedIds:r};const a=async s=>(await S(s,"LM",500)).map(e=>e.id);try{const s=await a(n);return i.apiCache.set(t,s,120),{likedIds:s}}catch(s){u.warn({err:s},"[music] liked ids failed, retrying with a fresh saved-cookie session");const c=await T();if(!c)return{likedIds:[]};try{const e=await a(c);return i.apiCache.set(t,e,120),{likedIds:e}}catch(e){return u.warn({err:e},"[music] liked ids retry failed, returning empty"),{likedIds:[]}}}}),like:y.input(W()).mutation(async({ctx:i,input:n})=>{const t=String(n.id||"").trim(),r=n.like===!0||n.like==="true";if(!t)throw I("id is required");const a=await m().catch(()=>null);if(!a||!await V())throw p("MUSIC_AUTH_REQUIRED");const s=async c=>{const e=c,o=e.actions??e.session?.actions;if(!o)throw new Error("Actions unavailable");const k=(await c.music.getUpNext(t)).contents??[];let d=null,f=!1;for(const w of k){const b=X(w,t),g=r?b.like:b.unlike;if(B(g)){f=!0;continue}if(g){d=g;break}}if(f&&!d)throw new Error("MUSIC_SESSION_EXPIRED");if(!d)throw new Error("Like toggle not found for track");await d.call(o)};try{await s(a)}catch(c){if(c.message==="MUSIC_SESSION_EXPIRED")throw u.warn("[music] like rejected, session expired"),p("MUSIC_AUTH_REQUIRED");u.warn({err:c},"[music] rate failed, retrying with a fresh saved-cookie session");const e=await T();if(!e)throw x("Like action failed");try{await s(e)}catch(o){throw o.message==="MUSIC_SESSION_EXPIRED"?p("MUSIC_AUTH_REQUIRED"):(u.error({err:o},"[music] rate retry failed"),x("Like action failed"))}}return i.apiCache.delete("music-liked-ids"),u.info(`[music] rate id=${t} liked=${r} via=toggle`),{success:!0,liked:r}})});export{et as musicRouter};
@@ -1 +0,0 @@
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 +0,0 @@
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 +0,0 @@
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 +0,0 @@
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 +0,0 @@
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};
@@ -1 +0,0 @@
1
- import{TRPCError as i}from"@trpc/server";import{protectedProcedure as s,router as g}from"../index.js";import{SettingsRepository as u}from"../../repositories/settings.repository.js";import{performWriteTransactionAsync as h}from"../../sync.js";import{AniListTracker as T}from"../../lib/tracker/anilist-tracker.js";import{syncAniList as I,importFromMalUsername as E,importFromUsername as M}from"../../lib/tracker/sync.service.js";import{syncAniListManga as U,importFromUsernameManga as S,importMangaFromMalUsername as R}from"../../lib/tracker/manga-sync.service.js";import{badRequest as m,defineSchema as p,failed as c,reqObj as A}from"../validation.js";const k="tracker_anilist_token",f="tracker_anilist_user";function y(r){return new i({code:"SERVICE_UNAVAILABLE",message:r})}function d(r){return r==null?{}:A(r)}const D=()=>p(r=>{const e=d(r);return{token:e.token,code:e.code,redirectUri:e.redirectUri}}),O=()=>p(r=>({provider:d(r).provider})),w=()=>p(r=>{const e=d(r);return{username:e.username,erase:e.erase}}),C=()=>p(r=>{const e=d(r);return{username:e.username,erase:e.erase,useOfflineDb:e.useOfflineDb,skipFallback:e.skipFallback}}),F=()=>p(r=>({direction:d(r).direction}));function b(r){return r.includes("private")||r.includes("not found")?new i({code:"NOT_FOUND",message:r}):r.includes("blocked")||r.includes("HTTP 429")?new i({code:"TOO_MANY_REQUESTS",message:r}):c(r)}const B=g({status:s.query(async({ctx:r})=>{try{const e=await u.getByKey(r.db,k),a=await u.getByKey(r.db,f);let n=null;if(a?.value)try{n=JSON.parse(a.value)}catch{n=null}return{anilist:{connected:!!e?.value,user:n}}}catch{throw c("Failed to read tracker status")}}),anilistAuth:s.input(D()).mutation(async({ctx:r,input:e})=>{const a=typeof e.token=="string"?e.token.trim():"";if(!a)throw m("Access token is required");try{const t=await new T(a).getViewer();return await h(r.db,async o=>{await u.upsert(o,k,a),await u.upsert(o,f,JSON.stringify(t))}),{success:!0,user:t}}catch(n){throw new i({code:"UNAUTHORIZED",message:n instanceof Error?n.message:"Authentication failed"})}}),anilistDisconnect:s.mutation(async({ctx:r})=>{try{return await h(r.db,async e=>{await u.upsert(e,k,""),await u.upsert(e,f,"")}),{success:!0}}catch{throw c("Failed to disconnect")}}),sync:s.input(O()).mutation(async({ctx:r,input:e})=>{const{provider:a="anilist"}=e;if(a!=="anilist")throw m(`Provider "${a}" is not supported yet`);try{return{success:!0,summary:await I(r.db)}}catch(n){throw c(n instanceof Error?n.message:"Sync failed")}}),anilistImport:s.input(w()).mutation(async({ctx:r,input:e})=>{const{username:a,erase:n}=e;if(!a||typeof a!="string")throw m("Username is required");try{return{success:!0,count:await M(r.db,a.trim(),n===!0)}}catch(t){throw c(t instanceof Error?t.message:"Import failed")}}),malImport:s.input(C()).mutation(async({ctx:r,input:e})=>{const{username:a,erase:n,useOfflineDb:t,skipFallback:o}=e;if(!a||typeof a!="string")throw m("MAL username is required");try{return{success:!0,count:await E(r.db,a.trim(),{erase:n===!0,useOfflineDb:t!==!1,skipFallback:o===!0})}}catch(l){throw b(l instanceof Error?l.message:"Import failed")}}),mangaSync:s.input(F()).mutation(async({ctx:r,input:e})=>{const{direction:a}=e;try{const n=r.mangaDb;if(!n||n.isClosedCheck())throw y("Manga database is not ready");const t=a==="pull-only"?"pull-only":"two-way",o=await U(r.db,n,{direction:t});return{success:!0,summary:o.summary,details:o.details,direction:t}}catch(n){throw n instanceof i?n:c(n instanceof Error?n.message:"Manga sync failed")}}),mangaImport:s.input(w()).mutation(async({ctx:r,input:e})=>{const{username:a,erase:n}=e;if(!a||typeof a!="string")throw m("Username is required");try{const t=r.mangaDb;if(!t||t.isClosedCheck())throw y("Manga database is not ready");return{success:!0,count:await S(r.db,t,a.trim(),n===!0)}}catch(t){throw t instanceof i?t:c(t instanceof Error?t.message:"Import failed")}}),mangaMalImport:s.input(w()).mutation(async({ctx:r,input:e})=>{const{username:a,erase:n}=e;if(!a||typeof a!="string")throw m("Username is required");try{const t=r.mangaDb;if(!t||t.isClosedCheck())throw y("Manga database is not ready");return{success:!0,...await R(r.db,t,a.trim(),n===!0)}}catch(t){throw t instanceof i?t:b(t instanceof Error?t.message:"Import failed")}})});export{B as trackerRouter};
@@ -1 +0,0 @@
1
- import{TRPCError as b}from"@trpc/server";import{protectedProcedure as n,router as g}from"../index.js";import{performTvWriteTransactionAsync as o}from"../../sync.js";import{buildTvId as w,TV_STATUSES as v,TvLibraryRepository as s,TvProgressRepository as p}from"../../repositories/tv.repository.js";import{defineSchema as i,reqObj as u,reqStr as y,optStr as S,reqStrArray as I,readPageLimit as T}from"../validation.js";function L(r){return String(r).toLowerCase()==="movie"?"movie":"tv"}const l=()=>i(r=>{const t=u(r),a={...T(t)},e=S(t,"status");return e!==void 0&&(a.status=e),a}),f=()=>i(r=>({id:y(u(r),"id")})),h=()=>i(r=>{const t=u(r);return{tmdbId:t.tmdbId,mediaType:t.mediaType,id:t.id,title:t.title,poster:t.poster,backdrop:t.backdrop,year:t.year,overview:t.overview,status:t.status,adult:t.adult}}),k=()=>i(r=>{if(typeof r!="string"||r.length===0)throw new Error("id must be a string");return r}),A=()=>i(r=>{const t=u(r);return{id:y(t,"id"),status:y(t,"status")}}),D=()=>i(r=>{const t=u(r);return{ids:I(t,"ids",!0),status:y(t,"status")}}),E=()=>i(r=>{if(!Array.isArray(r)||r.length===0)throw new Error("ids must be a non-empty array");return r.map(t=>String(t))}),x=g({list:n.input(l()).query(async({ctx:r,input:t})=>{const a=Math.max(parseInt(String(t.page??1))||1,1),e=Math.min(Math.max(parseInt(String(t.limit??24))||24,1),100),d=(a-1)*e,c=await s.getAll(r.tvDb,t.status,e,d),m=await s.getCount(r.tvDb,t.status);return{data:c,total:m,page:a,limit:e}}),ids:n.query(async({ctx:r})=>({ids:await s.getIds(r.tvDb)})),check:n.input(f()).query(async({ctx:r,input:t})=>{const a=await s.getById(r.tvDb,t.id);return{inLibrary:!!a,status:a?.status??null}}),add:n.input(h()).mutation(async({ctx:r,input:t})=>{const a=Number(t.tmdbId);if(!a||!t.id&&!t.mediaType)throw new b({code:"BAD_REQUEST",message:"tmdbId and mediaType are required"});const e=L(t.mediaType||(String(t.id).startsWith("movie:")?"movie":"tv")),d=t.id&&String(t.id).includes(":")?String(t.id):w(e,a),c=typeof t.status=="string"&&v.includes(t.status)?t.status:"Watching";return await o(r.tvDb,async m=>{await s.upsert(m,{id:d,tmdbId:a,mediaType:e,title:String(t.title||""),poster:String(t.poster||""),backdrop:t.backdrop?String(t.backdrop):void 0,year:t.year?String(t.year):void 0,overview:t.overview?String(t.overview):void 0,status:c,adult:t.adult===!0})}),{success:!0,id:d}}),remove:n.input(k()).mutation(async({ctx:r,input:t})=>(await o(r.tvDb,async a=>{await s.delete(a,String(t)),await p.deleteByMedia(a,String(t))}),{success:!0})),setStatus:n.input(A()).mutation(async({ctx:r,input:t})=>{if(!t.id||!v.includes(t.status))throw new b({code:"BAD_REQUEST",message:"id and a valid status are required"});return await o(r.tvDb,async a=>{await s.updateStatus(a,String(t.id),String(t.status))}),{success:!0}}),batchStatus:n.input(D()).mutation(async({ctx:r,input:t})=>{if(!t.status||!v.includes(t.status))throw new b({code:"BAD_REQUEST",message:"a valid status is required"});const a=t.ids.map(e=>String(e));return await o(r.tvDb,async e=>{await s.updateStatusMany(e,a,String(t.status))}),{success:!0,updated:a.length}}),removeMany:n.input(E()).mutation(async({ctx:r,input:t})=>{const a=t.map(e=>String(e));return await o(r.tvDb,async e=>{await s.deleteMany(e,a),await p.deleteMany(e,a)}),{success:!0,removed:a.length}})});export{x as tvLibraryRouter};
@@ -1 +0,0 @@
1
- import{TRPCError as b}from"@trpc/server";import{protectedProcedure as a,router as h}from"../index.js";import{performTvWriteTransactionAsync as p}from"../../sync.js";import{adultNonListIds as f}from"../../utils/db-utils.js";import{SettingsRepository as M}from"../../repositories/settings.repository.js";import{normalizeTvMediaId as l,TvLibraryRepository as P,TvProgressRepository as s}from"../../repositories/tv.repository.js";import{defineSchema as u,reqObj as c,reqStr as g,strOrNull as m}from"../validation.js";const N=t=>t.adult===!0||t.adult===1,w=t=>f(t,"tv_progress","tv_library","mediaId","COALESCE(l.adult, p.adult) = 1"),S=()=>u(t=>({mediaId:g(c(t),"mediaId")})),k=()=>u(t=>{const e=c(t);return{mediaId:g(e,"mediaId"),season:e.season,episode:e.episode}}),C=()=>u(t=>{const e=c(t);return{mediaId:g(e,"mediaId"),season:e.season,episode:e.episode,currentTime:e.currentTime,duration:e.duration,completed:e.completed,title:m(e,"title"),poster:m(e,"poster"),backdrop:m(e,"backdrop"),year:m(e,"year"),overview:m(e,"overview"),tmdbId:e.tmdbId??null,mediaType:m(e,"mediaType"),adult:e.adult??null}}),D=()=>u(t=>{const e=c(t);return{mediaId:g(e,"mediaId"),season:e.season,episode:e.episode}}),R=()=>u(t=>{if(!Array.isArray(t)||t.length===0)throw new Error("ids must be a non-empty array");return t.map(e=>String(e))}),W=()=>u(t=>{const e=c(t),n={};if(e.limit!==void 0&&e.limit!==null&&e.limit!==""){const r=Number(e.limit);Number.isFinite(r)&&(n.limit=r)}return n}),O=h({getByMedia:a.input(S()).query(async({ctx:t,input:e})=>({progress:await s.getByMedia(t.tvDb,l(e.mediaId))})),latest:a.input(k()).query(async({ctx:t,input:e})=>{const n=l(e.mediaId),r=parseInt(String(e.season),10),i=parseInt(String(e.episode),10);return Number.isFinite(r)&&Number.isFinite(i)?await s.getEpisode(t.tvDb,n,r,i)||{currentTime:0,duration:0,completed:0}:await s.getLatest(t.tvDb,n)||{currentTime:0,duration:0,completed:0}}),save:a.input(C()).mutation(async({ctx:t,input:e})=>{const n=l(e.mediaId);if(!n)throw new b({code:"BAD_REQUEST",message:"mediaId is required"});const r=Math.max(Number(e.season)||1,1),i=Math.max(Number(e.episode)||1,1),d=Math.max(Number(e.currentTime)||0,0),o=Math.max(Number(e.duration)||0,0),v=o>0?Math.min(d,o):d,I=o>0&&v>=o*.8?o:v,T=Number(e.completed)===1||o>0&&I>=o*.8?1:0;return await p(t.tvDb,async y=>{await s.upsert(y,{mediaId:String(n),season:Math.round(r),episode:Math.round(i),currentTime:I,duration:o,completed:T,title:e.title??null,poster:e.poster??null,backdrop:e.backdrop??null,year:e.year??null,overview:e.overview??null,tmdbId:e.tmdbId!=null?Number(e.tmdbId):null,mediaType:e.mediaType??null,adult:e.adult!=null?Number(e.adult):null}),await P.touchProgress(y,String(n),{season:Math.round(r),episode:Math.round(i)})}),{success:!0}}),remove:a.input(D()).mutation(async({ctx:t,input:e})=>{const n=l(e.mediaId);if(!n)throw new b({code:"BAD_REQUEST",message:"mediaId is required"});return await p(t.tvDb,async r=>{e.season!==void 0&&e.episode!==void 0?await s.deleteEpisode(r,String(n),Number(e.season),Number(e.episode)):await s.deleteByMedia(r,String(n))}),{success:!0}}),removeMany:a.input(R()).mutation(async({ctx:t,input:e})=>{const n=e.map(r=>l(r));return await p(t.tvDb,async r=>{await s.deleteMany(r,n)}),{success:!0,removed:n.length}}),continueWatching:a.input(W()).query(async({ctx:t,input:e})=>{const n=Math.min(Math.max(parseInt(String(e.limit??24))||24,1),100),{ignoreAdult:r,listOnly:i}=await M.readContinueFlags(t.db,"tvIgnoreAdultContent","tvCwWatchlistOnly"),d=(await s.getContinueWatching(t.tvDb,n)).filter(o=>!(r&&N(o)||i&&o.watchlistStatus!=="Watching"));return{data:d,total:d.length}}),adultCount:a.query(async({ctx:t})=>({count:(await w(t.tvDb)).length})),purgeAdult:a.mutation(async({ctx:t})=>{const e=await w(t.tvDb);return e.length>0&&await p(t.tvDb,async n=>{for(const r of e)await s.deleteByMedia(n,r)}),{success:!0,removed:e.length}})});export{O as tvProgressRouter};
@@ -1 +0,0 @@
1
- import{TRPCError as $}from"@trpc/server";import{protectedProcedure as h,router as C}from"../index.js";import{defineSchema as I,failed as g,optStr as b,readCount as q,reqId as k,reqObj as T,reqStr as M}from"../validation.js";import{getTmdbKey as w,TMDB_BASE as f,TMDB_IMAGE as B}from"../../lib/tmdb.js";import{parseJsonBody as _}from"../../utils/http.utils.js";async function D(s,e,r){const n={...r};if(n.title&&n.imdbId&&n.year)return n;try{const t=await w();if(t){const a=await fetch(`${f}/${s}/${e}?api_key=${t}&append_to_response=external_ids`,{headers:{"User-Agent":"Mozilla/5.0"},signal:AbortSignal.timeout(4e3)});if(a.ok){const i=await _(a);n.title||(n.title=i.title||i.name||""),n.year||(n.year=(i.release_date||i.first_air_date||"").split("-")[0]||""),n.imdbId||(n.imdbId=i.external_ids?.imdb_id||i.imdb_id||""),s==="tv"&&i.number_of_seasons&&(n.totalSeasons=String(i.number_of_seasons))}}}catch{}return n}function P(s,e){return{id:s.id,title:s.title||s.name||"",year:(s.release_date||s.first_air_date||"").split("-")[0],type:e,image:s.poster_path?`${B}/w500${s.poster_path}`:"",backdrop:s.backdrop_path?`${B}/w780${s.backdrop_path}`:"",overview:s.overview||"",vote_average:s.vote_average||0,adult:s.adult===!0,genre_ids:s.genre_ids||[]}}const E=()=>I(s=>{const e=T(s),r={},n=b(e,"q");n!==void 0&&(r.q=n),e.page!==void 0&&(r.page=q(e,"page",1));const t=b(e,"type");t!==void 0&&(r.type=t);const a=b(e,"genre");a!==void 0&&(r.genre=a);const i=b(e,"year");i!==void 0&&(r.year=i);const o=b(e,"sortBy");return o!==void 0&&(r.sortBy=o),r}),R=()=>I(s=>{const e=T(s),r={},n=b(e,"mediaType");n!==void 0&&(r.mediaType=n);const t=b(e,"timeWindow");return t!==void 0&&(r.timeWindow=t),e.page!==void 0&&(r.page=q(e,"page",1)),r}),L=()=>I(s=>{const e=T(s);return{type:M(e,"type"),id:k(e,"id")}}),x=()=>I(s=>{const e=T(s);return{id:k(e,"id"),season:k(e,"season")}}),j=()=>I(s=>({imdbId:M(T(s),"imdbId")})),z=()=>I(s=>{const e=T(s),r={},n=b(e,"q");return n!==void 0&&(r.q=n),r}),W=()=>I(s=>{const e=T(s),r={type:M(e,"type"),tmdbId:k(e,"tmdbId")},n=b(e,"season");n!==void 0&&(r.season=n);const t=b(e,"episode");return t!==void 0&&(r.episode=t),r}),N=()=>I(s=>{const e=T(s),r={provider:M(e,"provider"),type:M(e,"type"),tmdbId:k(e,"tmdbId")};e.season!==void 0&&(r.season=q(e,"season",1)),e.episode!==void 0&&(r.episode=q(e,"episode",1));const n=b(e,"server");n!==void 0&&(r.server=n);const t=b(e,"title");t!==void 0&&(r.title=t);const a=b(e,"year");a!==void 0&&(r.year=a);const i=b(e,"imdbId");i!==void 0&&(r.imdbId=i);const o=b(e,"totalSeasons");return o!==void 0&&(r.totalSeasons=o),r}),O=C({search:h.input(E()).query(async({ctx:s,input:e})=>{const r=e.q||"",n=e.page||1,t=e.type||"multi",a=e.genre||"",i=e.year||"",o=e.sortBy||"popularity.desc",l=`tv-search-${r.toLowerCase()}-${n}-${t}-${a}-${i}-${o}`,d=s.apiCache.get(l);if(d)return d;const v=await w();if(!v)throw g("No TMDB API key available");try{let c;if(t==="tv"||t==="movie")if(r.trim()){const u=new URLSearchParams({api_key:v,query:encodeURIComponent(r.trim()),page:String(n),include_adult:"true"});c=`${f}/search/${t}?${u.toString()}`}else{const u=new URLSearchParams({api_key:v,page:String(n),sort_by:o});a&&u.set("with_genres",a),i&&(t==="tv"?u.set("first_air_date_year",i):u.set("primary_release_year",i)),c=`${f}/discover/${t}?${u.toString()}`}else if(r.trim()){const u=new URLSearchParams({api_key:v,query:encodeURIComponent(r),page:String(n),include_adult:"true"});c=`${f}/search/multi?${u.toString()}`}else{const u=new URLSearchParams({api_key:v,page:String(n),sort_by:o});a&&u.set("with_genres",a),i&&u.set("first_air_date_year",i),c=`${f}/discover/tv?${u.toString()}`}const y=await fetch(c,{headers:{"User-Agent":"Mozilla/5.0"}});if(!y.ok)throw g("TMDB search failed");const m=await _(y);let S;t==="tv"||t==="movie"?S=(m.results||[]).map(u=>P(u,t)):S=(m.results||[]).filter(u=>u.media_type!=="person").map(u=>P(u,u.media_type||"tv"));const p={results:S,total_results:m.total_results||0,total_pages:m.total_pages||0,page:m.page||n};return s.apiCache.set(l,p,1800),p}catch(c){throw c instanceof $?c:g(c.message)}}),trending:h.input(R()).query(async({ctx:s,input:e})=>{const r=e.mediaType||"all",n=e.timeWindow||"week",t=e.page||1,a=`tv-trending-${r}-${n}-${t}`,i=s.apiCache.get(a);if(i)return i;const o=await w();if(!o)throw g("No TMDB API key available");try{const l=await fetch(`${f}/trending/${r}/${n}?api_key=${o}&page=${t}`,{headers:{"User-Agent":"Mozilla/5.0"}});if(!l.ok)throw g("TMDB trending failed");const d=await _(l),c={results:(d.results||[]).filter(y=>y.media_type!=="person").map(y=>P(y,y.media_type||"tv")),total_results:d.total_results||0,total_pages:d.total_pages||0,page:d.page||t};return s.apiCache.set(a,c,3600),c}catch(l){throw l instanceof $?l:g(l.message)}}),genres:h.query(async({ctx:s})=>{const e="tv-genres-list",r=s.apiCache.get(e);if(r)return r;const n=await w();if(!n)throw g("No TMDB API key available");try{const[t,a]=await Promise.all([fetch(`${f}/genre/tv/list?api_key=${n}`,{headers:{"User-Agent":"Mozilla/5.0"}}),fetch(`${f}/genre/movie/list?api_key=${n}`,{headers:{"User-Agent":"Mozilla/5.0"}})]),i=t.ok?await _(t):{genres:[]},o=a.ok?await _(a):{genres:[]},l={tv:(i.genres||[]).map(d=>({id:d.id,name:d.name})),movie:(o.genres||[]).map(d=>({id:d.id,name:d.name}))};return s.apiCache.set(e,l,86400),l}catch(t){throw t instanceof $?t:g(t.message)}}),details:h.input(L()).query(async({input:s})=>{const e=s.type,r=s.id,n=await w();if(!n)throw g("No TMDB API key available");try{const t=await fetch(`${f}/${e}/${r}?api_key=${n}&append_to_response=external_ids`,{headers:{"User-Agent":"Mozilla/5.0"}});if(!t.ok)throw g("TMDB details failed");const a=await _(t),i={id:a.id,imdb_id:a.external_ids?.imdb_id||a.imdb_id||null,title:a.title||a.name,overview:a.overview,vote_average:a.vote_average,year:(a.release_date||a.first_air_date||"").split("-")[0],poster:a.poster_path?`${B}/w500${a.poster_path}`:"",backdrop:a.backdrop_path?`${B}/original${a.backdrop_path}`:"",adult:a.adult===!0};return e==="tv"&&(i.seasons=(a.seasons||[]).filter(o=>o.season_number>0).map(o=>({season_number:o.season_number,episode_count:o.episode_count})),i.number_of_seasons=a.number_of_seasons),i}catch(t){throw t instanceof $?t:g(t.message)}}),episodes:h.input(x()).query(async({input:s})=>{const e=s.id,r=s.season,n=await w();if(!n)throw g("No TMDB API key available");try{const t=await fetch(`${f}/tv/${e}/season/${r}?api_key=${n}`,{headers:{"User-Agent":"Mozilla/5.0"}});if(!t.ok)throw g("TMDB episodes failed");return{episodes:((await _(t)).episodes||[]).map(o=>({episode_number:o.episode_number,name:o.name,vote_average:o.vote_average,overview:o.overview,still_path:o.still_path}))}}catch(t){throw t instanceof $?t:g(t.message)}}),lookupImdb:h.input(j()).query(async({input:s})=>{const e=await w();if(!e)return{tmdbId:null,error:"No TMDB API key available"};try{const r=await fetch(`${f}/find/${s.imdbId}?api_key=${e}&external_source=imdb_id`,{headers:{"User-Agent":"Mozilla/5.0"}});if(!r.ok)return{tmdbId:null,error:`TMDB error ${r.status}`};const n=await _(r),t=n.movie_results?.[0],a=n.tv_results?.[0],i=t||a;return{tmdbId:i?.id||null,type:t?"movie":a?"tv":null,title:i?.title||i?.name||null,year:(i?.release_date||i?.first_air_date||"").substring(0,4)||null}}catch(r){return{tmdbId:null,error:r.message}}}),searchImdb:h.input(z()).query(async({ctx:s,input:e})=>{const r=e.q||"";if(!r)return[];const n=`tv-imdb-search-${r.toLowerCase()}`,t=s.apiCache.get(n);if(t)return t;try{const a=await fetch(`https://v3.sg.media-imdb.com/suggestion/x/${encodeURIComponent(r)}.json`,{headers:{"User-Agent":"Mozilla/5.0"}});if(!a.ok)throw g("IMDB search failed");const i=await _(a),o=new Set(["tvSeries","tvMiniSeries","movie"]),l=(i.d||[]).filter(d=>!!(d.id&&d.l)&&o.has(d.qid||"")).slice(0,10).map(d=>({id:d.id,title:d.l,year:d.y,type:d.qid,image:d.i?.imageUrl||""}));return s.apiCache.set(n,l,3600),l}catch(a){throw a instanceof $?a:g(a.message)}}),subtitles:h.input(W()).query(async({ctx:s,input:e})=>{const r=e.type,n=e.tmdbId,t=parseInt(n,10);if(!t)return{subtitles:[]};const a=r==="movie"?"movie":"tv",i=e.season||"1",o=e.episode||"1",l=a==="tv"?`tv-subs-${t}-${i}-${o}`:`tv-subs-${t}-movie`,d=s.apiCache.get(l);if(d)return d;try{const v=a==="tv"?`https://subtitles.vidy.st/search?id=${t}&season=${encodeURIComponent(i)}&episode=${encodeURIComponent(o)}`:`https://subtitles.vidy.st/search?id=${t}`,c=await fetch(v,{headers:{"User-Agent":"Mozilla/5.0"},signal:AbortSignal.timeout(1e4)});if(!c.ok)return{subtitles:[]};const y=await _(c),S={subtitles:(Array.isArray(y)?y:[]).filter(p=>typeof p.url=="string"&&p.url.startsWith("https://")).map(p=>({language:String(p.language||"en"),label:String(p.display||p.language||"Unknown"),url:String(p.url),trusted:p.isTrusted===!0,hi:p.isHearingImpaired===!0,downloads:Number(p.downloadCount)||0,media:String(p.media||p.release||"")})).sort((p,u)=>{const U=p.language.toLowerCase().startsWith("en")?0:1,A=u.language.toLowerCase().startsWith("en")?0:1;return U!==A?U-A:p.trusted!==u.trusted?p.trusted?-1:1:u.downloads-p.downloads}).slice(0,50)};return s.apiCache.set(l,S,3600),S}catch{return{subtitles:[]}}}),sources:h.input(N()).query(async({ctx:s,input:e})=>{const r=e.provider,n=e.type,t=e.tmdbId,a=n==="movie"?"movie":"tv",i=parseInt(t,10);if(!i)return{sources:[],audioTracks:[],error:"Bad tmdb id"};const o=s.getTvProvider(String(r).toLowerCase());if(!o||typeof o.getSources!="function")return{sources:[],audioTracks:[],error:`Unknown TV provider ${r}`};const l=e.season||1,d=e.episode||1,v=e.server,c=await D(a,i,{title:e.title||"",year:e.year||"",imdbId:e.imdbId||"",totalSeasons:e.totalSeasons||"1"}),y={tmdbId:i,type:a,season:l,episode:d,title:c.title,year:c.year,imdbId:c.imdbId,totalSeasons:parseInt(c.totalSeasons,10)||1};try{const m=await o.getSources(y,v);return!m||!Array.isArray(m.sources)||m.sources.length===0?{provider:o.name,server:m?.server||v,sources:[],audioTracks:[],valid:!1,error:"No sources found"}:{provider:o.name,server:m.server||v,servers:m.servers,embedUrl:m.embedUrl,sources:m.sources,audioTracks:m.audioTracks||[],subtitles:m.subtitles||[],referer:m.referer||"",valid:!0}}catch(m){return{provider:o.name,sources:[],audioTracks:[],valid:!1,error:m.message}}})});export{O as tvRouter};
@@ -1,3 +0,0 @@
1
- import{protectedProcedure as g,router as _}from"../index.js";import{WatchlistRepository as p}from"../../repositories/watchlist.repository.js";import{WatchedEpisodesRepository as N}from"../../repositories/watched-episodes.repository.js";import{ShowsMetaRepository as b}from"../../repositories/shows-meta.repository.js";import{NotificationsRepository as M}from"../../repositories/notifications.repository.js";import{QueueRepository as k}from"../../repositories/queue.repository.js";import{SettingsRepository as v}from"../../repositories/settings.repository.js";import{performWriteTransactionAsync as I}from"../../sync.js";import{getMigratedId as y}from"../../lib/migration.js";import{isAnilistRateLimited as $,searchAnilist as z,searchAnilistByTitle as H,getAnilistEpisodes as U,getShowMetaById as j}from"../../lib/anilist.js";import{kitsuSearchAnime as V}from"../../lib/kitsu.js";import{offlineDb as E}from"../../lib/offline-db.js";import{dbAll as A}from"../../utils/db-utils.js";import{defineSchema as B,idInput as Y,idsInput as K,optNum as W,optStr as h,reqObj as C,showIdInput as R,watchlistAddInput as J,watchlistBatchStatusInput as Q,watchlistStatusInput as X}from"../validation.js";const Z=()=>B(t=>{const e=C(t),s={...F(e)},n=h(e,"status");n!==void 0&&(s.status=n);const i=W(e,"page");i!==void 0&&(s.page=i);const r=W(e,"limit");r!==void 0&&(s.limit=r);const a=h(e,"showMature");return a!==void 0&&(s.showMature=a),s}),ee=()=>B(t=>{const e=C(t),s={...F(e)},n=W(e,"page");n!==void 0&&(s.page=n);const i=W(e,"limit");return i!==void 0&&(s.limit=i),s}),Ae=_({check:g.input(R()).query(async({ctx:t,input:e})=>{const s=await y(t.db,e.showId),n=await p.getById(t.db,s);return{inWatchlist:!!n,status:n?.status??null}}),add:g.input(J()).mutation(async({ctx:t,input:e})=>{const s=await y(t.db,e.id);if(await I(t.db,async n=>{await p.upsert(n,{id:s,name:e.name,thumbnail:e.thumbnail||"",status:e.status||"Watching",nativeName:e.nativeName||"",englishName:e.englishName||"",type:e.type||"TV"}),typeof e.isAdult=="boolean"&&await b.upsert(n,{id:s,isAdult:e.isAdult?1:0})}),e.name&&!/^\d+$/.test(s)){const n=t.db,i=e.name;(async()=>{if(!$()){const a=await H(i);if(a?.id){await b.upsert(n,{id:s,anilistId:a.id});return}}try{const a=await V({query:i,page:1,perPage:3});if(a.length>0){const l=Math.abs(a[0].id);await b.upsert(n,{id:s,anilistId:l})}}catch{}})().catch(()=>{})}return{success:!0}}),remove:g.input(Y()).mutation(async({ctx:t,input:e})=>{const s=await y(t.db,e.id);return await I(t.db,async n=>{await p.delete(n,s),await N.deleteByShow(n,s),await M.deleteByShow(n,s)}),{success:!0}}),removeMany:g.input(K()).mutation(async({ctx:t,input:e})=>{const s=await Promise.all(e.ids.map(n=>y(t.db,n)));return await I(t.db,async n=>{await p.deleteMany(n,s);for(const i of s)await N.deleteByShow(n,i),await M.deleteByShow(n,i)}),{success:!0,removed:s.length}}),setStatus:g.input(X()).mutation(async({ctx:t,input:e})=>{const s=await y(t.db,e.id);return await I(t.db,async n=>{await p.updateStatus(n,s,e.status)}),{success:!0}}),batchStatus:g.input(Q()).mutation(async({ctx:t,input:e})=>{const s=await Promise.all(e.ids.map(n=>y(t.db,n)));return await I(t.db,async n=>{await p.updateStatusMany(n,s,e.status)}),{success:!0,updated:s.length}}),list:g.input(Z()).query(async({ctx:t,input:e})=>{const s=t.db,n=Math.max(parseInt(String(e.page??1))||1,1),i=Math.min(Math.max(parseInt(String(e.limit??10))||10,1),100),r=e.showMature,a=(n-1)*i,l=q({query:e.query,type:e.type,season:e.season,year:e.year,genres:e.genres,excludeGenres:e.excludeGenres,sortBy:e.sortBy,titlePreference:e.titlePreference}),o=e.status,d=await p.getAll(s,o);let c=await O(d,l,s);if(r==="false"){const m=c.map(f=>f.id);if(m.length>0){const f=m.map(()=>"?").join(","),T=await A(s,`SELECT id FROM shows_meta WHERE id IN (${f}) AND isAdult = 1`,m),G=new Set(T.map(P=>P.id));c=c.filter(P=>!G.has(P.id))}}const u=c.slice(a,a+i);for(const m of u)if(!m.thumbnail||m.thumbnail.trim()==="")try{const f=await b.getById(s,m.id);f?.thumbnail&&f.thumbnail.trim()!==""&&(m.thumbnail=f.thumbnail)}catch{}await x(s,u);const w=u.map(m=>m.id);let L=new Set;if(w.length>0){const m=w.map(()=>"?").join(","),f=await A(s,`SELECT id FROM shows_meta WHERE id IN (${m}) AND isAdult = 1`,w);L=new Set(f.map(T=>T.id))}return{data:u.map(m=>({...m,_id:m.id,thumbnail:m.thumbnail||"",isAdult:L.has(m.id)})),total:c.length,page:n,limit:i}}),continueWatchingAll:g.input(ee()).query(async({ctx:t,input:e})=>{const s=t.db,n=Math.max(parseInt(String(e.page??1))||1,1),i=Math.min(Math.max(parseInt(String(e.limit??10))||10,1),100),r=(n-1)*i,a=q({query:e.query,type:e.type,season:e.season,year:e.year,genres:e.genres,excludeGenres:e.excludeGenres,sortBy:e.sortBy,titlePreference:e.titlePreference}),l=await O(await le(s),a,s);return{data:l.slice(r,r+i),total:l.length,page:n,limit:i}}),queueSuggested:g.input(R()).query(async({ctx:t,input:e})=>{const s=t.db,n=await y(s,e.showId),i=await N.getLatestResumeProgress(s,n);if(i)return{showId:n,episodeNumber:i.episodeNumber,resumeTime:i.currentTime||0};const[r,a]=await Promise.all([N.getByShow(s,n),D(n)]),l=new Set(r.map(u=>u.episodeNumber.toString())),o=r.filter(u=>u.duration>0&&u.currentTime>=u.duration*.8).map(u=>parseFloat(u.episodeNumber)).filter(u=>!Number.isNaN(u)),d=o.length>0?String(Math.max(...o)+1):void 0,c=(d&&a.includes(d)&&!l.has(d)?d:a.find(u=>!l.has(u)))||a[0]||"1";return{showId:n,episodeNumber:c,resumeTime:0}}),queueRemaining:g.input(R()).query(async({ctx:t,input:e})=>{const s=t.db,n=await y(s,e.showId),[i,r,a]=await Promise.all([N.getByShow(s,n),k.getByShow(s,n),D(n)]),l=new Set(i.map(c=>c.episodeNumber.toString())),o=new Set(r.map(c=>c.episodeNumber.toString())),d=a.filter(c=>!l.has(c)&&!o.has(c));return{showId:n,episodes:d}})});function F(t){const e={},s=h(t,"query");s!==void 0&&(e.query=s);const n=h(t,"type");n!==void 0&&(e.type=n);const i=h(t,"season");i!==void 0&&(e.season=i);const r=h(t,"year");r!==void 0&&(e.year=r);const a=h(t,"genres");a!==void 0&&(e.genres=a);const l=h(t,"excludeGenres");l!==void 0&&(e.excludeGenres=l);const o=h(t,"sortBy");o!==void 0&&(e.sortBy=o);const d=h(t,"titlePreference");return d!==void 0&&(e.titlePreference=d),e}function S(t){if(typeof t!="string")return;const e=t.trim();return e&&e!=="ALL"?e:void 0}function q(t){return{query:S(t.query),type:S(t.type),season:S(t.season),year:S(t.year),genres:S(t.genres),excludeGenres:S(t.excludeGenres),sortBy:S(t.sortBy),titlePreference:["name","nativeName","englishName"].includes(String(t.titlePreference))?t.titlePreference:"name"}}function te(t,e){if(e.query){const s=new Set(e.query.toLowerCase().split(/\s+/).filter(a=>a.length>=2));if((t.englishName||t.name||t.nativeName||"").toLowerCase().split(/\s+/).filter(a=>s.has(a)).length<s.size)return!1}return!0}function ne(t,e){const s=n=>(e.titlePreference?n[e.titlePreference]:void 0)||n.name||"";return e.sortBy==="name_asc"?[...t].sort((n,i)=>s(n).localeCompare(s(i))):e.sortBy==="name_desc"?[...t].sort((n,i)=>s(i).localeCompare(s(n))):t}async function se(t,e){const s=new Map;if(!e||e==="ALL")return s;const n=parseInt(e);if(Number.isNaN(n))return s;const i=50;let r=1;for(;;){const a={seasonYear:n,page:r,perPage:i};t&&t!=="ALL"&&(a.season=t.toUpperCase());const l=await z(a);for(const o of l)o.anilistId&&s.set(o.anilistId,{title:o.names||{}});if(l.length<i||(r++,r>10))break}return s}function ie(t,e){if(e.size===0||/^\d+$/.test(t.id)&&e.has(parseInt(t.id)))return!0;const s=(t.englishName||t.name||t.nativeName||"").toLowerCase();if(!s)return!1;const n=new Set(s.split(/\s+/).filter(i=>i.length>=2));if(n.size===0)return!1;for(const[,i]of e){const r=[i.title?.romaji,i.title?.english,i.title?.native].filter(Boolean);for(const a of r){const l=new Set(a.toLowerCase().split(/\s+/).filter(c=>c.length>=2)),o=[...n].filter(c=>l.has(c)).length,d=Math.min(n.size,l.size);if(d>=2&&o/d>=.7)return!0}}return!1}async function O(t,e,s){let n=t.filter(i=>te(i,e));if(e.type==="ADULT"&&s&&n.length>0){const i=n.map(o=>o.id),r=i.map(()=>"?").join(","),a=await A(s,`SELECT id FROM shows_meta WHERE id IN (${r}) AND isAdult = 1`,i),l=new Set(a.map(o=>o.id));n=n.filter(o=>l.has(o.id))}else e.type&&e.type!=="ALL"&&e.type!=="ADULT"&&(n=n.filter(i=>i.type===e.type));if((e.genres||e.excludeGenres)&&s){const i=n.map(c=>c.id),r=i.map(()=>"?").join(","),a=await A(s,`SELECT id, genres FROM shows_meta WHERE id IN (${r})`,i),l=new Map(a.map(c=>[c.id,c.genres?JSON.parse(c.genres):[]])),o=e.genres?.split(",").map(c=>c.trim()).filter(Boolean)||[],d=e.excludeGenres?.split(",").map(c=>c.trim()).filter(Boolean)||[];n=n.filter(c=>{const u=l.get(c.id)||[];return!(o.length&&!o.every(w=>u.includes(w))||d.length&&d.some(w=>u.includes(w)))})}if((e.season&&e.season!=="ALL"||e.year&&e.year!=="ALL")&&n.length>0){const i=await se(e.season,e.year);i.size>0&&(n=n.filter(r=>ie(r,i)))}return ne(n,e)}async function x(t,e){const s=e.filter(i=>!i.thumbnail||i.thumbnail.trim()==="");if(s.length===0)return;const n=s.slice(0,10);await Promise.allSettled(n.map(async i=>{try{const r=await j(i.id),a=r?.thumbnail?.trim();if(!a)return;i.thumbnail=a;const l=i;l._id!==void 0&&(l._id=i.id);try{await b.upsert(t,{id:i.id,thumbnail:a,popularityScore:r?.score??(r?.averageScore!=null?r.averageScore/10:void 0)})}catch{}try{await p.updateThumbnail(t,i.id,a)}catch{}}catch{}})),await ae(t)}async function ae(t){try{t.run(`UPDATE shows_meta SET name = (SELECT w.name FROM watchlist w WHERE w.id = shows_meta.id)
2
- WHERE (name IS NULL OR TRIM(name) = '')
3
- AND EXISTS (SELECT 1 FROM watchlist w WHERE w.id = shows_meta.id AND w.name IS NOT NULL AND TRIM(w.name) != '')`)}catch{}const[e,s]=await Promise.all([p.getMissingThumbnails(t),b.getShowIdsMissingMeta(t)]),n=[...new Set([...e.map(i=>i.id),...s.map(i=>i.id)])].slice(0,500);if(n.length!==0)for(const i of n){const r=re(i);r&&await oe(t,i,r)}}function re(t){if(/^\d+$/.test(t))return E.getByAnilistId(Number(t))?.thumbnail?.trim()||null;const e=/^mal-(\d+)$/i.exec(t)?.[1];return e&&E.getByMalId(Number(e))?.thumbnail?.trim()||null}async function oe(t,e,s){try{await b.upsert(t,{id:e,thumbnail:s})}catch{}try{await p.updateThumbnail(t,e,s)}catch{}}async function le(t,e){const s=await N.getContinueWatching(t,e),n=await v.getByKey(t,"ignoreAdultContent"),i=n?n.value!=="false":!0,r=await v.getByKey(t,"cwWatchlistOnly"),a=r?r.value==="true"||r.value==="1":!1,l=s.filter(o=>!(i&&o.isAdult||a&&o.watchlistStatus!=="Watching")).map(o=>({...o,episodeCount:o.episodeCount,type:o.type||o.smType,thumbnail:o.thumbnail??""}));return await x(t,l),l}async function D(t){const e=await U(t);return Array.isArray(e)&&e.length?[...e].sort((n,i)=>parseFloat(n)-parseFloat(i)):[]}export{Ae as watchlistRouter};
@@ -1 +0,0 @@
1
- import{TRPCError as S}from"@trpc/server";function i(t){return{"~standard":{version:1,vendor:"dango",types:void 0,validate(e){try{return{value:t(e)}}catch(n){return{issues:[{message:n instanceof Error?n.message:"Invalid input"}]}}}}}}function v(t){if(typeof t!="object"||t===null)throw new Error("Expected an object");const e=t.key;if(typeof e!="string"||e.length===0)throw new Error("key must be a string");return e}function o(t,e){const n=t[e];if(typeof n!="string"||n.length===0)throw new Error(`${e} must be a string`);return n}function s(t,e){const n=t[e];if(n!=null)return String(n)}function a(t,e){const n=t[e];if(n==null||n==="")return;const r=Number(n);return Number.isFinite(r)?r:void 0}function k(t,e){const n=t[e];if(typeof n=="boolean")return n}function Q(t){return new S({code:"BAD_REQUEST",message:t})}function T(t){return new S({code:"INTERNAL_SERVER_ERROR",message:t})}function P(t,e){const n=t[e];if(typeof n!="string"&&typeof n!="number"||String(n).length===0)throw new Error(`${e} must be a string`);return String(n)}function j(t,e,n){const r=t[e];if(r==null||r==="")return n;const d=Math.floor(Number(r));return Number.isFinite(d)?d:n}function q(t,e){const n=t[e];return typeof n=="string"?n:null}function M(t){const e={},n=a(t,"page");n!==void 0&&(e.page=n);const r=a(t,"limit");return r!==void 0&&(e.limit=r),e}function f(t,e){const n=t[e];if(typeof n!="string"&&typeof n!="number"||String(n).length===0)throw new Error(`${e} must be a string`);return String(n)}function l(t,e,n){const r=t[e];if(!Array.isArray(r)||n&&r.length===0)throw new Error(`${e} must be ${n?"a non-empty ":"an "}array`);return r.map(d=>String(d))}function u(t){if(typeof t!="object"||t===null)throw new Error("Expected an object");return t}const O=()=>i(t=>({key:v(t)})),B=()=>i(t=>({id:o(u(t),"id")})),W=()=>i(t=>({showId:o(u(t),"showId")})),C=()=>i(t=>{const e=u(t);return{showId:o(e,"showId"),episodeNumber:f(e,"episodeNumber")}}),U=()=>i(t=>({ids:[...new Set(l(u(t),"ids",!0))]})),D=()=>i(t=>{const e=u(t),n={id:o(e,"id"),name:o(e,"name")},r=s(e,"status");r!==void 0&&(n.status=r);const d=s(e,"thumbnail");d!==void 0&&(n.thumbnail=d);const p=s(e,"nativeName");p!==void 0&&(n.nativeName=p);const g=s(e,"englishName");g!==void 0&&(n.englishName=g);const c=s(e,"type");return c!==void 0&&(n.type=c),typeof e.isAdult=="boolean"&&(n.isAdult=e.isAdult),n}),$=()=>i(t=>{const e=u(t);return{id:o(e,"id"),status:o(e,"status")}}),_=()=>i(t=>{const e=u(t);return{ids:[...new Set(l(e,"ids",!0))],status:o(e,"status")}});function A(t){return{showName:s(t,"showName"),showThumbnail:s(t,"showThumbnail"),nativeName:s(t,"nativeName"),englishName:s(t,"englishName"),type:s(t,"type")}}const F=()=>i(t=>{const e=u(t);return{...A(e),showId:o(e,"showId"),episodeNumber:f(e,"episodeNumber")}}),L=()=>i(t=>{const e=u(t);return{...A(e),showId:o(e,"showId"),episodeNumbers:l(e,"episodeNumbers",!0)}}),K=()=>i(t=>{const e=u(t);return{showId:o(e,"showId"),episodeNumber:f(e,"episodeNumber")}}),V=()=>i(t=>{const e=u(t),n={showId:o(e,"showId")};return e.episodeNumbers!==void 0&&(n.episodeNumbers=l(e,"episodeNumbers",!1)),n}),z=()=>i(t=>{if(!Array.isArray(t))throw new Error("items must be an array");return t.map(e=>{const n=u(e),r={};return typeof n.id=="number"&&(r.id=n.id),n.showId!==void 0&&(r.showId=String(n.showId)),n.episodeNumber!==void 0&&(r.episodeNumber=String(n.episodeNumber)),r})}),G=()=>i(t=>{if(typeof t!="object"||t===null)throw new Error("Expected an object");return{key:v(t),value:t.value}}),H=()=>i(t=>{const e=u(t),n={showId:o(e,"showId"),episodeNumber:f(e,"episodeNumber")},r=a(e,"currentTime");r!==void 0&&(n.currentTime=r);const d=a(e,"duration");d!==void 0&&(n.duration=d);const p=s(e,"showName");p!==void 0&&(n.showName=p);const g=s(e,"showThumbnail");g!==void 0&&(n.showThumbnail=g);const c=s(e,"nativeName");c!==void 0&&(n.nativeName=c);const h=s(e,"englishName");h!==void 0&&(n.englishName=h);const m=e.genres;(typeof m=="string"||Array.isArray(m))&&(n.genres=(Array.isArray(m)?m:[m]).map(String));const I=a(e,"popularityScore");I!==void 0&&(n.popularityScore=I);const w=s(e,"type");w!==void 0&&(n.type=w);const y=s(e,"status");y!==void 0&&(n.status=y);const b=a(e,"episodeCount");b!==void 0&&(n.episodeCount=b);const N=k(e,"isPlaying");N!==void 0&&(n.isPlaying=N);const x=s(e,"sessionId");x!==void 0&&(n.sessionId=x);const R=k(e,"isAdult");return R!==void 0&&(n.isAdult=R),n}),J=()=>i(t=>{const e=u(t);return{showId:o(e,"showId"),episodeNumber:f(e,"episodeNumber")}});export{Q as badRequest,i as defineSchema,C as episodeProgressInput,T as failed,B as idInput,U as idsInput,O as keyInput,J as notificationDismissInput,k as optBool,a as optNum,s as optStr,H as progressUpdateInput,F as queueAddInput,L as queueBatchInput,K as queueRemoveInput,V as queueRemoveManyInput,z as queueReorderInput,j as readCount,M as readPageLimit,P as reqId,u as reqObj,o as reqStr,l as reqStrArray,G as setSettingInput,W as showIdInput,q as strOrNull,D as watchlistAddInput,_ as watchlistBatchStatusInput,$ as watchlistStatusInput};
@@ -1 +0,0 @@
1
- import{LRUCache as s}from"lru-cache";class r{cache;constructor(e={}){this.cache=new s({max:e.maxKeys??5e3,ttl:(e.ttlSeconds??3600)*1e3})}get(e){return this.cache.get(e)}set(e,n,t){const c=n;t===void 0?this.cache.set(e,c):this.cache.set(e,c,{ttl:t*1e3})}delete(e){this.cache.delete(e)}}export{r as AppCache};
@@ -1 +0,0 @@
1
- function r(n){if(!n)return"";let e=String(n).trim();return e=e.replace(/^cf_clearance/i,""),e=e.replace(/^[:=]\s*/,""),e=e.replace(/["']/g,"").trim(),e}function i(n){const e=r(n);return e?`cf_clearance=${e}`:""}export{i as buildCfClearanceCookie,r as sanitizeCfClearance};
@@ -1,5 +0,0 @@
1
- const e=(r,n,a=[])=>r.all(n,a),u=(r,n,a=[])=>r.get(n,a),b=(r,n,a=[])=>{r.run(n,a)};async function d(r,n,a,t,s){return(await e(r,`SELECT DISTINCT p."${t}" as id
2
- FROM "${n}" p
3
- LEFT JOIN "${a}" l ON l.id = p."${t}"
4
- WHERE ${s}
5
- AND l.id IS NULL`)).map(i=>i.id)}export{d as adultNonListIds,e as dbAll,u as dbGet,b as dbRun};
@@ -1,5 +0,0 @@
1
- import t from"fs";import{CONFIG as u}from"../config.js";const f=`${u.ENV_PATH}.lock`;async function w(){let e,a=0;for(;!e;)try{e=await t.promises.open(f,"wx")}catch{if(++a>=100)throw t.promises.unlink(f).catch(()=>{}),new Error("Failed to acquire lock on .env file - another process may be holding it");await new Promise(i=>setTimeout(i,100))}return e}function k(e){e.close().catch(()=>{}),t.promises.unlink(f).catch(()=>{})}async function $(e){const a=await w();try{const i=u.ENV_PATH;let p="";t.existsSync(i)&&(p=t.readFileSync(i,"utf8"));const n=[...p.split(`
2
- `)];Object.entries(e).forEach(([r,c])=>{const s=r.trim();if(!/^[A-Z0-9_]+$/i.test(s))throw new Error(`Invalid environment variable key: ${s}`);const l=String(c??"").replace(/[\r\n]/g,"");let m=!1;for(let o=0;o<n.length;o++){const h=n[o];if(h&&h.startsWith(`${s}=`)){if(l===""){n.splice(o,1);continue}else n[o]=`${s}=${l}`;m=!0;break}}!m&&l!==""&&n.push(`${s}=${l}`)});const d=n.join(`
3
- `).replace(/\n{2,}/g,`
4
- `).trim()+`
5
- `;t.writeFileSync(i,d),Object.entries(e).forEach(([r,c])=>{c===""?delete process.env[r]:process.env[r]=c})}finally{k(a)}}export{$ as updateEnvFile};
@@ -1 +0,0 @@
1
- import{brotliDecompressSync as l,gunzipSync as m,inflateSync as p}from"node:zlib";class w extends Error{status;constructor(r,e){super(e||`Upstream request failed with status ${r}`),this.name="HttpError",this.status=r}}function b(t){return t.length>2&&t[0]===31&&t[1]===139?m(t):t}async function x(t){const r=Buffer.from(await t.arrayBuffer()),e=b(r).toString("utf8");try{return JSON.parse(e)}catch(n){const s=[()=>l(r),()=>p(r)];for(const i of s)try{return JSON.parse(i().toString("utf8"))}catch{}throw n}}function A(t,r){t.addEventListener("abort",()=>r.abort(),{once:!0})}function S(t,r){for(const e of["content-length","content-range","accept-ranges"]){const n=r(e),s=Array.isArray(n)?n[0]:n;s&&t.set(e,s)}t.set("Access-Control-Allow-Origin","*")}function g(t){if(!t||typeof t!="object")return!1;const r=t.name;return r==="AbortError"||r==="TimeoutError"}const y=t=>new Promise(r=>setTimeout(r,t));function h(t){return t===408||t===429||t>=500}async function E(t,r={},e={}){const{retries:n=0,baseDelayMs:s=100,timeoutMs:i=3e4,signal:c}=e;let a=0;for(;;){const u=AbortSignal.timeout(i),f=c?AbortSignal.any([c,u]):u;try{const o=await fetch(t,{...r,signal:f});if(!h(o.status)||a>=n)return o;try{await o.body?.cancel()}catch{}}catch(o){if(c?.aborted||a>=n||!g(o)&&!(o instanceof TypeError))throw o}a+=1,await y(Math.min(s*2**(a-1),3e3))}}export{w as HttpError,b as decodeMaybeGzip,E as fetchWithRetry,g as isAbortError,A as linkAbort,x as parseJsonBody,S as setProxyHeaders};
@@ -1 +0,0 @@
1
- import{AsyncLocalStorage as t}from"node:async_hooks";const r=new t;export{r as requestContext};
@@ -1 +0,0 @@
1
- import{CONFIG as l}from"../config.js";function d(s){if(typeof s!="string"||!s.trim())return{safe:!1,error:"URL required"};let o;try{o=new URL(s.trim())}catch{return{safe:!1,error:"Invalid URL format"}}if(o.protocol!=="http:"&&o.protocol!=="https:")return{safe:!1,error:"Only http and https protocols are supported"};const r=o.hostname.toLowerCase();if(r==="localhost"||r==="127.0.0.1"||r==="::1"||r==="0.0.0.0"||r.endsWith(".localhost")||r.endsWith(".local")||r.endsWith(".internal"))return{safe:!1,error:"Access to loopback/local addresses is forbidden"};const e=r.match(/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/);if(e){const t=Number(e[1]),a=Number(e[2]),n=Number(e[3]),i=Number(e[4]);if([t,a,n,i].some(f=>f<0||f>255))return{safe:!1,error:"Invalid IP address format"};if(t===0||t===10||t===127||t===172&&a>=16&&a<=31||t===192&&a===168||t===169&&a===254)return{safe:!1,error:"Access to private or link-local IP addresses is forbidden"}}if(r.startsWith("[")||r.includes(":")){const t=r.replace(/^\[|\]$/g,"").toLowerCase();if(t==="::1"||t==="::"||t.startsWith("fe80:")||t.startsWith("fc00:")||t.startsWith("fd00:")||t.startsWith("::ffff:127.")||t.startsWith("::ffff:192.168.")||t.startsWith("::ffff:10.")||t.startsWith("::ffff:172.")||t.startsWith("::ffff:169.254."))return{safe:!1,error:"Access to private or link-local IPv6 addresses is forbidden"}}return{safe:!0,url:o}}function u(s){if(!s)return!0;try{const r=new URL(s).hostname.toLowerCase(),e=r==="localhost"||r==="127.0.0.1"||r==="::1";return l.HOST==="0.0.0.0"?!0:e}catch{return!1}}export{u as isAllowedOrigin,d as isSafeExternalUrl};