@serifpersia/dango 3.6.8 → 3.7.0
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.
- package/README.md +2 -2
- package/client/dist/assets/{AnimeCard-DraA_TF9.js → AnimeCard-VxBIpH-H.js} +1 -1
- package/client/dist/assets/{AnimeInfo-BJ7pzs-P.js → AnimeInfo-D_Yk-Yyk.js} +1 -1
- package/client/dist/assets/{AnimeInfoPage-B_esUr5C.js → AnimeInfoPage-DprI9g3s.js} +2 -2
- package/client/dist/assets/{Asmr-Ckuh2xfW.js → Asmr-CwgL7nn4.js} +2 -2
- package/client/dist/assets/{AsmrResetProgressModal-BS6UFJN4.js → AsmrResetProgressModal-Cw_1gqgF.js} +1 -1
- package/client/dist/assets/{AvSyncCalibrator-DwHCd9yB.js → AvSyncCalibrator-Cf_hnFZR.js} +1 -1
- package/client/dist/assets/{Home-DuM1EuvN.js → Home-BaR_V71s.js} +1 -1
- package/client/dist/assets/{Insights-D3Pd9c9V.js → Insights-CUeDNk4y.js} +1 -1
- package/client/dist/assets/{LibraryListPage-Dm2yVn0-.js → LibraryListPage-AoI14-dx.js} +1 -1
- package/client/dist/assets/ListeningList-BZexqK7W.js +1 -0
- package/client/dist/assets/{Manga-JwprMKBk.js → Manga-CPOuaQjZ.js} +1 -1
- package/client/dist/assets/{MangaInfoPage-DaeyJl2p.js → MangaInfoPage-Bo-FOngx.js} +1 -1
- package/client/dist/assets/{MangaReadPage-COg0fHI9.js → MangaReadPage-DuGPnGRF.js} +1 -1
- package/client/dist/assets/{MangaResetProgressModal-kguXv0_M.js → MangaResetProgressModal-CUvP02Ve.js} +1 -1
- package/client/dist/assets/{Mature-IHDaasrs.js → Mature-B1ZOlHvr.js} +1 -1
- package/client/dist/assets/{MediaCard-CpwsrXo5.js → MediaCard-C1kVq8jh.js} +1 -1
- package/client/dist/assets/{OptionListMenu-11CcAsy8.js → OptionListMenu-vUGV_jq6.js} +1 -1
- package/client/dist/assets/{Player-DQoEQkHL.js → Player-xUmvl3aX.js} +2 -2
- package/client/dist/assets/{PlayerSettings-D4Ud4kv4.js → PlayerSettings-B63XtOiv.js} +1 -1
- package/client/dist/assets/{QueueOptionsButton-B8PlPKjC.js → QueueOptionsButton-BGz5pDT3.js} +1 -1
- package/client/dist/assets/{QueueRail-B4Dok7U3.js → QueueRail-CQzlXS9D.js} +1 -1
- package/client/dist/assets/{ReadingList-BCsxIz57.js → ReadingList-BVU8jeNE.js} +1 -1
- package/client/dist/assets/{ResetProgressModal-BXHxSfy-.js → ResetProgressModal-MfdZU4Jw.js} +1 -1
- package/client/dist/assets/{Search-PK1ds07O.js → Search-Dl_zHidX.js} +1 -1
- package/client/dist/assets/{Settings-Bxk_x55l.js → Settings-OXxsNnw3.js} +1 -1
- package/client/dist/assets/{SkeletonGrid-CpJCZSRN.js → SkeletonGrid-BaEViX2G.js} +1 -1
- package/client/dist/assets/{Trackers-RieCh_84.js → Trackers-CF2lf0FQ.js} +1 -1
- package/client/dist/assets/{Tv-DLytPJUj.js → Tv-C3lwYGvC.js} +1 -1
- package/client/dist/assets/{TvInfo-C8djKebR.js → TvInfo-CgduZDby.js} +1 -1
- package/client/dist/assets/{TvPopup-40JGGJDk.js → TvPopup-Cwcs0iMP.js} +1 -1
- package/client/dist/assets/{TvSearch-A3hbospf.js → TvSearch-BCYb067l.js} +1 -1
- package/client/dist/assets/{TvWatchlist-DuXjqmZM.js → TvWatchlist-Bk_L18MC.js} +1 -1
- package/client/dist/assets/{Watchlist-B9wGBh0u.js → Watchlist-B0bqJqc4.js} +1 -1
- package/client/dist/assets/{hls-CZ5X3Ya-.js → hls-CBz3B7bO.js} +1 -1
- package/client/dist/assets/{index-dRhc9-ol.js → index-DxRnpRhu.js} +19 -19
- package/client/dist/assets/{useAnimeInfoData-ZzDtLfvD.js → useAnimeInfoData-VGk7ptlM.js} +1 -1
- package/client/dist/assets/{useAsmr-CKOdHgol.js → useAsmr-CxGWhApO.js} +1 -1
- package/client/dist/assets/useAsmrPopup-HC0kPwIY.js +1 -0
- package/client/dist/assets/{useMangaLibrary-CKATe31P.js → useMangaLibrary-8FuAAh_p.js} +1 -1
- package/client/dist/assets/{useMangaPopup-C75QuYgY.js → useMangaPopup-0vWil7K4.js} +1 -1
- package/client/dist/assets/{useProviders-CMtvZbEi.js → useProviders-DHClk0cR.js} +1 -1
- package/client/dist/assets/{useTv-DUX4zYMr.js → useTv-SJHkxQtj.js} +1 -1
- package/client/dist/assets/{useTvLibrary-By4Gt-R_.js → useTvLibrary-CnPxLtnZ.js} +1 -1
- package/client/dist/index.html +1 -1
- package/package.json +3 -19
- package/server/dist/server.js +1089 -1
- package/server/package.json +9 -13
- package/client/dist/assets/Asmr.module-CdWlioTY.js +0 -1
- package/client/dist/assets/ListeningList-D-zt6EXZ.js +0 -1
- package/client/dist/assets/Music-BGH_0voi.css +0 -1
- package/client/dist/assets/Music-Dks4tPeK.js +0 -1
- package/client/dist/assets/Radio-BHK6LSk4.js +0 -1
- package/client/dist/assets/Radio-CCHNFN7R.css +0 -1
- package/client/dist/assets/Radio.module-DGSZLpKM.js +0 -1
- package/client/dist/assets/useAsmrPopup-SFHPhpcl.js +0 -1
- package/server/dist/app-auth.js +0 -1
- package/server/dist/app-hono.js +0 -48
- package/server/dist/db/drizzle.js +0 -1
- package/server/dist/db/schema-anime.js +0 -1
- package/server/dist/db/schema-asmr.js +0 -1
- package/server/dist/db/schema-manga.js +0 -1
- package/server/dist/db/schema-tv.js +0 -1
- package/server/dist/db.js +0 -1
- package/server/dist/discord-gateway.js +0 -1
- package/server/dist/discord-rpc.js +0 -1
- package/server/dist/github-sync.js +0 -1
- package/server/dist/google.js +0 -1
- package/server/dist/hono/asmr.js +0 -1
- package/server/dist/hono/auth.js +0 -26
- package/server/dist/hono/music.js +0 -1
- package/server/dist/hono/proxy.js +0 -80
- package/server/dist/hono/tv.js +0 -5
- package/server/dist/hono/watchlist.js +0 -5
- package/server/dist/lib/anilist.js +0 -151
- package/server/dist/lib/anischedule.js +0 -1
- package/server/dist/lib/insights.js +0 -1
- package/server/dist/lib/ipc.js +0 -1
- package/server/dist/lib/kitsu.js +0 -1
- package/server/dist/lib/mal-import.js +0 -1
- package/server/dist/lib/mal.js +0 -1
- package/server/dist/lib/migration.js +0 -1
- package/server/dist/lib/offline-db.js +0 -15
- package/server/dist/lib/potoken.js +0 -1
- package/server/dist/lib/recommendations.service.js +0 -1
- package/server/dist/lib/temp-ids.js +0 -1
- package/server/dist/lib/tmdb.js +0 -1
- package/server/dist/lib/tracker/anilist-tracker.js +0 -71
- package/server/dist/lib/tracker/manga-sync.service.js +0 -1
- package/server/dist/lib/tracker/sync-state.js +0 -1
- package/server/dist/lib/tracker/sync.service.js +0 -1
- package/server/dist/lib/translate.js +0 -3
- package/server/dist/lib/ump.js +0 -1
- package/server/dist/lib/ytmusic.js +0 -1
- package/server/dist/logger.js +0 -1
- package/server/dist/providers/manga/manga.types.js +0 -0
- package/server/dist/providers/provider.interface.js +0 -0
- package/server/dist/providers/remote-loader.js +0 -2
- package/server/dist/providers/remote-types.js +0 -1
- package/server/dist/providers/title-matching.js +0 -1
- package/server/dist/providers/tv.types.js +0 -0
- package/server/dist/rclone.js +0 -2
- package/server/dist/repositories/asmr.repository.js +0 -42
- package/server/dist/repositories/insights.repository.js +0 -92
- package/server/dist/repositories/library.repository.js +0 -3
- package/server/dist/repositories/mal-cache.repository.js +0 -1
- package/server/dist/repositories/manga.repository.js +0 -51
- package/server/dist/repositories/notifications.repository.js +0 -14
- package/server/dist/repositories/queue.repository.js +0 -19
- package/server/dist/repositories/settings.repository.js +0 -3
- package/server/dist/repositories/shows-meta.repository.js +0 -5
- package/server/dist/repositories/temp-show-ids.repository.js +0 -3
- package/server/dist/repositories/tv.repository.js +0 -50
- package/server/dist/repositories/watched-episodes.repository.js +0 -33
- package/server/dist/repositories/watchlist.repository.js +0 -9
- package/server/dist/scripts/backfill-episode-duration.js +0 -1
- package/server/dist/sync-payload.js +0 -1
- package/server/dist/sync.js +0 -13
- package/server/dist/trpc/context.js +0 -1
- package/server/dist/trpc/errors.js +0 -1
- package/server/dist/trpc/index.js +0 -1
- package/server/dist/trpc/router.js +0 -1
- package/server/dist/trpc/routers/asmr-library.js +0 -1
- package/server/dist/trpc/routers/asmr-progress.js +0 -1
- package/server/dist/trpc/routers/asmr.js +0 -1
- package/server/dist/trpc/routers/auth.js +0 -1
- package/server/dist/trpc/routers/continue-watching.js +0 -25
- package/server/dist/trpc/routers/data.js +0 -1
- package/server/dist/trpc/routers/discord.js +0 -1
- package/server/dist/trpc/routers/discovery.js +0 -1
- package/server/dist/trpc/routers/insights.js +0 -1
- package/server/dist/trpc/routers/manga-library.js +0 -1
- package/server/dist/trpc/routers/manga-progress.js +0 -1
- package/server/dist/trpc/routers/manga.js +0 -1
- package/server/dist/trpc/routers/music.js +0 -1
- package/server/dist/trpc/routers/notifications.js +0 -1
- package/server/dist/trpc/routers/progress.js +0 -1
- package/server/dist/trpc/routers/queue.js +0 -1
- package/server/dist/trpc/routers/radio.js +0 -1
- package/server/dist/trpc/routers/settings.js +0 -1
- package/server/dist/trpc/routers/tracker.js +0 -1
- package/server/dist/trpc/routers/tv-library.js +0 -1
- package/server/dist/trpc/routers/tv-progress.js +0 -1
- package/server/dist/trpc/routers/tv.js +0 -1
- package/server/dist/trpc/routers/watchlist.js +0 -3
- package/server/dist/trpc/validation.js +0 -1
- package/server/dist/utils/cache.utils.js +0 -1
- package/server/dist/utils/cookie.utils.js +0 -1
- package/server/dist/utils/db-utils.js +0 -5
- package/server/dist/utils/env.utils.js +0 -5
- package/server/dist/utils/http.utils.js +0 -1
- package/server/dist/utils/request-context.js +0 -1
- package/server/dist/utils/security.utils.js +0 -1
- /package/client/dist/assets/{Asmr-D1KuQwQy.css → useAsmrPopup-D1KuQwQy.css} +0 -0
package/server/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dango-server",
|
|
3
3
|
"private": true,
|
|
4
|
-
"version": "3.
|
|
4
|
+
"version": "3.7.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
7
7
|
"node": ">=22.12.0"
|
|
@@ -14,30 +14,26 @@
|
|
|
14
14
|
"lint": "eslint ."
|
|
15
15
|
},
|
|
16
16
|
"dependencies": {
|
|
17
|
+
"got-scraping": "^3.2.9"
|
|
18
|
+
},
|
|
19
|
+
"devDependencies": {
|
|
17
20
|
"@hono/node-server": "^2.1.1",
|
|
18
21
|
"@octokit/auth-oauth-device": "^8.0.3",
|
|
19
22
|
"@octokit/rest": "^22.0.1",
|
|
20
23
|
"@trpc/server": "11.19.0",
|
|
24
|
+
"@types/node": "^22.5.0",
|
|
25
|
+
"@types/ws": "^8.5.10",
|
|
21
26
|
"@xhayper/discord-rpc": "^1.3.4",
|
|
22
|
-
"bgutils-js": "^4.0.3",
|
|
23
27
|
"cheerio": "^1.2.0",
|
|
24
28
|
"dotenv": "^17.4.2",
|
|
25
29
|
"drizzle-orm": "1.0.0-rc.4",
|
|
30
|
+
"esbuild": "0.28.2",
|
|
26
31
|
"fast-xml-parser": "^5.11.1",
|
|
27
|
-
"got-scraping": "^3.2.9",
|
|
28
32
|
"hono": "^4.13.9",
|
|
29
|
-
"jsdom": "^30.1.0",
|
|
30
33
|
"lru-cache": "^11.5.2",
|
|
31
34
|
"pino": "^10.3.1",
|
|
32
|
-
"ws": "^8.18.0",
|
|
33
|
-
"youtubei.js": "^18.1.0"
|
|
34
|
-
},
|
|
35
|
-
"devDependencies": {
|
|
36
|
-
"@types/jsdom": "^30.0.0",
|
|
37
|
-
"@types/node": "^22.5.0",
|
|
38
|
-
"@types/ws": "^8.5.10",
|
|
39
|
-
"esbuild": "0.28.2",
|
|
40
35
|
"pino-pretty": "^13.1.1",
|
|
41
|
-
"tsx": "^4.23.13"
|
|
36
|
+
"tsx": "^4.23.13",
|
|
37
|
+
"ws": "^8.18.0"
|
|
42
38
|
}
|
|
43
39
|
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
const o="_sortSelect_16zfo_1",e="_grid_16zfo_11",t="_fetching_16zfo_18",a="_thumbPlaceholder_16zfo_57",_="_asmrCardWrap_16zfo_93",n="_bookmarkBtn_16zfo_98",r="_bookmarked_16zfo_122",l="_popupCover_16zfo_139",s="_statusMsg_16zfo_162",c="_skeletonCard_16zfo_170",p="_skeletonThumb_16zfo_175",i="_skeletonLine_16zfo_181",f="_shimmer_16zfo_188",d="_pagination_16zfo_202",z="_pageBtn_16zfo_210",k="_pageIndicator_16zfo_224",g="_matureToggleWrap_16zfo_229",y="_matureLabel_16zfo_236",m="_translateToggleWrap_16zfo_242",B="_filterTogglesGroup_16zfo_249",h="_filterBar_16zfo_261",T="_filterLabel_16zfo_268",b="_detailOverlay_16zfo_273",u="_detailModal_16zfo_285",w="_detailClose_16zfo_297",L="_detailHeader_16zfo_313",C="_detailCover_16zfo_319",R="_detailInfo_16zfo_328",v="_detailTitle_16zfo_332",A="_detailRj_16zfo_339",I="_metaRow_16zfo_345",S="_metaLabel_16zfo_351",H="_tagChips_16zfo_357",O="_tagChip_16zfo_357",W="_npOverlay_16zfo_373",D="_playerBarDocked_16zfo_388",M="_playerBarDockedHidden_16zfo_402",P="_npOverlayControlsHidden_16zfo_413",j="_npArrow_16zfo_413",G="_npImageCount_16zfo_414",V="_npStage_16zfo_423",x="_sliderViewport_16zfo_439",q="_slide_16zfo_439",E="_slideImg_16zfo_441",F="_npStageBlank_16zfo_460",J="_sliderTrack_16zfo_479",K="_slideImgLoaded_16zfo_512",N="_slideSpinner_16zfo_517",Q="_npArrowLeft_16zfo_567",U="_npArrowRight_16zfo_571",X="_chapterPanel_16zfo_588",Y="_chapterRow_16zfo_603",Z="_chapterRowActive_16zfo_622",$="_chapterRowTime_16zfo_626",oo="_chapterRowLabel_16zfo_637",eo="_npBackdrop_16zfo_643",to="_playerBtn_16zfo_649",ao="_playerBtnActive_16zfo_649",_o="_tracksSection_16zfo_655",no="_tracksHeading_16zfo_659",ro="_trackList_16zfo_665",lo="_trackRow_16zfo_674",so="_trackIcon_16zfo_692",co="_trackLabel_16zfo_697",po="_trackType_16zfo_702",io="_noTracks_16zfo_707",fo="_playAllBtn_16zfo_712",zo="_detailActions_16zfo_725",ko="_playerBar_16zfo_388",go="_playerActions_16zfo_748",yo="_playerInfo_16zfo_754",mo="_playerTitle_16zfo_759",Bo="_playerTrack_16zfo_768",ho="_playerControls_16zfo_774",To="_seekRow_16zfo_781",bo="_playerButtons_16zfo_789",uo="_seekBtn_16zfo_824",wo="_seekBtnLabel_16zfo_829",Lo="_playBtn_16zfo_834",Co="_seekBar_16zfo_881",Ro="_volumeBar_16zfo_882",vo="_timeLabel_16zfo_974",Ao="_playerRight_16zfo_980",Io="_volumeIcon_16zfo_986",So="_playerToggle_16zfo_1030",Ho={sortSelect:o,grid:e,fetching:t,thumbPlaceholder:a,asmrCardWrap:_,bookmarkBtn:n,bookmarked:r,popupCover:l,statusMsg:s,skeletonCard:c,skeletonThumb:p,skeletonLine:i,shimmer:f,pagination:d,pageBtn:z,pageIndicator:k,matureToggleWrap:g,matureLabel:y,translateToggleWrap:m,filterTogglesGroup:B,filterBar:h,filterLabel:T,detailOverlay:b,detailModal:u,detailClose:w,detailHeader:L,detailCover:C,detailInfo:R,detailTitle:v,detailRj:A,metaRow:I,metaLabel:S,tagChips:H,tagChip:O,npOverlay:W,playerBarDocked:D,playerBarDockedHidden:M,npOverlayControlsHidden:P,npArrow:j,npImageCount:G,npStage:V,sliderViewport:x,slide:q,slideImg:E,npStageBlank:F,sliderTrack:J,slideImgLoaded:K,slideSpinner:N,npArrowLeft:Q,npArrowRight:U,chapterPanel:X,chapterRow:Y,chapterRowActive:Z,chapterRowTime:$,chapterRowLabel:oo,npBackdrop:eo,playerBtn:to,playerBtnActive:ao,tracksSection:_o,tracksHeading:no,trackList:ro,trackRow:lo,trackIcon:so,trackLabel:co,trackType:po,noTracks:io,playAllBtn:fo,detailActions:zo,playerBar:ko,playerActions:go,playerInfo:yo,playerTitle:mo,playerTrack:Bo,playerControls:ho,seekRow:To,playerButtons:bo,seekBtn:uo,seekBtnLabel:wo,playBtn:Lo,seekBar:Co,volumeBar:Ro,timeLabel:vo,playerRight:Ao,volumeIcon:Io,playerToggle:So};export{Ho as a};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{o as U,d as k,h as L,T as E,u,S as y}from"./vendor-core-DfaOB3Rn.js";import{M as F}from"./MediaCard-CpwsrXo5.js";import{L as N}from"./LibraryListPage-Dm2yVn0-.js";import{u as _,A as q}from"./useAsmrPopup-SFHPhpcl.js";import{A as z}from"./AsmrResetProgressModal-BS6UFJN4.js";import{g as h}from"./index-dRhc9-ol.js";import{m as O,u as Q,n as Y,o as D,e as G,p as H,q as K,r as Z,A as T,k as J}from"./useAsmr-CKOdHgol.js";import"./vendor-ui-3qrARCPr.js";import"./useIsMobile-Dvx1uJ82.js";import"./vendor-icons-DrybRNxP.js";import"./SkeletonGrid-CpJCZSRN.js";import"./ErrorMessage-DAxegITP.js";import"./Watchlist.module-XGtVQr1d.js";import"./Asmr.module-CdWlioTY.js";import"./usePopup-DS9jMzp6.js";import"./ResetProgressModal-BXHxSfy-.js";import"./vendor-query-BBT5_sHx.js";const V=["All","Continue Listening",...T],A=24;function W(a){const d=a.currentTime??0,i=a.duration??0,r=(a.trackIndex??0)+1;return i>0?`Track ${r} · ${h(d)} / ${h(i)}`:`Track ${r}`}function kt(){const{filter:a="All"}=U(),[d,i]=k(1),[r,m]=k(null);L(()=>{document.title="Listening List - dango"},[]),L(()=>{i(1)},[a]);const o=a==="Continue Listening",n=O(o?"All":a,d,A),l=Q(100),f=Y(),C=D(),{toggle:v,bookmarkedIds:R}=G(),{popup:s,openPopup:I,scheduleClose:c,cancelClose:p,closePopup:B}=_(),P=H(),S=K(),j=Z(),M=E(()=>o?(l.data?.data??[]).map(t=>{const e=t.currentTime??0,g=t.duration??0,b=t.rjCode||t.id;return{key:t.id,libId:t.id,rjCode:b,title:t.title,thumbnail:t.thumbnail||"",status:t.status,isAdult:!!t.isAdult,trackLabel:`Track ${(t.trackIndex??0)+1}`,progressPercent:g>0?Math.min(100,e/g*100):0,progressLabel:W(t),detailTarget:`/asmr/${encodeURIComponent(b)}`}}):(n.data?.data??[]).map(t=>{const e=t.rjCode||t.id;return{key:t.id,libId:t.id,rjCode:e,title:t.title,thumbnail:t.thumbnail||"",status:t.status,isAdult:!!t.isAdult,trackLabel:t.lastTrackIndex!==null&&t.lastTrackIndex!==void 0?`Track ${t.lastTrackIndex+1}`:void 0,progressPercent:void 0,progressLabel:void 0,detailTarget:`/asmr/${encodeURIComponent(e)}`}}),[o,l.data,n.data]),w=o?l.data?.total??0:n.data?.total??0,x=o?l.isLoading:n.isLoading,$=o?l.error:n.error;return u(N,{headerTitle:"My Listening List",headerSubtitle:"Track and manage your ASMR collection",filters:V,filterBy:a,filterBasePath:"/listening-list",continueFilter:"Continue Listening",statusOptions:[...T],entries:M,total:w,isLoading:x,error:$,page:d,pageSize:A,onPageChange:i,emptyTitle:"Your listening list is looking a bit lonely",emptyFilteredText:"No works match this filter.",emptyAllText:"Let's find something to listen to!",browseLabel:"Browse ASMR",browseTarget:"/asmr",removeListLabel:"Remove from Listening List",listNoun:"listening list",skipConfirmKey:"asmrSkipRemoveConfirmation",onBulkStatus:(t,e)=>P.mutate({ids:t,status:e}),onBulkRemove:t=>{o?j.mutate({ids:t}):S.mutate({ids:t})},onStatusChange:(t,e)=>C.mutate({id:t,status:e}),onRemoveItem:t=>f.mutate({id:t}),renderCard:t=>u(F,{item:{id:t.libId,title:t.title,thumbnail:t.thumbnail,typeBadge:t.rjCode||void 0,chapterBadge:t.trackLabel??null,isAdult:t.isAdult},linkTo:t.detailTarget,hoverIcon:"info",layout:"horizontal",showInfoButton:!o,progress:t.progressPercent!==void 0&&t.progressLabel?{percent:t.progressPercent,label:t.progressLabel}:void 0,showProgress:o,display:{elements:{poster:{typeBadge:!0,chapterBadge:!0,adultBadge:!0},info:{title:!0,mobileBadges:!0,progress:!0,meta:!1}}},onRemove:o?()=>m(t):void 0,onOpenDetails:o?void 0:e=>I(e,{rjCode:t.rjCode,title:t.title,thumbnail:t.thumbnail,isAdult:t.isAdult,listenTarget:t.detailTarget,progressLabel:t.progressLabel}),onPopupHoverIntent:e=>e?p():c(),rawThumbnail:!0}),modals:u(y,{children:[u(z,{workId:r?r.libId:null,title:r?.title,onClose:()=>m(null)}),s&&u(q,{data:s.data,anchorRect:s.rect,bookmarked:R.has(J(s.data.rjCode)),onToggleBookmark:()=>v({rjCode:s.data.rjCode,title:s.data.title,thumbnail:s.data.thumbnail||"",isAdult:s.data.isAdult}),onMouseEnter:p,onMouseLeave:c,onRequestClose:B})]})})}export{kt as default};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
._authRow_10tvv_1{display:flex;gap:8px;align-items:center;flex-wrap:wrap;margin-bottom:16px}._btn_10tvv_9{display:inline-flex;align-items:center;justify-content:center;gap:8px;padding:10px 20px;border:none;border-radius:var(--radius-full);background:var(--accent);color:var(--accent-text);font-size:var(--font-size-sm);font-weight:600;cursor:pointer;transition:background var(--transition-fast),transform var(--transition-quick),opacity var(--transition-fast)}._btn_10tvv_9:hover:not(:disabled){background:var(--accent-light);box-shadow:0 0 12px var(--accent-glow)}._btn_10tvv_9:active:not(:disabled){transform:scale(.96)}._btn_10tvv_9:disabled{opacity:.5;cursor:not-allowed}._btnGhost_10tvv_42{display:inline-flex;align-items:center;justify-content:center;gap:8px;padding:10px 20px;border:1px solid var(--border-primary);border-radius:var(--radius-full);background:#ffffff0a;color:var(--text-primary);font-size:var(--font-size-sm);font-weight:600;cursor:pointer;transition:background var(--transition-fast),transform var(--transition-quick),opacity var(--transition-fast)}._btnGhost_10tvv_42:hover:not(:disabled){background:#ffffff14}._btnGhost_10tvv_42:active:not(:disabled){transform:scale(.96)}._btnGhost_10tvv_42:disabled{opacity:.5;cursor:not-allowed}._cookieForm_10tvv_74{display:flex;gap:8px;flex-wrap:wrap;align-items:center;flex:1;min-width:280px}._cookieInput_10tvv_83{flex:1;min-width:200px;padding:10px 16px;border:1px solid var(--border-primary);border-radius:var(--radius-full);background:var(--bg-secondary);color:var(--text-primary);font-size:var(--font-size-sm);outline:none}._cookieInput_10tvv_83:focus{border-color:var(--accent)}._link_10tvv_99{color:var(--accent);text-decoration:underline;font-weight:600}._link_10tvv_99:hover{opacity:.85}._contentLayout_10tvv_109{display:grid;grid-template-columns:minmax(0,1fr) 340px;gap:24px;align-items:start}._mainCol_10tvv_116{min-width:0}._upnextAside_10tvv_120{position:sticky;top:16px;max-height:calc(100vh - 140px);overflow-y:auto;background:#ffffff08;border:1px solid var(--border-primary);border-radius:12px;padding:12px}._upnextHeader_10tvv_131{display:flex;align-items:center;justify-content:space-between;gap:8px;margin-bottom:8px}._upnextTitle_10tvv_139{font-size:var(--font-size-sm);font-weight:700;opacity:.9;display:flex;align-items:center;gap:6px;min-width:0}._upnextActions_10tvv_149{display:flex;gap:8px;margin-bottom:12px;flex-wrap:wrap}._nowPlayingLabel_10tvv_156{font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.6px;opacity:.55;margin:4px 0 8px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}._nowPlayingWrap_10tvv_168{margin-bottom:12px}._likeBtn_10tvv_172{display:inline-flex;align-items:center;justify-content:center;padding:8px;border:0;background:transparent;color:inherit;opacity:.55;cursor:pointer;flex-shrink:0;border-radius:50%}._likeBtn_10tvv_172:hover:not(:disabled){opacity:1;background:#ffffff14}._likeBtn_10tvv_172:disabled{opacity:.3;cursor:not-allowed}._likeActive_10tvv_196{opacity:1;color:var(--accent-light)}@media(max-width:900px){._contentLayout_10tvv_109{grid-template-columns:minmax(0,1fr)}._upnextAside_10tvv_120{position:static;max-height:none;order:-1}}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{d as y,h as A,u as e,A as R,g as Pe,S,$ as tt,p as nt,T as j}from"./vendor-core-DfaOB3Rn.js";import{t as Ie,P as it,h as H,z as $}from"./index-dRhc9-ol.js";import{I as f}from"./vendor-icons-DrybRNxP.js";import{r as p}from"./Radio.module-DGSZLpKM.js";import{a}from"./Asmr.module-CdWlioTY.js";import{b as ne,u as Ae,a as Ee}from"./vendor-query-BBT5_sHx.js";import{p as te}from"./page.module-C1MlZzf3.js";const pe=new Set,ue=new Map,qe=n=>`/api/image-proxy?url=${encodeURIComponent(n)}&strict=1`,ze=n=>/googleusercontent\.com/.test(n)?n.replace(/=w\d+-h\d+(?:-l\d+)?(?:-rj)?$/,"=w544-h544-l90-rj"):n,Ye=/^[A-Za-z0-9_-]{11}$/;function st(n,s){const l=[],b=n?ue.get(n):void 0;b&&!pe.has(b)&&l.push(b);for(const x of s??[]){if(!x?.url)continue;const w=qe(ze(x.url));pe.has(w)||l.includes(w)||l.push(w)}if(n&&Ye.test(n)){const x=qe(`https://i.ytimg.com/vi/${n}/hqdefault.jpg`);!pe.has(x)&&!l.includes(x)&&l.push(x)}return l}function at(n,s){const l=s?.[0]?.url;return l?ze(l):n&&Ye.test(n)?`https://i.ytimg.com/vi/${n}/hqdefault.jpg`:""}function rt(n,s){!n||pe.has(s)||(ue.set(n,s),ue.size>500&&ue.clear())}function ot(n){pe.add(n);for(const[s,l]of ue)l===n&&ue.delete(s)}function $e({id:n,thumbnails:s}){const[l,b]=y(0),[x,w]=y(!1);A(()=>{b(0),w(!1)},[n]);const _=st(n,s),E=_[l];return x||!E?e("span",{className:p.stationThumbPlaceholder,children:e(f,{name:"headphones"})}):e("img",{src:E,alt:"",className:p.stationThumb,loading:"lazy",decoding:"async",onLoad:()=>rt(n,E),onError:()=>{ot(E),l+1<_.length?b(l+1):w(!0)}})}function Ue(n){if(!Number.isFinite(n)||n<0)return"0:00";const s=Math.floor(n/60),l=Math.floor(n%60);return`${s}:${String(l).padStart(2,"0")}`}const ct=({track:n,shuffle:s,onToggleShuffle:l,onTrackStep:b,onTrackFailed:x,onClose:w,liked:_,likeVisible:E,likePending:de,onToggleLike:ie})=>{const O=R(null),me=R(!1),[T,se]=y(!1),[h,v]=y(!0),[V,B]=y(!1),[F,ae]=y(0),[Z,J]=y(0),[N,re]=y(()=>{const r=parseFloat(localStorage.getItem("musicVolume")||"");return Number.isFinite(r)?r:1}),[z,oe]=y(()=>localStorage.getItem("musicAutoplay")!=="false"),L=R(N);L.current=N;const k=R(z);k.current=z;const W=R(0),D=R(0),Y=R(null),G=R("");G.current||(G.current=typeof crypto<"u"&&"randomUUID"in crypto?crypto.randomUUID():`music-${Date.now()}-${Math.random().toString(36).slice(2)}`);const fe=`/api/music/audio?videoId=${encodeURIComponent(n.id)}`,q=Pe.useCallback(()=>{Y.current&&(window.clearTimeout(Y.current),Y.current=null)},[]),ce=Pe.useCallback(r=>{const c=O.current;if(!c)return;q(),v(!0),B(!1);const P=()=>{if(c.removeEventListener("loadedmetadata",P),r>5&&Number.isFinite(c.duration)&&r<c.duration-5)try{c.currentTime=r}catch{}};c.addEventListener("loadedmetadata",P),c.src=fe,c.load(),c.volume=L.current,c.play().catch(()=>{})},[fe,q]),ye=()=>{v(!1),B(!0),x?.(n),k.current&&b(1)},be=()=>{if(!Y.current){if(D.current>=3){ye();return}D.current+=1,Y.current=window.setTimeout(()=>{Y.current=null,ce(W.current)},5e3)}},Q=()=>{D.current=0,ce(W.current)};A(()=>{W.current=0,D.current=0,se(!1),ae(0),J(0);const r=O.current;return ce(W.current),()=>{q(),r&&r.removeAttribute("src")}},[ce,q]),A(()=>{const r=O.current;r&&(r.volume=N)},[N]),A(()=>(document.body.classList.add("asmr-player-open"),()=>document.body.classList.remove("asmr-player-open")),[]);const ve=at(n.id,n.thumbnails),K=Pe.useCallback(r=>{const c=O.current;Ie.discord.musicPresence.mutate({title:n.title,artistLabel:n.artists,isPlaying:r,thumbnail:ve||"",currentTime:c?.currentTime??0,duration:Number.isFinite(c?.duration??NaN)?c?.duration:0,sessionId:G.current}).catch(()=>{}),Ie.discord.heartbeat.mutate({sessionId:G.current}).catch(()=>{})},[n,ve]);A(()=>{K(T)},[K,T,n.id]),A(()=>{if(!T)return;const r=window.setInterval(()=>K(!0),15e3);return()=>window.clearInterval(r)},[T,K]),A(()=>{const r=G.current,c=()=>it(r),P=()=>c(),U=()=>{document.visibilityState==="hidden"&&c()};return window.addEventListener("pagehide",P),window.addEventListener("beforeunload",P),document.addEventListener("visibilitychange",U),()=>{window.removeEventListener("pagehide",P),window.removeEventListener("beforeunload",P),document.removeEventListener("visibilitychange",U),c()}},[]);const Se=()=>{const r=O.current;r&&(r.paused?r.play().catch(()=>{}):r.pause())},ke=`${n.title} — ${n.artists}`,Ne=e(S,{children:[e("div",{className:a.playerButtons,children:[e("button",{className:a.playerBtn,onClick:()=>b(-1),"aria-label":"Previous track",children:e(f,{name:"step-backward"})}),e("button",{className:`${a.playerBtn} ${a.playBtn}`,onClick:Se,"aria-label":T?"Pause":"Play",children:T?e(f,{name:"pause"}):e(f,{name:"play"})}),e("button",{className:a.playerBtn,onClick:()=>b(1),"aria-label":"Next track",children:e(f,{name:"step-forward"})}),V&&e("button",{className:a.playerBtn,onClick:Q,title:"Retry playback","aria-label":"Retry playback",children:e(f,{name:"redo"})})]}),e("div",{className:a.seekRow,children:[e("span",{className:a.timeLabel,children:Ue(F)}),e("input",{className:a.seekBar,type:"range",min:0,max:Z||0,step:.1,value:Math.min(F,Z||0),onChange:r=>{const c=O.current;!c||!Number.isFinite(c.duration)||(c.currentTime=parseFloat(r.target.value),ae(c.currentTime))},onPointerDown:()=>{me.current=!0},onPointerUp:()=>{me.current=!1},style:{"--played-percent":`${Z>0?F/Z*100:0}%`},"aria-label":"Seek"}),e("span",{className:a.timeLabel,children:Ue(Z)}),h&&!V&&e("span",{className:a.timeLabel,children:"loading…"})]})]}),xe=e(S,{children:[e($e,{id:n.id,thumbnails:n.thumbnails}),e("div",{className:a.playerInfo,children:[e("p",{className:a.playerTitle,title:ke,children:ke}),e("p",{className:a.playerTrack,children:n.artists})]}),e("div",{className:a.playerControls,children:Ne}),e("div",{className:a.playerRight,children:[N===0?e(f,{name:"volume-mute",className:a.volumeIcon}):N<.5?e(f,{name:"volume-down",className:a.volumeIcon}):e(f,{name:"volume-up",className:a.volumeIcon}),e("input",{className:a.volumeBar,type:"range",min:0,max:1,step:.01,value:N,onChange:r=>{const c=parseFloat(r.target.value);re(c),localStorage.setItem("musicVolume",String(c))},style:{"--volume-percent":`${N*100}%`},"aria-label":"Volume"})]}),e("div",{className:a.playerActions,children:[E&&ie&&e("button",{className:`${a.playerBtn} ${_?a.playerBtnActive:""}`,onClick:ie,disabled:de,title:_?"Remove from Liked Music":"Add to Liked Music","aria-label":_?"Remove from Liked Music":"Add to Liked Music","aria-pressed":!!_,children:e(f,{name:"heart"})}),e("button",{className:`${a.playerBtn} ${s?a.playerBtnActive:""}`,onClick:l,title:s?"Shuffle on":"Shuffle off","aria-label":"Toggle shuffle","aria-pressed":s,children:e(f,{name:"shuffle"})}),e("button",{className:`${a.playerBtn} ${z?a.playerBtnActive:""}`,onClick:()=>{oe(r=>(localStorage.setItem("musicAutoplay",String(!r)),!r))},title:z?"Autoplay on: next track plays automatically":"Autoplay off","aria-label":"Toggle autoplay","aria-pressed":z,children:e(f,{name:"forward"})}),e("a",{href:`https://music.youtube.com/watch?v=${encodeURIComponent(n.id)}`,target:"_blank",rel:"noreferrer",title:"Open in YouTube Music","aria-label":"Open in YouTube Music",className:a.playerBtn,style:{display:"inline-flex",alignItems:"center"},children:e(f,{name:"external-link-alt"})}),e("button",{className:a.playerBtn,onClick:w,"aria-label":"Close player",children:e(f,{name:"times"})})]})]});return tt(e(S,{children:[e("audio",{ref:O,preload:"none",onPlay:()=>{se(!0),v(!1),B(!1),D.current=0,q()},onPause:()=>se(!1),onWaiting:()=>{V||v(!0)},onPlaying:()=>{v(!1),D.current=0,q()},onTimeUpdate:r=>{const c=r.target.currentTime;W.current=c,q(),me.current||ae(c)},onStalled:()=>{be()},onLoadedMetadata:r=>{const c=r.target.duration;Number.isFinite(c)&&J(c)},onDurationChange:r=>{const c=r.target.duration;Number.isFinite(c)&&J(c)},onError:()=>{be()},onEnded:()=>{k.current&&b(1)}}),e("div",{className:a.playerBar,children:xe})]}),document.body)},lt=()=>e("div",{style:{marginTop:"0.9rem",paddingTop:"0.9rem",borderTop:"1px dashed var(--border-color)"},children:[e("h4",{style:{margin:"0 0 0.35rem",fontSize:"0.95rem"},children:"Where to get the cookie"}),e("p",{style:{margin:"0 0 0.6rem",fontSize:"0.85rem",color:"var(--text-secondary)"},children:"Google marks its login cookies unreadable to page scripts, so this has to come from DevTools — it takes about 20 seconds:"}),e("ol",{style:{margin:"0 0 0.75rem",paddingLeft:"1.25rem",fontSize:"0.85rem",color:"var(--text-secondary)",display:"grid",gap:"0.3rem"},children:[e("li",{children:["Open"," ",e("a",{href:"https://music.youtube.com/",target:"_blank",rel:"noopener noreferrer",children:"music.youtube.com"})," ","and make sure you are logged in."]}),e("li",{children:["Press ",e("kbd",{children:"F12"}),", go to the ",e("strong",{children:"Network"})," tab, and type ",e("kbd",{children:"browse"})," ","in the filter box."]}),e("li",{children:["Click any ",e("kbd",{children:"POST"})," browse entry, then scroll to Request Headers."]}),e("li",{children:[e("strong",{children:"Right-click"})," the ",e("kbd",{children:"Cookie"})," value and choose"," ",e("strong",{children:"Copy value"})," — right-click copies the full unwrapped value, selecting the text by hand truncates it."]}),e("li",{children:"Paste it into the sign-in box above and press Sign in."})]})]}),ge=300*1e3,ut=()=>{const n=H();return ne({...n.music.authStatus.queryOptions(),staleTime:30*1e3,refetchInterval:s=>s.state.data?.authenticated?!1:3e3,retry:1})},dt=()=>{const n=H(),s=Ae();return Ee(n.music.authStart.mutationOptions({onSuccess:(l,b)=>{localStorage.setItem("ytmusic_cookie",b.cookie??""),s.invalidateQueries(n.music.pathFilter())}}))},mt=()=>{const n=H(),s=Ae();return Ee(n.music.signOut.mutationOptions({onSuccess:()=>{s.invalidateQueries(n.music.pathFilter())}}))},ht=n=>{const s=H();return ne({...s.music.search.queryOptions({q:n}),enabled:n.trim().length>0,staleTime:ge,retry:1})},pt=n=>{const s=H();return ne({...s.music.library.queryOptions(),enabled:n,staleTime:ge,retry:1})},gt=n=>{const s=H();return ne({...s.music.track.queryOptions({id:n||void 0}),enabled:!!n,staleTime:ge,retry:1})},ft=n=>{const s=H();return ne({...s.music.playlist.queryOptions({id:n||void 0}),enabled:!!n,staleTime:ge,retry:1})},yt=n=>{const s=H();return ne({...s.music.upnext.queryOptions({id:n||void 0}),enabled:!!n,staleTime:ge,retry:1})},bt=n=>{const s=H();return ne({...s.music.likes.queryOptions(),enabled:n,staleTime:120*1e3,retry:1})},vt=()=>{const n=H(),s=Ae();return Ee(n.music.like.mutationOptions({onSuccess:()=>{s.invalidateQueries(n.music.pathFilter())}}))},kt="_authRow_10tvv_1",Nt="_btn_10tvv_9",wt="_btnGhost_10tvv_42",St="_cookieForm_10tvv_74",xt="_cookieInput_10tvv_83",Tt="_link_10tvv_99",Lt="_contentLayout_10tvv_109",Mt="_mainCol_10tvv_116",_t="_upnextAside_10tvv_120",Ct="_upnextHeader_10tvv_131",Rt="_upnextTitle_10tvv_139",Pt="_upnextActions_10tvv_149",It="_nowPlayingLabel_10tvv_156",$t="_nowPlayingWrap_10tvv_168",At="_likeBtn_10tvv_172",Et="_likeActive_10tvv_196",g={authRow:kt,btn:Nt,btnGhost:wt,cookieForm:St,cookieInput:xt,link:Tt,contentLayout:Lt,mainCol:Mt,upnextAside:_t,upnextHeader:Ct,upnextTitle:Rt,upnextActions:Pt,nowPlayingLabel:It,nowPlayingWrap:$t,likeBtn:At,likeActive:Et};function He(n){const s=n.trim();if(!s)return null;const l=s.match(/[?&]v=([A-Za-z0-9_-]{11})/)?.[1]??s.match(/youtu\.be\/([A-Za-z0-9_-]{11})/)?.[1]??null;return l||(/^[A-Za-z0-9_-]{11}$/.test(s)?s:null)}const Ot="https://github.com/serifpersia/dango/releases/latest/download/dango-extension.zip",Yt=()=>{const[n,s]=nt(),l=He(n.get("v")??""),[b,x]=y(()=>n.get("q")??""),w=n.get("q")??"";A(()=>{x(w)},[w]);const[_,E]=y(()=>{try{return localStorage.getItem("ytmusic_cookie")??""}catch{return""}}),[de,ie]=y(!1),[O,me]=y(()=>localStorage.getItem("musicShuffle")==="true"),[T,se]=y(()=>localStorage.getItem("musicRadio")!=="false"),[h,v]=y(null),[V,B]=y([]),[F,ae]=y(null),[Z,J]=y({}),N=R([]);N.current=V;const re=R(null);re.current=h;const z=R(!1),oe=R(new Set),{data:L}=ut(),k=dt(),W=mt(),D=typeof navigator<"u"?navigator.userAgent:"",Y=/android/i.test(D),G=/firefox|fxios/i.test(D)&&!Y,fe=!G&&!Y&&/chrome|chromium|edg|brave/i.test(D),q=G||fe,ce=()=>{if(de)return;ie(!0);const t=`${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`,i=window.setTimeout(()=>{window.removeEventListener("dango:ytmusic-cookie",o),ie(!1),$.error("Helper not detected — install the Dango Helper extension first.")},3e3),o=d=>{let m;try{m=JSON.parse(d.detail)}catch{return}if(!(!m||m.requestId!==t))if(window.clearTimeout(i),window.removeEventListener("dango:ytmusic-cookie",o),ie(!1),m?.ok&&m.cookie){const u=m.cookie.replace(/^cookie\s*:\s*/i,"").replace(/^["']+|["']+$/g,"").trim();if(u.includes("…")||u.includes("...")){$.error(`Helper returned a truncated cookie (${u.length} chars) — reload music.youtube.com and Extract again.`);return}E(u),$.success("Cookie extracted from helper — signing in…"),k.mutate({cookie:u})}else m?.error==="NO_COOKIE"?$.error("Helper found no YouTube login — log in on a music.youtube.com tab first."):$.error("Helper request failed — try again.")};window.addEventListener("dango:ytmusic-cookie",o),window.dispatchEvent(new CustomEvent("dango:get-ytmusic-cookie",{detail:{requestId:t}}))},{data:ye,isLoading:be}=ht(w),{data:Q,isLoading:ve,isFetching:K,error:Se,refetch:ke}=pt(L?.authenticated===!0),{data:Ne,isLoading:xe}=ft(F?.id??null),{data:Te,isLoading:r}=yt(h?.id??null),{data:c}=bt(L?.authenticated===!0),P=vt(),U=j(()=>ye?.tracks||[],[ye]),Le=j(()=>Q?.playlists||[],[Q]),I=j(()=>Q?.tracks||[],[Q]),X=j(()=>Ne?.tracks||[],[Ne]),M=j(()=>{const t=Te?.tracks||[];return h?t.filter(i=>i.id!==h.id):t},[Te,h]),Ge=j(()=>new Set(c?.likedIds||[]),[c]),Me=t=>Z[t.id]??(Ge.has(t.id)||t.liked===!0);A(()=>{L?.authenticated!==!0&&J({})},[L?.authenticated]);const Qe=j(()=>[...U,...X,...I,...M],[U,X,I,M]),Ve=l&&h?.id!==l&&!Qe.some(t=>t.id===l)?l:null,{data:Oe}=gt(Ve),_e=h?.id;A(()=>{document.title=h?`${h.title} - dango`:"Music - dango"},[h]),A(()=>{if(!l){_e&&v(null);return}if(_e===l)return;const t=d=>{const m=d.find(u=>u.id===l);return m?(v(m),B(u=>u.some(le=>le.id===l)?u:d.length>0?d:[m]),!0):!1};if(t(U)||t(X)||t(I))return;const i=M.find(d=>d.id===l);if(i){v(i),B(d=>d.some(m=>m.id===i.id)?d:[i]);return}const o=Oe?.track;o&&o.id===l&&(v(o),B(d=>d.some(m=>m.id===o.id)?d:[o]))},[l,_e,U,X,I,M,Oe]);const ee=t=>{s(i=>{const o=new URLSearchParams(i);return t?o.set("v",t):o.delete("v"),o})},Ce=(t,i)=>{oe.current.delete(t.id),v(t),B(i.length>0?i:[t]),ee(t.id)},We=t=>{oe.current.add(t.id),$.error(`Couldn't play “${t.title}” — skipped.`)},je=async()=>{if(z.current)return;const t=N.current,i=t[t.length-1]??re.current;if(i){z.current=!0;try{const o=await Ie.music.upnext.query({id:i.id}),d=new Set(t.map(u=>u.id)),m=(o.tracks||[]).filter(u=>!d.has(u.id)&&!oe.current.has(u.id));if(m.length===0){const u=N.current[0];u&&(v(u),ee(u.id));return}B([...N.current,...m]),v(m[0]),ee(m[0].id)}catch{const o=N.current[0];o&&(v(o),ee(o.id))}finally{z.current=!1}}},Ze=t=>{const i=N.current,o=re.current;if(!o||i.length===0)return;const d=i.findIndex(C=>C.id===o.id);if(t>0&&T&&(d<0||d===i.length-1)){je();return}const m=C=>oe.current.has(C.id),u=i.length;let le;if(O&&u>1){let C=Math.floor(Math.random()*u);for(let Re=0;Re<u&&(C===d||m(i[C]));Re++)C=Math.floor(Math.random()*u);le=i[C]}else{const C=t>0?1:-1;let we=(((d<0?C>0?-1:0:d)+C)%u+u)%u;for(let De=1;De<u&&m(i[we]);De++)we=((we+C)%u+u)%u;le=i[we]}v(le),ee(le.id)},Je=()=>{if(T){se(!1),localStorage.setItem("musicRadio","false");return}const t=re.current;if(!t)return;if(M.length===0){$.error("No radio suggestions for this song yet.");return}const i=new Set,o=[];for(const d of[t,...M])i.has(d.id)||(i.add(d.id),o.push(d));se(!0),localStorage.setItem("musicRadio","true"),B(o),$.success(`Radio started — ${M.length} songs queued.`)},Be=t=>{if(L?.authenticated!==!0){$.error("Sign in to like songs.");return}const i=!Me(t);J(o=>({...o,[t.id]:i})),P.mutate({id:t.id,like:i},{onError:()=>{J(o=>{const d={...o};return delete d[t.id],d}),$.error("Like action failed — try again.")}})},Ke=()=>{me(t=>(localStorage.setItem("musicShuffle",String(!t)),!t))},Xe=t=>e("button",{className:p.stationRow,onClick:()=>ae(t),"aria-label":`Open ${t.title}`,children:[e($e,{id:t.id,thumbnails:t.thumbnails}),e("span",{className:p.stationMeta,children:[e("span",{className:p.stationName,children:t.title}),t.subtitle&&e("span",{className:p.stationSub,children:t.subtitle})]})]},t.id),he=(t,i,o)=>{const d=h?.id===t.id,m=Me(t),u=[t.album,t.duration].filter(Boolean).join(" · ");return e("div",{className:`${p.stationRow} ${d?p.stationRowActive:""}`,style:{cursor:"default"},children:[e("button",{onClick:()=>o?o(t):Ce(t,i),className:p.stationRow,style:{flex:1,minWidth:0,background:"transparent",border:0,padding:0},"aria-label":`Play ${t.title}`,children:[e($e,{id:t.id,thumbnails:t.thumbnails}),e("span",{className:p.stationMeta,children:[e("span",{className:p.stationName,children:t.title}),e("span",{className:p.stationSub,children:[t.artists,u?` · ${u}`:""]})]})]}),L?.authenticated===!0&&e("button",{className:`${g.likeBtn} ${m?g.likeActive:""}`,onClick:()=>Be(t),disabled:P.isPending,title:m?"Remove from Liked Music":"Add to Liked Music","aria-label":m?`Remove ${t.title} from Liked Music`:`Add ${t.title} to Liked Music`,"aria-pressed":m,children:e(f,{name:"heart"})}),e("a",{href:`https://music.youtube.com/watch?v=${encodeURIComponent(t.id)}`,target:"_blank",rel:"noreferrer",title:"Open in YouTube Music","aria-label":`Open ${t.title} in YouTube Music`,style:{padding:8,color:"inherit",opacity:.7,flexShrink:0},children:e(f,{name:"external-link-alt"})})]},t.id)},Fe=j(()=>{if(!h)return M;const t=new Set,i=[];for(const o of[h,...M])t.has(o.id)||(t.add(o.id),i.push(o));return i},[h,M]),et=t=>{Ce(t,Fe)};return e("div",{className:`${te.page} ${h?te.pageWithPlayer:""}`,children:[e("div",{className:te.header,children:[e("h1",{className:te.pageTitle,children:[e(f,{name:"headphones"})," Music"]}),e("form",{className:te.searchForm,onSubmit:t=>{t.preventDefault();const i=He(b);if(i)ee(i);else{const o=b.trim();s(d=>{const m=new URLSearchParams(d);return o?m.set("q",o):m.delete("q"),m})}},children:[e("input",{className:te.searchInput,value:b,onChange:t=>x(t.target.value),placeholder:"Search YouTube Music…","aria-label":"Search music"}),e("button",{className:te.searchBtn,type:"submit","aria-label":"Search",children:e(f,{name:"search"})})]})]}),e("div",{className:g.authRow,children:[L?.authenticated?e(S,{children:[e("span",{className:a.statusMsg,children:["Signed in to YouTube Music",Q&&` — ${Q.tracks.length} songs, ${Q.playlists.length} playlists`]}),e("button",{className:g.btnGhost,onClick:()=>W.mutate(),disabled:W.isPending,children:"Sign out"})]}):e(S,{children:[e("form",{onSubmit:t=>{t.preventDefault();const i=_.replace(/^cookie\s*:\s*/i,"").replace(/^["']+|["']+$/g,"").trim();if(i){if(i.includes("…")||i.includes("...")){$.error("That cookie is truncated (contains …). Right-click the Cookie header → Copy value, not the wrapped preview.");return}i!==_&&E(i),k.mutate({cookie:i})}},className:g.cookieForm,children:[e("input",{className:g.cookieInput,value:_,onChange:t=>E(t.target.value),placeholder:"Paste music.youtube.com Cookie header…","aria-label":"YouTube Music cookie",type:"password"}),e("button",{className:g.btn,type:"submit",disabled:k.isPending||!_.trim(),children:k.isPending?"Checking…":"Sign in"}),q&&e("button",{className:g.btnGhost,type:"button",onClick:ce,disabled:de||k.isPending,title:"Read the YouTube Music cookie via the Dango Helper extension (same zip as AnimePahe/JapaneseASMR)",children:de?"Extracting…":"Extract from helper"})]}),q&&e("p",{className:a.statusMsg,children:["Two ways to sign in: paste the Cookie header manually (steps below), or download"," ",e("a",{href:Ot,target:"_blank",rel:"noreferrer",className:g.link,children:"dango-extension.zip"})," ","then"," ",G?e(S,{children:[e("code",{children:"about:debugging"})," → ",e("strong",{children:"This Firefox"})," →"," ",e("strong",{children:"Load Temporary Add-on"})," →"," ",e("code",{children:"dango-extension/firefox/manifest.json"})]}):e(S,{children:[e("code",{children:"chrome://extensions"})," → ",e("strong",{children:"Developer mode"})," →"," ",e("strong",{children:"Load unpacked"})," → ",e("code",{children:"dango-extension/chrome"})]}),", log in on a music.youtube.com tab, and click Extract."]}),e(lt,{})]}),k.isSuccess&&e("p",{className:a.statusMsg,children:["Signed in — found ",k.data.tracks," songs and ",k.data.playlists," ","playlists."]}),k.isError&&e("p",{className:a.statusMsg,children:k.error?.message||"Sign-in failed. Paste a fresh Cookie header from music.youtube.com."}),!L?.authenticated&&!k.isError&&!k.isSuccess&&e("p",{className:a.statusMsg,children:"Sign in with your YouTube Music cookie to see your library. Search and playback work without sign-in."})]}),e("div",{className:h?g.contentLayout:void 0,children:[e("div",{className:g.mainCol,children:w?e(S,{children:[e("h2",{className:p.sectionTitle,children:["Results for “",w,"”"]}),be?e("p",{className:a.statusMsg,children:"Searching…"}):U.length===0?e("p",{className:a.statusMsg,children:"No tracks found."}):e("div",{className:p.stationList,children:U.map(t=>he(t,U))})]}):L?.authenticated?e(S,{children:[e("h2",{className:p.sectionTitle,children:"Your library"}),ve?e("p",{className:a.statusMsg,children:"Loading library…"}):Le.length===0&&I.length===0&&!F?e(S,{children:[e("p",{className:a.statusMsg,children:Se?"Library failed to load.":"Your library came back empty — YouTube sometimes drops the saved session."}),e("div",{style:{marginBottom:8},children:e("button",{className:g.btnGhost,onClick:()=>{ke()},disabled:K,"aria-label":"Retry loading library",children:K?"Retrying…":"Retry"})}),e("p",{className:a.statusMsg,children:"If it keeps happening, sign out and paste a fresh cookie."})]}):e(S,{children:[Le.length>0&&!F&&e(S,{children:[e("h3",{className:p.sectionTitle,children:"Playlists"}),e("div",{className:p.stationList,children:Le.map(t=>Xe(t))})]}),I.length>0&&!F&&e(S,{children:[e("h3",{className:p.sectionTitle,children:["Liked songs (",I.length,")"]}),e("div",{style:{marginBottom:8},children:e("button",{className:g.btnGhost,onClick:()=>Ce(I[0],I),"aria-label":"Play all liked songs",children:[e(f,{name:"play"})," Play all"]})}),e("div",{className:p.stationList,children:I.map(t=>he(t,I))})]}),F?e(S,{children:[e("button",{className:g.btnGhost,onClick:()=>ae(null),style:{marginBottom:8},children:"← Back to library"}),e("h3",{className:p.sectionTitle,children:F.title}),xe?e("p",{className:a.statusMsg,children:"Loading playlist…"}):X.length===0?e("p",{className:a.statusMsg,children:"No tracks in this playlist."}):e("div",{className:p.stationList,children:X.map(t=>he(t,X))})]}):null]})]}):e("p",{className:a.statusMsg,children:"Search above, or sign in to see your library."})}),h&&e("aside",{className:g.upnextAside,"aria-label":"Up next",children:[e("div",{className:g.upnextHeader,children:e("span",{className:g.upnextTitle,children:[e(f,{name:"list-ul"})," Up Next"]})}),e("div",{className:g.nowPlayingWrap,children:[e("p",{className:g.nowPlayingLabel,children:"Now playing"}),he(h,V.length>0?V:[h])]}),e("div",{className:g.upnextActions,children:e("button",{className:g.btnGhost,onClick:Je,disabled:!T&&(r||M.length===0),"aria-pressed":T,title:T?"Stop the radio — the queue will loop instead of fetching more songs":"Queue these suggestions after the current song and keep the mix going",children:T?"Stop radio":"Start radio"})}),r?e("p",{className:a.statusMsg,children:"Loading suggestions…"}):M.length===0?e("p",{className:a.statusMsg,children:"No suggestions for this song."}):e("div",{className:p.stationList,children:M.map(t=>he(t,Fe,et))})]})]}),h&&e(ct,{track:h,queue:V,shuffle:O,onToggleShuffle:Ke,onTrackStep:Ze,onTrackFailed:We,onClose:()=>{v(null),ee(null)},liked:h?Me(h):!1,likeVisible:L?.authenticated===!0,likePending:P.isPending,onToggleLike:()=>Be(h)})]})};export{Yt as default};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{d as h,A as F,h as y,g as se,u as e,S as P,$ as J,T as K}from"./vendor-core-DfaOB3Rn.js";import{I as o}from"./vendor-icons-DrybRNxP.js";import{b as Z}from"./vendor-query-BBT5_sHx.js";import{h as x,t as G,P as re}from"./index-dRhc9-ol.js";import{a}from"./Asmr.module-CdWlioTY.js";import{r as i}from"./Radio.module-DGSZLpKM.js";import{p as L}from"./page.module-C1MlZzf3.js";const ee=300*1e3,le="https://cdn.listen.moe/covers/";function z(n){return n?n.startsWith("http")?n:`${le}${n}`:null}function oe(n){return n?z(n.albums[0]?.image)||z(n.artists[0]?.image)||z(n.sources[0]?.image):null}function U(n){return n?n.artists.map(s=>s.nameRomaji||s.name).join(", "):""}const ie=()=>{const n=x();return Z({...n.radio.stations.queryOptions(),staleTime:ee})},ce=n=>{const s=x();return Z({...s.radio.search.queryOptions({q:n}),enabled:n.trim().length>0,staleTime:ee})},X={song:null,lastPlayed:[],listeners:0,startTime:null},de=n=>{const[s,p]=h(X),[l,c]=h(!1),d=F(null);return y(()=>{if(p(X),c(!1),!n)return;let u=null,f=!1;try{u=new WebSocket(n)}catch{return}return u.onopen=()=>{f||c(!0)},u.onmessage=v=>{try{const m=JSON.parse(v.data);if(m.op===0){const N=m.d?.heartbeat||35e3;d.current&&window.clearInterval(d.current),d.current=window.setInterval(()=>{u&&u.readyState===WebSocket.OPEN&&u.send(JSON.stringify({op:9}))},N)}else m.op===1&&p({song:m.d?.song||null,lastPlayed:m.d?.lastPlayed||[],listeners:m.d?.listeners||0,startTime:m.d?.startTime||null})}catch{}},u.onclose=()=>{f||c(!1)},()=>{f=!0,d.current&&window.clearInterval(d.current),d.current=null,u&&u.close()}},[n]),{nowPlaying:s,connected:l}};function Y(n){if(!n)return"";const s=Math.max(0,Math.floor((Date.now()-new Date(n).getTime())/1e3)),p=Math.floor(s/60),l=s%60;return`${p}:${String(l).padStart(2,"0")}`}const me=({station:n,nowPlaying:s,connected:p,expanded:l,onStationStep:c,onExpandedChange:d,onClose:u})=>{const f=F(null),[v,m]=h(!1),[N,V]=h(!0),[S,I]=h(!1),[T,k]=h(!0),[A,E]=h(""),[b,D]=h(()=>{const t=parseFloat(localStorage.getItem("radioVolume")||"");return Number.isFinite(t)?t:1}),r=F(""),g=F(b);g.current=b,r.current||(r.current=typeof crypto<"u"&&"randomUUID"in crypto?crypto.randomUUID():`radio-${Date.now()}-${Math.random().toString(36).slice(2)}`);const w=s.song,C=oe(w)||n.favicon||null,[B,W]=h(!1);y(()=>{W(!1)},[C]);const M=B?null:C,q=!!M,R=w?`${U(w)} — ${w.title}`:n.name;y(()=>{const t=f.current;if(t)return m(!1),t.src=n.streamUrl,t.load(),t.volume=g.current,t.play().catch(()=>m(!1)),()=>{t.removeAttribute("src")}},[n.id,n.streamUrl]),y(()=>{const t=f.current;t&&(t.volume=b)},[b]),y(()=>{if(l)return document.body.classList.add("asmr-player-open"),()=>document.body.classList.remove("asmr-player-open")},[l]),y(()=>{l&&k(!0)},[l]);const te=t=>t?Math.max(0,Math.floor((Date.now()-new Date(t).getTime())/1e3)):0,H=se.useCallback(t=>{n&&(G.discord.radioPresence.mutate({title:R,stationLabel:n.name,isPlaying:t,thumbnail:M||"",currentTime:te(s.startTime),sessionId:r.current}).catch(()=>{}),G.discord.heartbeat.mutate({sessionId:r.current}).catch(()=>{}))},[n,R,M,s.startTime]);y(()=>{H(v)},[H,v,n.id,R,l]),y(()=>{if(!v)return;const t=window.setInterval(()=>H(!0),15e3);return()=>window.clearInterval(t)},[v,H]),y(()=>{const t=r.current,$=()=>re(t),O=()=>$(),_=()=>{document.visibilityState==="hidden"&&$()};return window.addEventListener("pagehide",O),window.addEventListener("beforeunload",O),document.addEventListener("visibilitychange",_),()=>{window.removeEventListener("pagehide",O),window.removeEventListener("beforeunload",O),document.removeEventListener("visibilitychange",_),$()}},[]),y(()=>{if(!s.startTime){E("");return}E(Y(s.startTime));const t=window.setInterval(()=>E(Y(s.startTime)),1e3);return()=>window.clearInterval(t)},[s.startTime,s.song?.id]),y(()=>{if(!l)return;const t=$=>{$.key==="Escape"&&(S?I(!1):d(!1))};return window.addEventListener("keydown",t),()=>window.removeEventListener("keydown",t)},[l,S,d]);const ae=()=>{const t=f.current;t&&(t.paused?(t.load(),t.play().catch(()=>{})):t.pause())},ne=e(P,{children:[e("div",{className:a.playerButtons,children:[e("button",{className:a.playerBtn,onClick:()=>c(-1),"aria-label":"Previous station",children:e(o,{name:"step-backward"})}),e("button",{className:`${a.playerBtn} ${a.playBtn}`,onClick:ae,"aria-label":v?"Pause":"Play",children:v?e(o,{name:"pause"}):e(o,{name:"play"})}),e("button",{className:a.playerBtn,onClick:()=>c(1),"aria-label":"Next station",children:e(o,{name:"step-forward"})})]}),e("div",{className:a.seekRow,children:[e("span",{className:i.liveBadge,children:[e(o,{name:"broadcast-tower",size:10})," LIVE"]}),A&&e("span",{className:a.timeLabel,children:A}),p&&s.listeners>0&&e("span",{className:a.timeLabel,children:[s.listeners," listening"]}),!p&&n.source==="listen.moe"&&e("span",{className:a.timeLabel,children:"connecting…"})]})]}),j=e(P,{children:[e("button",{className:`${a.playerBtn} ${a.playerToggle}`,onClick:()=>d(!l),"aria-label":l?"Minimize player":"Expand player",title:l?"Minimize to browse":"Expand",children:l?e(o,{name:"chevron-down"}):e(o,{name:"chevron-up"})}),e("div",{className:a.playerInfo,children:[e("p",{className:a.playerTitle,title:R,children:R}),e("p",{className:a.playerTrack,children:n.name})]}),e("div",{className:a.playerControls,children:ne}),e("div",{className:a.playerRight,children:[b===0?e(o,{name:"volume-mute",className:a.volumeIcon}):b<.5?e(o,{name:"volume-down",className:a.volumeIcon}):e(o,{name:"volume-up",className:a.volumeIcon}),e("input",{className:a.volumeBar,type:"range",min:0,max:1,step:.01,value:b,onChange:t=>{const $=parseFloat(t.target.value);D($),localStorage.setItem("radioVolume",String($))},style:{"--volume-percent":`${b*100}%`},"aria-label":"Volume"})]}),e("div",{className:a.playerActions,children:[l&&q&&e("button",{className:a.playerBtn,onClick:()=>V(t=>!t),"aria-label":N?"Hide art":"Show art",title:N?"Hide art":"Show art",children:N?e(o,{name:"eye-slash"}):e(o,{name:"image"})}),l&&s.lastPlayed.length>0&&e("button",{className:`${a.playerBtn} ${S?a.playerBtnActive:""}`,onClick:()=>I(t=>!t),"aria-label":S?"Hide history":"Show history",title:"Recently played",children:e(o,{name:"list-ol"})}),e("button",{className:a.playerBtn,onClick:u,"aria-label":"Close player",children:e(o,{name:"times"})})]})]}),Q=e("audio",{ref:f,preload:"none",onPlay:()=>m(!0),onPause:()=>m(!1)});return l?J(e(P,{children:[Q,e("div",{className:`${a.npOverlay} ${T?"":a.npOverlayControlsHidden}`,children:[e("div",{className:`${a.npStage} ${!N||!q?a.npStageBlank:""}`,onClick:()=>k(t=>!t),children:N&&q&&M&&e("div",{className:i.coverWrap,children:e("img",{src:M,alt:R,loading:"lazy",decoding:"async",draggable:!1,className:i.cover,onError:()=>W(!0)})})}),e("div",{className:`${a.playerBar} ${a.playerBarDocked} ${T?"":a.playerBarDockedHidden}`,onClick:t=>t.stopPropagation(),children:[j,S&&e(P,{children:[e("div",{className:a.npBackdrop,onClick:()=>I(!1)}),e("div",{className:a.chapterPanel,children:s.lastPlayed.map(t=>e("div",{className:a.chapterRow,children:e("span",{className:a.chapterRowLabel,title:`${U(t)} — ${t.title}`,children:[U(t)," — ",t.title]})},t.id))})]})]})]})]}),document.body):J(e(P,{children:[Q,e("div",{className:a.playerBar,children:j})]}),document.body)},Ne=()=>{y(()=>{document.title="Radio - dango"},[]);const[n,s]=h(""),[p,l]=h(""),[c,d]=h(null),[u,f]=h(!0),{data:v,isLoading:m}=ie(),{data:N,isLoading:V}=ce(p),S=K(()=>v?.stations||[],[v]),I=K(()=>N?.stations||[],[N]),T=p?I:S,{nowPlaying:k,connected:A}=de(c?.gateway),E=r=>{d(r),f(!0)},b=r=>{const g=T.length>0?T:S;if(g.length===0||!c)return;const w=g.findIndex(B=>B.id===c.id),C=g[(w<0?0:w+r+g.length)%g.length];d(C)},D=r=>{const g=c?.id===r.id,w=g&&k.song?`${U(k.song)} — ${k.song.title}`:null,C=[r.codec,r.bitrate?`${r.bitrate}k`:"",r.tags].filter(Boolean).join(" · ");return e("button",{className:`${i.stationRow} ${g?i.stationRowActive:""}`,onClick:()=>E(r),children:[r.favicon?e("img",{src:r.favicon,alt:"",className:i.stationThumb,loading:"lazy",decoding:"async",onError:B=>{B.target.style.display="none"}}):e("span",{className:i.stationThumbPlaceholder,children:e(o,{name:"broadcast-tower"})}),e("span",{className:i.stationMeta,children:[e("span",{className:i.stationName,children:r.name}),C&&e("span",{className:i.stationSub,children:C})]}),w&&e("span",{className:i.stationNowPlaying,children:w})]},r.id)};return e("div",{className:`${L.page} ${c?L.pageWithPlayer:""}`,children:[e("div",{className:L.header,children:[e("h1",{className:L.pageTitle,children:[e(o,{name:"broadcast-tower"})," Radio"]}),e("form",{className:L.searchForm,onSubmit:r=>{r.preventDefault(),l(n)},children:[e("input",{className:L.searchInput,value:n,onChange:r=>s(r.target.value),placeholder:"Search stations (anime, j-pop, k-pop…)","aria-label":"Search radio stations"}),e("button",{className:L.searchBtn,type:"submit","aria-label":"Search",children:e(o,{name:"search"})})]})]}),p?e(P,{children:[e("h2",{className:i.sectionTitle,children:["Results for “",p,"”"]}),V?e("p",{className:a.statusMsg,children:"Searching…"}):I.length===0?e("p",{className:a.statusMsg,children:"No stations found."}):e("div",{className:i.stationList,children:I.map(D)})]}):e(P,{children:[e("h2",{className:i.sectionTitle,children:"Featured"}),m?e("p",{className:a.statusMsg,children:"Loading…"}):e("div",{className:i.stationList,children:S.map(D)})]}),c&&e(me,{station:c,nowPlaying:k,connected:A,expanded:u,onStationStep:b,onExpandedChange:f,onClose:()=>d(null)})]})};export{Ne as default};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
._liveBadge_lkioe_1{display:inline-flex;align-items:center;gap:4px;background:var(--accent-dark);color:#fff;font-size:11px;font-weight:700;letter-spacing:.5px;border-radius:4px;padding:2px 8px;white-space:nowrap}._stationList_lkioe_15{display:flex;flex-direction:column;gap:8px}._stationRow_lkioe_21{display:flex;align-items:center;gap:12px;width:100%;text-align:left;background:#ffffff0a;border:1px solid transparent;border-radius:10px;padding:10px 12px;cursor:pointer;color:inherit}._stationRow_lkioe_21:hover{background:#ffffff14}._stationRowActive_lkioe_39{border-color:var(--accent);background:hsla(var(--accent-h),var(--accent-s),var(--accent-l),.12)}._stationThumb_lkioe_44{width:44px;height:44px;border-radius:8px;object-fit:cover;flex-shrink:0;background:#ffffff0f}._stationThumbPlaceholder_lkioe_53{width:44px;height:44px;border-radius:8px;flex-shrink:0;display:flex;align-items:center;justify-content:center;background:#ffffff0f;color:#fff9}._stationMeta_lkioe_65{flex:1;min-width:0}._stationName_lkioe_70{display:block;font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}._stationSub_lkioe_78{display:block;font-size:12px;opacity:.65;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}._stationNowPlaying_lkioe_87{font-size:12px;color:var(--accent-light);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:40%;text-align:right}._coverWrap_lkioe_97{display:flex;align-items:center;justify-content:center;width:100%;height:100%}._cover_lkioe_97{max-width:min(60vw,420px);max-height:60vh;border-radius:12px;box-shadow:0 8px 40px #00000080}._sectionTitle_lkioe_112{margin:20px 0 10px;font-size:15px;font-weight:700;opacity:.9}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
const t="_liveBadge_lkioe_1",o="_stationList_lkioe_15",i="_stationRow_lkioe_21",s="_stationRowActive_lkioe_39",e="_stationThumb_lkioe_44",a="_stationThumbPlaceholder_lkioe_53",n="_stationMeta_lkioe_65",_="_stationName_lkioe_70",c="_stationSub_lkioe_78",l="_stationNowPlaying_lkioe_87",r="_coverWrap_lkioe_97",k="_cover_lkioe_97",v="_sectionTitle_lkioe_112",b={liveBadge:t,stationList:o,stationRow:i,stationRowActive:s,stationThumb:e,stationThumbPlaceholder:a,stationMeta:n,stationName:_,stationSub:c,stationNowPlaying:l,coverWrap:r,cover:k,sectionTitle:v};export{b as r};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{u as e,S as g,L as m}from"./vendor-core-DfaOB3Rn.js";import{I as a}from"./vendor-icons-DrybRNxP.js";import{c as y}from"./useAsmr-CKOdHgol.js";import{p as s,a as k}from"./MediaCard-CpwsrXo5.js";import{a as L}from"./Asmr.module-CdWlioTY.js";import{u as f}from"./usePopup-DS9jMzp6.js";const P=({data:i,anchorRect:d,bookmarked:n,onToggleBookmark:p,onMouseEnter:h,onMouseLeave:u,onRequestClose:N})=>{const{data:c,isLoading:v}=y(i.rjCode||null),r=c?.tracks.length??0,l=r>0,t=!v;return e(k,{anchorRect:d,onMouseEnter:h,onMouseLeave:u,onRequestClose:N,children:[e("div",{className:s.header,children:[i.thumbnail&&e("img",{src:i.thumbnail,alt:i.title,loading:"lazy",decoding:"async",className:L.popupCover}),e("div",{className:s.title,children:i.title}),e("div",{className:s.synopsis,children:i.rjCode})]}),e("div",{className:s.body,children:[e("div",{className:s.metaRow,children:[e("div",{className:s.metaItem,children:[e(a,{name:"headphones",size:14}),e("span",{children:t?l?e(g,{children:[r," track",r===1?"":"s"]}):"No tracks":"Loading tracks…"})]}),i.rating&&e("div",{className:s.metaItem,children:e("span",{children:i.rating.toUpperCase()})}),i.isAdult&&e("div",{className:s.metaItem,children:e("span",{children:"18+"})}),i.progressLabel&&e("div",{className:s.metaItem,children:[e(a,{name:"bookmark",size:12}),e("span",{children:i.progressLabel})]})]}),e("div",{className:s.synopsis,children:!t&&!c?.description?"Loading description…":c?.description??"No description available."})]}),e("div",{className:s.footer,children:[e("div",{className:s.primaryAction,children:l?e(m,{to:i.listenTarget,className:s.watchBtn,children:[e(a,{name:"play",size:14}),"Listen now"]}):e("span",{className:s.watchBtn,"aria-disabled":"true",style:{opacity:.5,pointerEvents:"none"},children:[e(a,{name:"play",size:14}),t?"No tracks":"Loading…"]})}),e("div",{className:s.secondaryActions,children:[e("button",{className:`${s.watchlistBtn} ${n?s.active:""}`,onClick:o=>{o.preventDefault(),o.stopPropagation(),p()},children:[n?e(a,{name:"check",size:12}):e(a,{name:"plus",size:12}),e("span",{children:n?"Remove":"Bookmark"})]}),e(m,{to:i.listenTarget,className:s.detailsBtn,children:"Details"})]})]})]})},S=()=>f();export{P as A,S as u};
|
package/server/dist/app-auth.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import i from"crypto";import u from"fs";import g from"path";import{CONFIG as a}from"./config.js";const f="dango_lan_auth",A=720*60*60*1e3;function p(){return g.join(a.ROOT,"lan_sessions.json")}function l(){try{if(!u.existsSync(p()))return{};const t=u.readFileSync(p(),"utf8"),n=JSON.parse(t),e=Date.now();let r=!1;for(const[s,o]of Object.entries(n))(typeof o!="number"||o<e)&&(delete n[s],r=!0);return r&&c(n),n}catch{return{}}}function c(t){try{u.mkdirSync(a.ROOT,{recursive:!0}),u.writeFileSync(p(),JSON.stringify(t))}catch{}}function d(){return process.env.APP_PASSWORD_HASH||a.APP_PASSWORD_HASH||""}function H(){return!!d()}function x(t){const n=i.randomBytes(16).toString("hex"),e=i.scryptSync(t,n,32).toString("hex");return`scrypt:${n}:${e}`}function O(t){const n=d();if(!n)return!1;const e=n.split(":");if(e.length!==3||e[0]!=="scrypt")return!1;const[,r,s]=e;try{const o=i.scryptSync(t,r,32),S=Buffer.from(s,"hex");return o.length!==S.length?!1:i.timingSafeEqual(o,S)}catch{return!1}}async function w(t){const{updateEnvFile:n}=await import("./utils/env.utils.js");if(!t){await n({APP_PASSWORD_HASH:""}),a.APP_PASSWORD_HASH="";return}const e=x(t);await n({APP_PASSWORD_HASH:e}),a.APP_PASSWORD_HASH=e}function R(){const t=i.randomBytes(32).toString("hex"),n=Date.now()+A,e=l();return e[t]=n,c(e),{token:t,expiry:n}}function b(t){if(!t)return!1;const n=l(),e=n[t];return e?e<Date.now()?(delete n[t],c(n),!1):!0:!1}function L(t){if(!t)return;const n=l();n[t]&&(delete n[t],c(n))}function D(){c({})}function m(t){return t?t.startsWith("::ffff:")?t.slice(7):t:""}function W(t){const n=m(t);return n==="127.0.0.1"||n==="::1"}function v(t,n){if(t&&t.toLowerCase().startsWith("bearer "))return t.slice(7).trim()||null;if(n){const e=n.split(";");for(const r of e){const s=r.indexOf("=");if(s===-1)continue;if(r.slice(0,s).trim()===f)return decodeURIComponent(r.slice(s+1).trim())||null}}return null}const C=new Set(["/api/auth/app-status","/api/auth/app-login","/api/auth/app-logout","/api/internal/shutdown"]);function I(t,n){const e=Math.max(1,Math.floor((n-Date.now())/1e3));return`${f}=${encodeURIComponent(t)}; HttpOnly; Path=/; Max-Age=${e}; SameSite=Lax`}function T(){return`${f}=; HttpOnly; Path=/; Max-Age=0; SameSite=Lax`}export{f as LAN_AUTH_COOKIE,C as LAN_AUTH_PUBLIC_PATHS,I as buildLanCookie,D as clearAllLanSessions,T as clearLanCookie,R as createLanSession,d as getAppPasswordHash,v as getTokenFromHeaders,H as hasAppPassword,x as hashAppPassword,W as isLoopbackIp,m as normalizeClientIp,L as revokeLanSession,w as setAppPassword,b as validateLanSession,O as verifyAppPassword};
|
package/server/dist/app-hono.js
DELETED
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
import{Hono as N}from"hono";import{compress as L}from"hono/compress";import{getConnInfo as O}from"@hono/node-server/conninfo";import M from"path";import v from"fs";import{XMLParser as F}from"fast-xml-parser";import{CONFIG as k}from"./config.js";import{isAllowedOrigin as D}from"./utils/security.utils.js";import{requestContext as B}from"./utils/request-context.js";import m from"./logger.js";import{LAN_AUTH_PUBLIC_PATHS as W,buildLanCookie as U,clearAllLanSessions as V,clearLanCookie as X,createLanSession as $,getTokenFromHeaders as T,hasAppPassword as A,isLoopbackIp as _,revokeLanSession as z,setAppPassword as J,validateLanSession as E,verifyAppPassword as G}from"./app-auth.js";import{importMangaFromMalXmlItems as Y}from"./lib/tracker/manga-sync.service.js";import{LibraryRepository as b}from"./repositories/library.repository.js";import{performAsmrWriteTransactionAsync as K,performMangaWriteTransactionAsync as Q,performTvWriteTransactionAsync as Z,performWriteTransactionAsync as ee,KIND_SYNC as P,setKindManifestVersion as re}from"./sync.js";import{ANIME_SYNC_TABLES as te,ASMR_SYNC_TABLES as ae,MANGA_SYNC_TABLES as oe,TV_SYNC_TABLES as ne,exportTables as S,importTables as se,normalizePayload as ie,readPayloadVersion as de}from"./sync-payload.js";import{getMalImportStatus as ce,prepareMalImport as le,executeMalImport as pe}from"./lib/mal-import.js";import{registerMusic as ue}from"./hono/music.js";import{registerAuth as fe}from"./hono/auth.js";import{registerTv as me}from"./hono/tv.js";import{registerProxy as ge}from"./hono/proxy.js";import{fetchRequestHandler as he}from"@trpc/server/adapters/fetch";import{appRouter as ye}from"./trpc/router.js";import{buildTrpcContext as be}from"./trpc/context.js";const we=new F({isArray:a=>a==="manga",parseTagValue:!1,trimValues:!0});function q(a){return Array.isArray(a)?String(a[0]??""):String(a??"")}function ve(a){switch(a){case"Plan to Read":return"Planned";case"On Hold":return"On-Hold";case"Reading":case"Completed":case"Dropped":return a;default:return"Planned"}}function ke(a){try{return O(a).remote.address}catch{return a.req.header("x-dango-client-ip")??void 0}}const H="x-dango-not-ready";function C(a,l){return a.header(H,"1"),typeof l=="string"?a.text(l,503):a.json(l,503)}async function Ce(a,l){const s=process.hrtime.bigint(),p=new URL(a.req.url),c=`${p.pathname}${p.search}`;await l();const g=Math.round(Number(process.hrtime.bigint()-s)/1e6*10)/10,n=a.res.status,o={method:a.req.method,url:c,status:n,ms:g};n>=500&&!a.res.headers.get(H)?m.warn(o,"request failed"):c.startsWith("/api/proxy")||m.trace(`${a.req.method} ${c} ${n} ${g}ms`)}async function Ae(a,l){const s=new Map,p=a.req.header("x-animepahe-ua");p&&s.set("ua",p);const c=a.req.header("x-animepahe-cookie");c&&s.set("cookie",c);const g=a.req.header("x-jasmr-ua");g&&s.set("jasmr_ua",g);const n=a.req.header("x-jasmr-cookie");return n&&s.set("jasmr_cookie",n),B.run(s,()=>l())}async function je(a,l,s){let p;try{p=JSON.parse(s.toString("utf-8"))}catch{return a.json({error:"Invalid backup file."},400)}const c=p?.databases;if(!c||typeof c!="object")return a.json({error:"Invalid backup file: missing databases."},400);const g={anime:l.db,manga:l.mangaDb,tv:l.tvDb,asmr:l.asmrDb},o=Object.keys(P).map(r=>({key:r,db:g[r],tables:P[r].tables,libraryTables:P[r].libraryTables,backupName:P[r].backupName,setVersion:t=>re(r,t)})).filter(r=>c[r.key]!==void 0);if(o.length===0)return a.json({error:"Invalid backup file: no databases found."},400);const h=new Map;try{for(const r of o)h.set(r.key,ie(c[r.key],r.tables,`backup ${r.key}`))}catch(r){return m.warn({err:r},"Multi-database restore rejected: invalid payload"),a.json({error:"Invalid backup file."},400)}const e=[];try{for(const r of o){const t=h.get(r.key);t&&(se(r.db,r.tables,t,{libraryTables:r.libraryTables,backupName:r.backupName}),await r.setVersion(de(t)),e.push(r.key))}}catch(r){return m.error({err:r,restored:e},"Multi-database restore failed partway"),a.json({error:"Restore failed partway.",restored:e},500)}return m.warn({restored:e},"Databases restored from backup file by user request"),a.json({success:!0,message:"Databases restored.",restored:e})}function De(a,l,s,p){const c=k.IS_DEV?k.DB_NAME_DEV:k.DB_NAME_PROD,g=M.join(k.ROOT,"restore_temp.db"),n=M.join(k.ROOT,c);try{v.writeFileSync(g,s)}catch(h){return m.error({err:h},"Failed to stage legacy database restore file"),Promise.resolve(a.json({error:"Failed to stage restore file."},500))}const o=l.db;return new Promise(h=>{o.close(e=>{if(e){h(a.json({error:"Failed to close database."},500));return}try{o.checkpoint()}catch(r){m.warn({err:r},"WAL checkpoint failed")}try{v.existsSync(`${n}-wal`)&&v.unlinkSync(`${n}-wal`),v.existsSync(`${n}-shm`)&&v.unlinkSync(`${n}-shm`)}catch(r){m.warn({err:r},"Failed to clean up WAL files")}v.rename(g,n,async r=>{if(r){try{p.setDb(await p.initializeDatabase(n))}catch(t){m.error({err:t},"Failed to reopen DB after rename failure")}h(a.json({error:"Failed to replace database file."},500));return}try{p.setDb(await p.initializeDatabase(n)),h(a.json({success:!0,message:"Database restored."}))}catch(t){m.error({err:t},"Failed to initialize restored database"),h(a.json({error:"Failed to initialize restored database."},500))}})})})}function Ye(a,l,s,p,c,g,n){const o=new N;o.use(Ce),o.use(Ae),o.get("/api/health",e=>{const r=a();return r.shuttingDown?C(e,{status:"shutting-down",ready:!1}):r.dbReady?e.json({status:"ok",ready:!0}):C(e,{status:"starting",ready:!1})}),o.use(async(e,r)=>{const t=l();if(t.shuttingDown)return C(e,"Server is shutting down...");if(!t.dbsReady)return C(e,"Database initializing...");if(t.bootSyncing&&e.req.method!=="GET"&&e.req.method!=="HEAD"&&e.req.method!=="OPTIONS")return C(e,"Sync in progress...");await r()});const h=L({threshold:1024});return o.use(async(e,r)=>{if(e.req.header("x-no-compression")){await r();return}await h(e,r)}),o.use(async(e,r)=>{const t=e.req.header("origin");if(e.req.method==="OPTIONS"){if(!t||!D(t)){await r();return}const i=e.req.header("access-control-request-headers");return new Response(null,{status:204,headers:{"Access-Control-Allow-Origin":t,"Access-Control-Allow-Credentials":"true","Access-Control-Allow-Methods":"GET,HEAD,PUT,PATCH,POST,DELETE",...i?{"Access-Control-Allow-Headers":i}:{},Vary:"Origin"}})}(!t||D(t))&&(t&&e.header("Access-Control-Allow-Origin",t),e.header("Access-Control-Allow-Credentials","true"),e.header("Vary","Origin")),await r()}),o.use(async(e,r)=>{const t=e.req.method.toUpperCase();if(!["POST","PUT","DELETE","PATCH"].includes(t)){await r();return}if(e.req.header("sec-fetch-site")==="cross-site"){const d=e.req.header("origin");if(!D(d))return e.json({error:"Forbidden: cross-site request rejected"},403)}const y=e.req.header("origin");if(y&&!D(y))return e.json({error:"Forbidden: cross-origin request rejected"},403);await r()}),o.get("/api/auth/app-status",e=>{const r=!A()||E(T(e.req.header("authorization"),e.req.header("cookie")));return e.json({hasPassword:A(),isAuthenticated:r,port:k.PORT})}),o.post("/api/auth/app-login",async e=>{const r=await e.req.json().catch(()=>{}),t=String(r?.password||"");if(!A())return e.json({success:!0});if(!t)return e.json({error:"Password required"},400);if(!G(t))return e.json({error:"Invalid password"},401);const i=$();return e.header("Set-Cookie",U(i.token,i.expiry)),e.json({success:!0})}),o.post("/api/auth/app-logout",e=>(z(T(e.req.header("authorization"),e.req.header("cookie"))),e.header("Set-Cookie",X()),e.json({success:!0}))),o.post("/api/auth/app-setup",async e=>{if(!_(e.req.header("x-dango-client-ip")))return e.json({error:"Forbidden"},403);const r=await e.req.json().catch(()=>{}),t=String(r?.password||"");try{return await J(t),V(),e.json({success:!0,hasPassword:A()})}catch{return e.json({error:"Failed to update password"},500)}}),o.use(async(e,r)=>{const t=e.req.path;if(!t.startsWith("/api/")){await r();return}if(W.has(t)){await r();return}if(t.startsWith("/api/internal/")&&_(ke(e))){await r();return}if(!A()){await r();return}if(E(T(e.req.header("authorization"),e.req.header("cookie")))){await r();return}return e.json({error:"LAN_AUTH_REQUIRED"},401)}),o.all("/api/trpc/*",e=>he({endpoint:"/api/trpc",req:e.req.raw,router:ye,createContext:()=>be({...s(),apiCache:n.getApiCache(),getTvProvider:n.getTvProvider,getMangaProvider:n.getMangaProvider,getJasmr:n.getJasmr,getProviders:n.getProviders,getCatalog:c.getCatalog},e.req.header("authorization"),e.req.header("cookie"))})),o.post("/api/import/mal-xml",async e=>{const r=s(),t=await e.req.parseBody().catch(()=>({})),i=t.xmlfile,y=i instanceof File?Buffer.from(await i.arrayBuffer()):void 0,d=le(y,{erase:t.erase,useOfflineDb:t.useOfflineDb,skipFallback:t.skipFallback});if(!d.ok)return e.json(d.body,d.status);const u=new TransformStream,f=u.writable.getWriter(),x=new TextEncoder;let j=!1;e.req.raw.signal.addEventListener("abort",()=>{j=!0},{once:!0});const R=(w,I)=>{j||f.write(x.encode(`event: ${w}
|
|
2
|
-
data: ${JSON.stringify(I)}
|
|
3
|
-
|
|
4
|
-
`)).catch(()=>{})};return pe(r.db,d.import,R).finally(()=>{f.close().catch(()=>{})}),new Response(u.readable,{headers:{"Content-Type":"text/event-stream","Cache-Control":"no-cache",Connection:"keep-alive"}})}),o.get("/api/providers",e=>(e.header("Cache-Control","public, max-age=300"),e.json(c.getCatalog()))),o.post("/api/providers/refresh",async e=>{try{return e.json(await c.refresh())}catch(r){return e.json({error:r.message||"refresh failed"},500)}}),o.get("/api/tracker/anilist/callback",e=>e.html(`<!doctype html>
|
|
5
|
-
<html><head><meta charset="utf-8"><title>AniList \u2014 completing login</title></head>
|
|
6
|
-
<body style="font-family:system-ui;padding:24px;background:#0b1426;color:#e5eefc"><p>Completing AniList login\u2026</p>
|
|
7
|
-
<script>
|
|
8
|
-
(function(){
|
|
9
|
-
var qs = new URLSearchParams(location.search);
|
|
10
|
-
var state = qs.get('state');
|
|
11
|
-
var hash = location.hash || '';
|
|
12
|
-
var hp = new URLSearchParams(hash.slice(1));
|
|
13
|
-
if (!state) state = hp.get('state');
|
|
14
|
-
var token = hp.get('access_token');
|
|
15
|
-
var error = hp.get('error') || qs.get('error');
|
|
16
|
-
var frontend = location.origin + '/trackers';
|
|
17
|
-
if (state) {
|
|
18
|
-
try {
|
|
19
|
-
var candidate = decodeURIComponent(state);
|
|
20
|
-
if (candidate.startsWith('/') && !candidate.startsWith('//')) {
|
|
21
|
-
frontend = location.origin + candidate;
|
|
22
|
-
} else {
|
|
23
|
-
var parsed = new URL(candidate, location.origin);
|
|
24
|
-
if (parsed.origin === location.origin) {
|
|
25
|
-
frontend = parsed.href;
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
} catch(e) {}
|
|
29
|
-
}
|
|
30
|
-
if (frontend.indexOf('/api/tracker/anilist/callback') !== -1) {
|
|
31
|
-
frontend = location.origin + '/trackers';
|
|
32
|
-
}
|
|
33
|
-
if (error) {
|
|
34
|
-
location.replace(frontend + (frontend.indexOf('?') !== -1 ? '&' : '?') + 'anilist=error&reason=' + encodeURIComponent(error));
|
|
35
|
-
return;
|
|
36
|
-
}
|
|
37
|
-
if (token) {
|
|
38
|
-
location.replace(frontend + hash);
|
|
39
|
-
return;
|
|
40
|
-
}
|
|
41
|
-
var code = qs.get('code');
|
|
42
|
-
if (code) {
|
|
43
|
-
location.replace(frontend + (frontend.indexOf('?') !== -1 ? '&' : '?') + 'anilist=error&reason=code_flow_removed');
|
|
44
|
-
return;
|
|
45
|
-
}
|
|
46
|
-
location.replace(frontend + (frontend.indexOf('?') !== -1 ? '&' : '?') + 'anilist=error&reason=no_token');
|
|
47
|
-
})();
|
|
48
|
-
</script></body></html>`)),o.post("/api/import/mal-xml-manga",async e=>{const r=await e.req.parseBody().catch(()=>({})),t=r.xmlfile;if(!(t instanceof File))return e.json({error:"No file"},400);const i=r.erase,y=i===!0||i==="true"||i==="1";try{const d=s();if(!d.mangaDb||d.mangaDb.isClosedCheck())return C(e,{error:"Manga database is not ready"});let u;try{u=we.parse(Buffer.from(await t.arrayBuffer()).toString())}catch{return e.json({error:"Invalid XML"},400)}const f=u?.myanimelist?.manga||[];if(f.length===0)return e.json({error:"No manga found in XML"},400);const x=f.map(w=>({malId:parseInt(q(w.series_mangadb_id),10)||0,title:q(w.series_title),status:ve(q(w.my_status)),chapters:Math.max(parseInt(q(w.my_read_chapters),10)||0,0)})),{imported:j,skipped:R}=await Y(d.db,d.mangaDb,x,y);return e.json({success:!0,imported:j,skipped:R})}catch(d){const u=d instanceof Error?d.message:"Import failed";return e.json({error:u},500)}}),o.get("/api/backup-db",e=>{try{const r=s(),t={version:1,exportedAt:new Date().toISOString(),databases:{anime:S(r.db,te),manga:S(r.mangaDb,oe),tv:S(r.tvDb,ne),asmr:S(r.asmrDb,ae)}};return e.header("Content-Disposition",'attachment; filename="dango-backup.json"'),e.json(t)}catch(r){return m.error({err:r},"Multi-database backup failed"),e.json({error:"Backup failed"},500)}}),o.post("/api/database/clear",async e=>{if((await e.req.json().catch(()=>{}))?.confirm!==!0)return e.json({error:"Confirmation required"},400);if(ce().running)return e.json({error:"An import is in progress"},409);try{const t=s(),i=await b.countAll(t.db),y=await b.countManga(t.mangaDb),d=await b.countTv(t.tvDb),u=await b.countAsmr(t.asmrDb);return await ee(t.db,async f=>{await b.clearAll(f)}),await Q(t.mangaDb,async f=>{await b.clearManga(f)}),await Z(t.tvDb,async f=>{await b.clearTv(f)}),await K(t.asmrDb,async f=>{await b.clearAsmr(f)}),m.warn({before:i,beforeManga:y,beforeTv:d,beforeAsmr:u},"Library database cleared by user request"),e.json({success:!0,deleted:i,deletedManga:y,deletedTv:d,deletedAsmr:u})}catch{return e.json({error:"DB error"},500)}}),o.post("/api/restore-db",async e=>{const r=s();let t,i="";try{const u=(await e.req.parseBody()).dbfile;u instanceof File&&(t=Buffer.from(await u.arrayBuffer()),i=u.name||"")}catch{return e.json({error:"No file uploaded."},400)}if(!t)return e.json({error:"No file uploaded."},400);const y=t.slice(0,16).toString("utf-8").trimStart();return i.endsWith(".json")||y.startsWith("{")?je(e,r,t):De(e,r,t,p)}),ue(o),fe(o,s,g),me(o,n.getTvProvider),ge(o,n.getProxyRules),o.onError((e,r)=>{m.error({err:e,url:r.req.path,method:r.req.method},"Unhandled error");const t=e.status||500;return r.json({error:e.message||"Internal Server Error",status:t},t)}),o}export{Ye as createHonoApp,ke as getHonoClientIp,Ae as honoRequestContextMiddleware,Ce as honoRequestLogger};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{drizzle as r}from"drizzle-orm/node-sqlite";function p(e){return r({client:e.getClient()})}const a=new WeakMap;function i(e){let t=a.get(e);return t||(t=p(e),a.set(e,t)),t}async function o(e,t){await e.transact(t)}export{p as createDrizzle,i as getDrizzle,o as runTx};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{sql as d}from"drizzle-orm";import{integer as o,primaryKey as i,real as n,sqliteTable as s,text as e}from"drizzle-orm/sqlite-core";const a=s("watched_episodes",{showId:e("showId").notNull(),episodeNumber:e("episodeNumber").notNull(),watchedAt:e("watchedAt"),currentTime:n("currentTime"),duration:n("duration")},t=>[i({columns:[t.showId,t.episodeNumber]})]),r=s("watchlist",{id:e("id").notNull(),name:e("name"),thumbnail:e("thumbnail"),status:e("status"),nativeName:e("nativeName"),englishName:e("englishName"),type:e("type")},t=>[i({columns:[t.id]})]),m=s("shows_meta",{id:e("id").notNull(),name:e("name"),thumbnail:e("thumbnail"),nativeName:e("nativeName"),englishName:e("englishName"),episodeCount:o("episodeCount"),status:e("status"),genres:e("genres"),popularityScore:o("popularityScore"),type:e("type"),anilistId:o("anilistId"),isAdult:o("isAdult"),episodeDuration:o("episodeDuration")},t=>[i({columns:[t.id]})]),p=s("settings",{key:e("key").notNull(),value:e("value")},t=>[i({columns:[t.key]})]),N=s("queue",{id:o("id"),showId:e("showId").notNull(),episodeNumber:e("episodeNumber").notNull(),queueOrder:o("queue_order").notNull()}),c=s("discovered_notifications",{showId:e("showId").notNull(),episodeNumber:e("episodeNumber").notNull(),discoveredAt:e("discoveredAt").default(d`CURRENT_TIMESTAMP`)},t=>[i({columns:[t.showId,t.episodeNumber]})]),h=s("dismissed_notifications",{showId:e("showId").notNull(),episodeNumber:e("episodeNumber").notNull(),dismissedAt:e("dismissedAt").default(d`CURRENT_TIMESTAMP`)},t=>[i({columns:[t.showId,t.episodeNumber]})]),w=s("temp_show_ids",{id:e("id").notNull(),provider:e("provider").notNull(),nativeId:e("nativeId").notNull(),title:e("title").notNull(),thumbnail:e("thumbnail"),createdAt:o("createdAt").notNull()},t=>[i({columns:[t.id]})]);export{c as discoveredNotifications,h as dismissedNotifications,N as queue,p as settings,m as showsMeta,w as tempShowIds,a as watchedEpisodes,r as watchlist};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{integer as t,primaryKey as d,real as a,sqliteTable as l,text as r}from"drizzle-orm/sqlite-core";const i=l("asmr_progress",{workId:r("workId").notNull(),trackIndex:t("trackIndex").notNull(),trackLabel:r("trackLabel"),currentTime:a("currentTime"),duration:a("duration"),updatedAt:t("updatedAt"),title:r("title"),thumbnail:r("thumbnail"),rjCode:r("rjCode"),isAdult:t("isAdult")},e=>[d({columns:[e.workId,e.trackIndex]})]);export{i as asmrProgress};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{integer as a,primaryKey as r,sqliteTable as l,text as t}from"drizzle-orm/sqlite-core";const i=l("manga_library",{id:t("id").notNull(),provider:t("provider").notNull(),mangaId:t("mangaId").notNull(),title:t("title"),cover:t("cover"),status:t("status"),author:t("author"),altTitle:t("altTitle"),contentRating:t("contentRating"),lastChapterId:t("lastChapterId"),lastChapterNumber:t("lastChapterNumber"),lastPage:a("lastPage"),updatedAt:a("updatedAt"),anilistId:a("anilistId"),anilistIdSource:t("anilistIdSource")},e=>[r({columns:[e.id]})]),o=l("manga_progress",{mangaId:t("mangaId").notNull(),chapterId:t("chapterId").notNull(),chapterNumber:t("chapterNumber"),page:a("page"),pageCount:a("pageCount"),updatedAt:a("updatedAt"),title:t("title"),cover:t("cover"),provider:t("provider"),altTitle:t("altTitle"),contentRating:t("contentRating")},e=>[r({columns:[e.mangaId,e.chapterId]})]);export{i as mangaLibrary,o as mangaProgress};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{integer as t,primaryKey as r,real as o,sqliteTable as a,text as e}from"drizzle-orm/sqlite-core";const s=a("tv_progress",{mediaId:e("mediaId").notNull(),season:t("season").notNull(),episode:t("episode").notNull(),currentTime:o("currentTime"),duration:o("duration"),updatedAt:t("updatedAt"),title:e("title"),poster:e("poster"),backdrop:e("backdrop"),year:e("year"),overview:e("overview"),tmdbId:t("tmdbId"),mediaType:e("mediaType"),adult:t("adult"),completed:t("completed")},d=>[r({columns:[d.mediaId,d.season,d.episode]})]);export{s as tvProgress};
|
package/server/dist/db.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{DatabaseSync as h}from"node:sqlite";import{AsyncLocalStorage as c}from"node:async_hooks";import s from"fs";import l from"path";import n from"./logger.js";const a=new c;class o{db;isClosed=!1;statementCache=new Map;txDepth=0;txQueue=Promise.resolve();constructor(t,e){this.db=e}static async create(t){try{const e=l.dirname(t);s.existsSync(e)||s.mkdirSync(e,{recursive:!0});const r=new h(t);return new o(t,r)}catch(e){throw n.error({err:e},`Failed to initialize database at ${t}`),e}}getClient(){return this.db}configure(t,e){t==="busyTimeout"&&this.db.exec(`PRAGMA busy_timeout = ${e}`)}serialize(t){const e=this.txDepth===0,r=this.txDepth;this.run(e?"BEGIN IMMEDIATE":`SAVEPOINT dango_sp_${r}`),this.txDepth=r+1;try{a.run(this,t)}catch(i){this.txDepth=r;try{this.run(e?"ROLLBACK":`ROLLBACK TO SAVEPOINT dango_sp_${r}`)}catch{}throw i}this.txDepth=r,this.run(e?"COMMIT":`RELEASE SAVEPOINT dango_sp_${r}`)}acquireTxQueue(){const t=this.txQueue;let e;const r=new Promise(i=>{e=i});return this.txQueue=t.then(()=>r),t.then(()=>e)}async transact(t){if(a.getStore()===this){const r=this.txDepth;this.run(`SAVEPOINT dango_sp_${r}`),this.txDepth=r+1;try{await t(this)}catch(i){this.txDepth=r;try{this.run(`ROLLBACK TO SAVEPOINT dango_sp_${r}`)}catch{}throw i}this.txDepth=r,this.run(`RELEASE SAVEPOINT dango_sp_${r}`);return}const e=await this.acquireTxQueue();try{await a.run(this,async()=>{this.run("BEGIN IMMEDIATE"),this.txDepth=1;try{await t(this)}catch(r){this.txDepth=0;try{this.run("ROLLBACK")}catch{}throw r}this.txDepth=0,this.run("COMMIT")})}finally{e()}}close(t){if(this.isClosed){t&&t(null);return}try{this.isClosed=!0,this.statementCache.clear(),this.db.close(),t&&t(null)}catch(e){n.error({err:e},"Error during database close"),t&&t(e)}}isClosedCheck(){return this.isClosed}getPreparedStatement(t){let e=this.statementCache.get(t);return e||(this.statementCache.size>100&&this.statementCache.clear(),e=this.db.prepare(t),this.statementCache.set(t,e)),e}run(t,e=[]){if(this.isClosed)throw new Error("Database is closed");const r=this.getPreparedStatement(t);e.length>0?r.run(...e):r.run()}get(t,e=[]){if(this.isClosed)throw new Error("Database is closed");const r=this.getPreparedStatement(t);return e.length>0?r.get(...e):r.get()}all(t,e=[]){if(this.isClosed)throw new Error("Database is closed");const r=this.getPreparedStatement(t);return e.length>0?r.all(...e):r.all()}prepare(t){const e=this.getPreparedStatement(t);return{run:(...r)=>{e.run(...r)},all:()=>e.all(),get:()=>e.get(),finalize:()=>{}}}backup(t){try{s.existsSync(t)&&s.rmSync(t,{force:!0}),this.db.exec(`VACUUM INTO '${t}'`)}catch(e){throw n.error({err:e,backupPath:t},"Database backup failed via VACUUM INTO"),e}}checkpoint(){try{this.db.exec("PRAGMA wal_checkpoint(TRUNCATE)")}catch(t){throw n.error({err:t},"Database WAL checkpoint failed"),t}}}export{o as DatabaseWrapper};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import o from"ws";import d from"./logger.js";import{CONFIG as r}from"./config.js";import m from"dotenv";const a=d.child({module:"DiscordGateway"});class p{gateway=null;heartbeat=null;token=null;reconnectTimeout=null;lastActivity=null;isEnabled=!1;RECONNECT_DELAY=15e3;externalAssetCache=new Map;setEnabled(e){this.isEnabled=e,e?(this.loadToken(),this.connect()):this.disconnect()}get serviceEnabled(){return this.isEnabled&&!!this.token&&this.token.length>30}hasToken(){return!!this.token&&this.token.length>30}loadToken(){this.token=process.env.DISCORD_GATEWAY_TOKEN||null}reloadToken(){m.config({path:r.ENV_PATH,override:!0}),this.loadToken(),this.serviceEnabled&&(this.disconnect(),this.connect())}async getExternalAsset(e){if(!r.DISCORD_CLIENT_ID||!this.token)return null;const n=this.externalAssetCache.get(e);if(n)return n;try{const s=await fetch(`https://discord.com/api/v9/applications/${r.DISCORD_CLIENT_ID}/external-assets`,{method:"POST",headers:{Authorization:this.token,"Content-Type":"application/json"},body:JSON.stringify({urls:[e]})}),t=await s.text();if(!s.ok)return a.warn(`external-assets proxy failed ${s.status}: ${t.slice(0,200)}`),null;const i=JSON.parse(t);if(!i[0]?.external_asset_path)return null;const l=`mp:${i[0].external_asset_path}`;return this.externalAssetCache.set(e,l),l}catch(s){return a.warn({err:s},"external-assets proxy error"),null}}resolveImageUrl(e){const n=t=>!t.startsWith("https://")||t.includes("localhost")||t.includes("127.0.0.1")?!1:t.includes("s4.anilist.co")||t.includes("anilistcdn")?!0:!["youtube-anime.com","animepahe","animeya.cc","gogocdn.net","weeabo0.xyz","japaneseasmr.com"].some(l=>t.includes(l));let s=e.thumbnail;if(s&&s.includes("/api/image-proxy")){const t=s.match(/url=([^&]+)/);if(t)try{s=decodeURIComponent(t[1])}catch(i){}}if(s&&e.providerName!=="AnimePahe"&&n(s))return s;if(e.thumbnails){const t=e.thumbnails.find(i=>i&&(i.includes("s4.anilist.co")||i.includes("anilistcdn")));if(t)return t}return s&&n(s)?s:null}async updatePresence(e){if(this.lastActivity=e,!this.isEnabled||!this.gateway||this.gateway.readyState!==o.OPEN)return;const n="dango",s=e.title,t=e.stateLine?e.stateLine:`Episode ${e.episode}${e.totalEpisodes?`/${e.totalEpisodes}`:""}`,i=e.providerName==="ASMR"||e.providerName==="Radio"||e.providerName==="Music"?2:3,l=this.resolveImageUrl(e);let c;if(l){if(r.DISCORD_CLIENT_ID){const g=await this.getExternalAsset(l);g?c={large_image:g,large_text:e.title.slice(0,128),small_image:"small",small_text:"dango"}:c={large_image:"logo",large_text:e.title.slice(0,128)}}}else r.DISCORD_CLIENT_ID&&(c={large_image:"logo",large_text:e.title.slice(0,128)});const h={since:null,activities:[{name:n,type:i,state:e.isPlaying?t:`${t} (Paused)`,details:s,...r.DISCORD_CLIENT_ID?{application_id:r.DISCORD_CLIENT_ID}:{},...c?{assets:c}:{},buttons:["Learn More"],metadata:{button_urls:["https://github.com/serifpersia/dango"]},status_display_type:2}],status:"online",afk:!1},u=Date.now();e.isPlaying&&(e.currentTime&&e.currentTime>0&&(h.since=u-Math.round(e.currentTime*1e3)),e.duration&&e.duration>e.currentTime&&(h.activities[0].timestamps={start:u-Math.round(e.currentTime*1e3),end:u+Math.round((e.duration-e.currentTime)*1e3)}));try{this.gateway.send(JSON.stringify({op:3,d:h}))}catch(g){a.error({err:g},"Failed to send gateway presence update")}}setIdleStatus(e){if(!this.isEnabled||!this.gateway||this.gateway.readyState!==o.OPEN)return;const s={home:{details:"Home",state:"Browsing anime"},search:{details:"Search",state:"Exploring titles..."},watchlist:{details:"Watchlist",state:"Reviewing the watchlist"},anime:{details:"Anime Info",state:"Reading show details"},insights:{details:"Insights",state:"Reviewing stats"},settings:{details:"Settings",state:"Tweaking preferences"},trackers:{details:"Trackers",state:"Syncing watchlists"},map:{details:"Map",state:"Exploring the global user map"},asmr:{details:"ASMR",state:"Browsing ASMR works"},radio:{details:"Radio",state:"Browsing radio stations"},music:{details:"Music",state:"Browsing music"},tv:{details:"TV",state:"Browsing movies & shows"},manga:{details:"Manga",state:"Browsing manga"}}[e]??{details:"dango",state:"Idle"};try{this.gateway.send(JSON.stringify({op:3,d:{since:null,activities:[{name:"dango",type:e==="asmr"||e==="radio"||e==="music"?2:3,state:s.state,details:s.details,...r.DISCORD_CLIENT_ID?{application_id:r.DISCORD_CLIENT_ID}:{},...r.DISCORD_CLIENT_ID?{assets:{large_image:"logo",large_text:"dango"}}:{},buttons:["Learn More"],metadata:{button_urls:["https://github.com/serifpersia/dango"]},status_display_type:2}],status:"online",afk:!1}}))}catch(t){a.error({err:t},"Failed to send gateway idle status")}}clearPresence(){if(this.isEnabled){if(this.gateway&&this.gateway.readyState===o.OPEN)try{this.gateway.send(JSON.stringify({op:3,d:{since:null,activities:[],status:"online",afk:!1}}))}catch{}this.lastActivity=null}}shutdown(){this.isEnabled=!1,this.token=null,this.disconnect()}disconnect(){if(this.reconnectTimeout&&(clearTimeout(this.reconnectTimeout),this.reconnectTimeout=null),this.gateway){try{this.gateway.readyState===o.OPEN&&this.gateway.send(JSON.stringify({op:3,d:{since:null,activities:[],status:"online",afk:!1}})),this.gateway.close()}catch{}this.gateway=null}this.heartbeat&&(clearInterval(this.heartbeat),this.heartbeat=null)}connect(){if(!this.isEnabled||!this.token||this.gateway)return;const e="wss://gateway.discord.gg/?encoding=json&v=10";a.debug("Connecting Discord gateway"),this.gateway=new o(e);let n=41250;this.gateway.on("message",s=>{try{const t=JSON.parse(s.toString());if(t.op===10){n=t.d.heartbeat_interval,this.heartbeat=setInterval(()=>{try{this.gateway&&this.gateway.readyState===o.OPEN&&this.gateway.send(JSON.stringify({op:1,d:null}))}catch{}},n);const i={op:2,d:{token:this.token,capabilities:1021,client_state:{guild_hashes:{},highest_last_message_id:"0",private_channels_version:"0",read_state_version:0,user_guild_settings_version:-1,user_settings_version:-1},compress:!1,presence:{since:null,activities:[],status:"online",afk:!1},properties:{os:"Windows",os_version:"10.0.19044",os_arch:"x64",browser:"Discord Client",client_version:"0.0.20",client_build_number:152131,client_event_source:null,release_channel:"stable",system_locale:"en-US"}}};this.gateway&&this.gateway.send(JSON.stringify(i))}else t.op===11||(t.op===0?t.t==="READY"&&(a.info(`Gateway READY as ${t.d.user?.username||"unknown"}`),this.lastActivity&&this.updatePresence(this.lastActivity).catch(i=>a.warn({err:i},"failed to restore presence after READY"))):t.op===7?(a.warn("Gateway reconnect requested by Discord"),this.handleDisconnect()):t.op===9&&(a.warn("Gateway invalid session, reconnecting..."),this.handleDisconnect()))}catch(t){a.debug({err:t},"Gateway message parse error")}}),this.gateway.on("close",(s,t)=>{a.warn(`Gateway closed ${s} ${t.toString().slice(0,100)}`),this.handleDisconnect()}),this.gateway.on("error",s=>{a.error(s,"Gateway WebSocket error"),this.handleDisconnect()})}handleDisconnect(){this.heartbeat&&(clearInterval(this.heartbeat),this.heartbeat=null),this.gateway=null,this.isEnabled&&this.token&&(this.reconnectTimeout=setTimeout(()=>{this.reconnectTimeout=null,this.connect()},this.RECONNECT_DELAY))}}const v=new p;export{v as discordGatewayService};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{Client as u,StatusDisplayType as c}from"@xhayper/discord-rpc";import h from"./logger.js";import{CONFIG as d}from"./config.js";import{discordGatewayService as n}from"./discord-gateway.js";const i=h.child({module:"DiscordRPC"});class m{client=null;isEnabled=!1;hideMature=!0;reconnectTimeout=null;lastActivity=null;currentSessionId=null;hasPagePresence=!1;hasEverConnected=!1;permanentlyDisabled=!1;heartbeats=new Map;heartbeatTimer=null;retryCount=0;MAX_RETRIES=5;INITIAL_RECONNECT_DELAY=15e3;MAX_RECONNECT_DELAY=3e5;HEARTBEAT_TIMEOUT=4e4;HEARTBEAT_CHECK_INTERVAL=1e4;async setEnabled(e){if(this.isEnabled!==e)if(this.isEnabled=e,e){if(this.permanentlyDisabled){i.debug("Discord Rich Presence is unavailable on this device/session. Not retrying."),this.isEnabled=!1;return}this.retryCount=0,n.setEnabled(!0),n.serviceEnabled||this.connect()}else await this.disconnect(),n.setEnabled(!1)}get isServiceEnabled(){return this.isEnabled&&(this.client!==null||n.serviceEnabled)}setHideMature(e){this.hideMature=e}heartbeat(e){this.heartbeats.set(e,Date.now()),!this.heartbeatTimer&&this.isEnabled&&(this.heartbeatTimer=setInterval(()=>{this.checkHeartbeats()},this.HEARTBEAT_CHECK_INTERVAL))}removeHeartbeat(e){this.heartbeats.delete(e),this.checkHeartbeats()}async checkHeartbeats(){if(!this.isEnabled){this.heartbeatTimer&&(clearInterval(this.heartbeatTimer),this.heartbeatTimer=null);return}const e=Date.now();for(const[l,s]of this.heartbeats)e-s>this.HEARTBEAT_TIMEOUT&&this.heartbeats.delete(l);this.heartbeats.size===0&&(this.heartbeatTimer&&(clearInterval(this.heartbeatTimer),this.heartbeatTimer=null),(this.hasPagePresence||this.lastActivity||this.currentSessionId)&&(this.lastActivity=null,this.currentSessionId=null,this.hasPagePresence=!1,await this.setIdleStatus("idle")))}async connect(){if(!this.isEnabled||this.client||this.permanentlyDisabled)return;if(n.serviceEnabled){i.debug("Using Discord Gateway for presence (token configured). Skipping local RPC.");return}const e=!!process.env.TERMUX_VERSION,l=process.platform==="android";if(e||l){i.debug("Discord Rich Presence is not supported on Android/Termux. Skipping connection."),this.isEnabled=!1,this.permanentlyDisabled=!0;return}const s=d.DISCORD_CLIENT_ID;if(!s){i.debug("DISCORD_CLIENT_ID is not configured. Discord Rich Presence is disabled."),this.isEnabled=!1,this.permanentlyDisabled=!0;return}try{this.client=new u({clientId:s}),this.client.on("ready",()=>{i.info("Connected to Discord client successfully"),this.hasEverConnected=!0,this.retryCount=0,this.lastActivity?this.updatePresence(this.lastActivity):this.setIdleStatus("idle")}),this.client.on("disconnected",()=>{i.warn("Disconnected from Discord client, scheduling reconnect..."),this.cleanup(),this.scheduleReconnect()}),this.client.on("ERROR",a=>{const o=a instanceof Error?a.message:String(a);if(o.includes("ENOENT")||o.includes("ECONNREFUSED")){if(this.cleanup(),!this.hasEverConnected){i.debug("Discord client not detected. Rich Presence disabled for this session."),this.isEnabled=!1,this.permanentlyDisabled=!0;return}i.debug("Discord client not running or connection refused."),this.scheduleReconnect()}else i.error({err:a},"Discord RPC client error"),this.cleanup(),this.scheduleReconnect()});const t=this.client.login(),r=new Promise((a,o)=>setTimeout(()=>o(new Error("Login timeout")),5e3));await Promise.race([t,r]),this.hasEverConnected=!0}catch{if(this.cleanup(),!this.hasEverConnected){i.debug("Discord client not available at startup. Rich Presence disabled for the rest of this session."),this.isEnabled=!1,this.permanentlyDisabled=!0;return}this.scheduleReconnect()}}scheduleReconnect(){if(!this.isEnabled||this.reconnectTimeout||this.permanentlyDisabled)return;const e=Math.min(this.INITIAL_RECONNECT_DELAY*Math.pow(1.5,this.retryCount),this.MAX_RECONNECT_DELAY);this.retryCount++,this.retryCount>this.MAX_RETRIES?i.debug(`Discord RPC login failed ${this.retryCount} times. Retrying in ${Math.round(e/1e3)}s...`):i.info(`Discord RPC login failed. Retrying in ${Math.round(e/1e3)}s...`),this.reconnectTimeout=setTimeout(()=>{this.reconnectTimeout=null,this.connect()},e)}cleanup(){if(this.client){try{this.client.removeAllListeners(),this.client.destroy()}catch{}this.client=null}}async disconnect(){if(this.reconnectTimeout&&(clearTimeout(this.reconnectTimeout),this.reconnectTimeout=null),this.client?.user)try{await this.client.user.clearActivity()}catch{}this.cleanup(),this.lastActivity=null,this.currentSessionId=null,this.heartbeats.clear(),this.heartbeatTimer&&(clearInterval(this.heartbeatTimer),this.heartbeatTimer=null),n.disconnect()}async updatePresence(e){if(this.lastActivity=e,e.sessionId&&(this.currentSessionId=e.sessionId),!this.isEnabled)return;if(this.hideMature&&e.isAdult){if(n.serviceEnabled)n.clearPresence();else if(this.client?.user)try{await this.client.user.clearActivity()}catch{}return}if(n.serviceEnabled){n.updatePresence(e);return}if(!this.client||!this.client.user)return;const l=t=>!t.startsWith("https://")||t.includes("localhost")||t.includes("127.0.0.1")?!1:t.includes("s4.anilist.co")||t.includes("anilistcdn")?!0:!["youtube-anime.com","animepahe","animeya.cc","gogocdn.net","weeabo0.xyz","japaneseasmr.com"].some(a=>t.includes(a));let s="logo";if(e.thumbnail&&e.providerName!=="AnimePahe"){let t=e.thumbnail;if(t.includes("/api/image-proxy")){const r=t.match(/url=([^&]+)/);r&&(t=decodeURIComponent(r[1]))}if(l(t))s=t;else if(e.thumbnails){const r=e.thumbnails.find(a=>a&&(a.includes("s4.anilist.co")||a.includes("anilistcdn")));r&&(s=r)}}try{if(!e.isPlaying){await this.client.user.clearActivity(),await this.client.user.setActivity({details:e.title,state:e.stateLine?`${e.stateLine} (Paused)`:`Episode ${e.episode}${e.totalEpisodes?`/${e.totalEpisodes}`:""} (Paused)`,largeImageKey:s,largeImageText:e.title,...s!=="logo"?{smallImageKey:"small",smallImageText:"dango"}:{},type:e.providerName==="ASMR"||e.providerName==="Radio"||e.providerName==="Music"?2:3,statusDisplayType:c.DETAILS,buttons:[{label:"Learn More",url:"https://github.com/serifpersia/dango"}]}),this.hasPagePresence=!1;return}const t=Math.round(Date.now()/1e3),r={details:e.title,state:e.stateLine?e.stateLine:`Episode ${e.episode}${e.totalEpisodes?`/${e.totalEpisodes}`:""}`,largeImageKey:s,largeImageText:e.title,...s!=="logo"?{smallImageKey:"small",smallImageText:"dango"}:{},type:e.providerName==="ASMR"||e.providerName==="Radio"||e.providerName==="Music"?2:3,statusDisplayType:c.DETAILS,buttons:[{label:"Learn More",url:"https://github.com/serifpersia/dango"}]};e.currentTime&&e.currentTime>0&&(r.startTimestamp=Math.round(t-e.currentTime)),e.duration&&e.duration>e.currentTime&&(r.endTimestamp=Math.round(t+(e.duration-e.currentTime))),await this.client.user.setActivity(r),this.hasPagePresence=!1}catch(t){i.error({err:t},"Failed to set Discord activity")}}async setIdleStatus(e){if(!this.isEnabled)return;if(n.serviceEnabled){n.setIdleStatus(e);return}if(!this.client||!this.client.user){i.warn("Discord client not connected, skipping idle status update");return}const l={home:{details:"Home",state:"Browsing anime"},search:{details:"Search",state:"Exploring titles..."},watchlist:{details:"Watchlist",state:"Reviewing the watchlist"},anime:{details:"Anime Info",state:"Reading show details"},insights:{details:"Insights",state:"Reviewing stats"},settings:{details:"Settings",state:"Tweaking preferences"},mal:{details:"MAL Sync",state:"Syncing with MyAnimeList"},map:{details:"Map",state:"Exploring the global user map"},asmr:{details:"ASMR",state:"Browsing ASMR works"},radio:{details:"Radio",state:"Browsing radio stations"},music:{details:"Music",state:"Browsing music"},tv:{details:"TV",state:"Browsing movies & shows"},manga:{details:"Manga",state:"Browsing manga"}},s=l[e]??{details:"dango",state:"Idle"};try{if(!this.client||!this.client.user){i.warn("Discord client not connected, skipping idle status update");return}await this.client.user.setActivity({details:s.details,state:s.state,largeImageKey:"logo",largeImageText:"dango",type:e==="asmr"||e==="radio"||e==="music"?2:3,statusDisplayType:c.DETAILS,buttons:[{label:"Learn More",url:"https://github.com/serifpersia/dango"}]}),this.hasPagePresence=e in l}catch(t){t.message==="Closed by Discord"?i.warn("Discord connection ended, skipping idle status update"):i.error({err:t},"Failed to set Discord idle status")}}async clearPresence(e){if(!(e&&this.currentSessionId&&e!==this.currentSessionId)){if(this.lastActivity=null,this.currentSessionId=null,n.serviceEnabled){n.clearPresence();return}await this.setIdleStatus("idle")}}}const E=new m;export{E as discordRPCService};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import C from"./logger.js";import L from"path";import{dbAll as M,dbGet as x}from"./utils/db-utils.js";import{isTempSyncRow as O}from"./lib/temp-ids.js";import{updateEnvFile as R}from"./utils/env.utils.js";import{CONFIG as y}from"./config.js";import{SHIPPED_DEFAULTS as V}from"./shipped-defaults.js";import{exportTables as S,importTables as b,MANGA_SYNC_TABLES as v,TV_SYNC_TABLES as _,ASMR_SYNC_TABLES as P,normalizePayload as T,readPayloadVersion as p}from"./sync-payload.js";const u=C.child({module:"GitHubSync"}),l="dango-sync-data",E="aniweb-sync-data",W=["repo","delete_repo"],o={accept:"application/vnd.github+json","x-github-api-version":"2026-03-10"},g=["watchlist","watched_episodes","queue","settings","shows_meta","sync_metadata","dismissed_notifications","discovered_notifications"],G=new Function("specifier","return import(specifier)");function j(){const i=process.env.GITHUB_CLIENT_ID||V.GITHUB_CLIENT_ID||"";if(!i)throw new Error("GITHUB_CLIENT_ID is not configured");return i}function N(){return y.IS_DEV?"sync.dev.json":"sync.json"}function A(){return y.IS_DEV?"manga.sync.dev.json":"manga.sync.json"}function k(){return y.IS_DEV?"tv.sync.dev.json":"tv.sync.json"}function D(){return y.IS_DEV?"asmr.sync.dev.json":"asmr.sync.json"}async function H(i){const{Octokit:t}=await G("@octokit/rest");return new t({auth:i,request:{headers:o}})}function m(i){if(typeof i=="object"&&i&&"status"in i)return Number(i.status)}function f(i){return`"${i.replace(/"/g,'""')}"`}function $(i,t){return M(i,t)}function I(i){const e=i.tables.sync_metadata.find(a=>a.key==="db_version")?.value;return typeof e=="number"?e:Number(e||i.version||0)}function Y(i){if(!i||typeof i!="object"||!("tables"in i))throw new Error("Invalid GitHub sync payload.");const t=i;for(const e of g)if(!Array.isArray(t.tables?.[e]))throw new Error(`Invalid GitHub sync payload: missing ${e}.`);return t}class K{deviceState={status:"idle"};isAuthenticated(){return!!process.env.GITHUB_TOKEN}async migrateFromAniWebSync(){if(this.isAuthenticated())try{const t=await this.getOctokit(),{data:e}=await t.rest.users.getAuthenticated({headers:o});try{await t.rest.repos.get({owner:e.login,repo:E,headers:o})}catch(n){if(m(n)===404){u.info("No legacy aniweb-sync-data repo found. Skipping migration.");return}throw n}if(u.info("Found legacy aniweb-sync-data repo. Renaming to dango-sync-data..."),await t.rest.repos.get({owner:e.login,repo:l,headers:o}).catch(()=>null)){u.info("dango-sync-data repo already exists. Deleting legacy repo."),await t.rest.repos.delete({owner:e.login,repo:E,headers:o}),u.info("Deleted legacy aniweb-sync-data repo.");return}await t.rest.repos.update({owner:e.login,repo:E,name:l,description:"Private dango synchronization data.",headers:o}),u.info("Renamed aniweb-sync-data to dango-sync-data.")}catch(t){u.error({err:t},"GitHub sync migration failed")}}getDeviceState(){return this.deviceState}async startDeviceAuth(t,e){if(this.isAuthenticated()){const r=await this.getUserProfile();if(r){this.deviceState={status:"success",user:r};const{updateEnvFile:s}=await import("./utils/env.utils.js");return await s({SYNC_PROVIDER:"github"}),await e(t,"github"),this.deviceState}else u.warn("Saved GitHub token is invalid or expired. Clearing for new auth."),await this.logout()}if(this.deviceState.status==="pending")return this.deviceState;this.deviceState={status:"pending"};let a;const n=new Promise(r=>{a=r});return this.runDeviceAuth(t,e,a),await n,this.deviceState}async getUserProfile(){if(!process.env.GITHUB_TOKEN)return null;try{const t=await H(process.env.GITHUB_TOKEN),{data:e}=await t.rest.users.getAuthenticated({headers:o});return{login:e.login,name:e.name,avatarUrl:e.avatar_url}}catch(t){return m(t)===401&&(u.warn("GitHub token is invalid or expired. Logging out."),await this.logout()),null}}async logout(){await R({GITHUB_TOKEN:""}),delete process.env.GITHUB_TOKEN,this.deviceState={status:"idle"}}async getRemoteVersion(){const t=await this.fetchSyncPayload();return t?I(t):0}async getMangaRemoteVersion(){const t=await this.fetchMangaSyncPayload();return t?p(t):0}async getTvRemoteVersion(){const t=await this.fetchTvSyncPayload();return t?p(t):0}async getAsmrRemoteVersion(){const t=await this.fetchAsmrSyncPayload();return t?p(t):0}async syncUp(t){const e=await this.exportDatabase(t),a=await this.getOctokit(),n=await this.ensureRepo(a),r=await this.getSyncFile(a,n),s=Buffer.from(JSON.stringify(e,null,2),"utf8").toString("base64");await a.rest.repos.createOrUpdateFileContents({owner:n,repo:l,path:N(),message:`Sync dango data v${e.version}`,content:s,sha:r?.sha,headers:o})}async syncDown(t){const e=await this.fetchSyncPayload();return e?(this.importDatabase(t,e),I(e)):0}async syncMangaUp(t){const e=S(t,v),a=await this.getOctokit(),n=await this.ensureRepo(a),r=await this.getSyncFile(a,n,A()),s=Buffer.from(JSON.stringify(e,null,2),"utf8").toString("base64");await a.rest.repos.createOrUpdateFileContents({owner:n,repo:l,path:A(),message:`Sync dango manga data v${e.version}`,content:s,sha:r?.sha,headers:o})}async syncMangaDown(t){const e=await this.fetchMangaSyncPayload();return e?(b(t,v,e,{libraryTables:["manga_library","manga_progress"],backupName:"pre-sync-manga-backup.db"}),p(e)):0}async syncTvUp(t){const e=S(t,_),a=await this.getOctokit(),n=await this.ensureRepo(a),r=await this.getSyncFile(a,n,k()),s=Buffer.from(JSON.stringify(e,null,2),"utf8").toString("base64");await a.rest.repos.createOrUpdateFileContents({owner:n,repo:l,path:k(),message:`Sync dango TV data v${e.version}`,content:s,sha:r?.sha,headers:o})}async syncTvDown(t){const e=await this.fetchTvSyncPayload();return e?(b(t,_,e,{libraryTables:["tv_library","tv_progress"],backupName:"pre-sync-tv-backup.db"}),p(e)):0}async syncAsmrUp(t){const e=S(t,P),a=await this.getOctokit(),n=await this.ensureRepo(a),r=await this.getSyncFile(a,n,D()),s=Buffer.from(JSON.stringify(e,null,2),"utf8").toString("base64");await a.rest.repos.createOrUpdateFileContents({owner:n,repo:l,path:D(),message:`Sync dango ASMR data v${e.version}`,content:s,sha:r?.sha,headers:o})}async syncAsmrDown(t){const e=await this.fetchAsmrSyncPayload();return e?(b(t,P,e,{libraryTables:["asmr_library","asmr_progress"],backupName:"pre-sync-asmr-backup.db"}),p(e)):0}async runDeviceAuth(t,e,a){try{const{createOAuthDeviceAuth:n}=await G("@octokit/auth-oauth-device"),s=await n({clientId:j(),scopes:W,onVerification:c=>{this.deviceState={status:"pending",verification:{user_code:c.user_code,verification_uri:c.verification_uri,expires_in:c.expires_in,interval:c.interval}},a()}})({type:"oauth"});await R({GITHUB_TOKEN:s.token,SYNC_PROVIDER:"github"}),process.env.GITHUB_TOKEN=s.token;const d=await this.getUserProfile();this.deviceState={status:"success",user:d||void 0};try{await e(t,"github")}catch(c){u.error({err:c},"Post-GitHub-login sync failed")}}catch(n){this.deviceState={status:"error",error:n instanceof Error?n.message:"GitHub device authentication failed."},a(),u.error({err:n},"GitHub device authentication failed")}}async getOctokit(){if(!process.env.GITHUB_TOKEN)throw new Error("GitHub token is not configured.");return H(process.env.GITHUB_TOKEN)}async ensureRepo(t){const{data:e}=await t.rest.users.getAuthenticated({headers:o});try{await t.rest.repos.get({owner:e.login,repo:l,headers:o})}catch(a){if(m(a)!==404)throw a;await t.rest.repos.createForAuthenticatedUser({name:l,private:!0,auto_init:!0,description:"Private dango synchronization data.",headers:o})}return e.login}async getSyncFile(t,e,a=N()){try{const r=(await t.rest.repos.getContent({owner:e,repo:l,path:a,headers:o})).data;return r.type!=="file"||!r.content||!r.sha?null:{content:Buffer.from(r.content,"base64").toString("utf8"),sha:r.sha}}catch(n){if(m(n)===404)return null;throw n}}async fetchSyncPayload(){const t=await this.getOctokit(),e=await this.ensureRepo(t),a=await this.getSyncFile(t,e);return a?Y(JSON.parse(a.content)):null}async fetchMangaSyncPayload(){const t=await this.getOctokit(),e=await this.ensureRepo(t),a=await this.getSyncFile(t,e,A());return a?T(JSON.parse(a.content),v,"GitHub manga"):null}async fetchTvSyncPayload(){const t=await this.getOctokit(),e=await this.ensureRepo(t),a=await this.getSyncFile(t,e,k());return a?T(JSON.parse(a.content),_,"GitHub TV"):null}async fetchAsmrSyncPayload(){const t=await this.getOctokit(),e=await this.ensureRepo(t),a=await this.getSyncFile(t,e,D());return a?T(JSON.parse(a.content),P,"GitHub ASMR"):null}async exportDatabase(t){const e={};for(const a of g){const n=await $(t,`SELECT * FROM ${f(a)}`);e[a]=n.filter(r=>!O(r))}return{version:I({version:0,exportedAt:"",tables:e}),exportedAt:new Date().toISOString(),tables:e}}importDatabase(t,e){const a=(e.tables.watchlist?.length??0)+(e.tables.watched_episodes?.length??0),n=x(t,"SELECT (SELECT COUNT(*) FROM watchlist) + (SELECT COUNT(*) FROM watched_episodes) AS n")?.n??0;if(a===0&&n>0)throw new Error("Sync down refused: remote library is empty while local library has data");try{t.backup(L.join(y.ROOT,"pre-sync-backup.db"))}catch(s){u.warn({err:s},"Pre-sync backup failed, continuing without backup")}const r=new Map;for(const s of g){const d=t.all(`PRAGMA table_info("${s}")`);r.set(s,new Set(d.map(c=>c.name)))}t.serialize(()=>{for(const s of g)t.run(`DELETE FROM ${f(s)}`);for(const s of g){const d=r.get(s);for(const c of e.tables[s]){if(O(c))continue;const h=Object.keys(c).filter(w=>d?.has(w));if(h.length===0)continue;const U=h.map(f).join(", "),F=h.map(()=>"?").join(", "),B=h.map(w=>c[w]);t.run(`INSERT INTO ${f(s)} (${U}) VALUES (${F})`,B)}}})}}const rt=new K;export{rt as githubSyncService};
|
package/server/dist/google.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import p from"fs";import F from"path";import{Readable as A}from"stream";import{pipeline as C}from"stream/promises";import o from"./logger.js";import{CONFIG as n}from"./config.js";import{dbAll as L,dbGet as P}from"./utils/db-utils.js";import{isTempSyncRow as S}from"./lib/temp-ids.js";import{fetchWithRetry as m,HttpError as d,decodeMaybeGzip as I,parseJsonBody as y}from"./utils/http.utils.js";import{exportTables as w,importTables as b,MANGA_SYNC_TABLES as _,TV_SYNC_TABLES as T,ASMR_SYNC_TABLES as v,normalizePayload as k,readPayloadVersion as E}from"./sync-payload.js";const f=12e4;class D{tokens={};folderIdCache=new Map;constructor(){!n.GOOGLE_AUTH_WORKER_URL&&!n.GOOGLE_CLIENT_ID&&o.error("GOOGLE_AUTH_WORKER_URL is missing and no GOOGLE_CLIENT_ID fallback!"),this.loadTokens()}useWorker(){return!!n.GOOGLE_AUTH_WORKER_URL}loadTokens(){if(p.existsSync(n.TOKEN_PATH))try{this.tokens=JSON.parse(p.readFileSync(n.TOKEN_PATH,"utf-8"))}catch(e){o.error({err:e},"Failed to load Google tokens")}}saveTokens(e){const t={...this.tokens,...e};t.expires_in&&!t.expiry_date&&(t.expiry_date=Date.now()+t.expires_in*1e3),this.tokens=t;try{p.writeFileSync(n.TOKEN_PATH,JSON.stringify(this.tokens))}catch(r){o.error({err:r},"Failed to save refreshed tokens")}}getGoogleClientConfig(){return{clientId:n.GOOGLE_CLIENT_ID||"",clientSecret:n.GOOGLE_CLIENT_SECRET||""}}async postJson(e,t){const r=await m(e,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(t)},{timeoutMs:f});if(!r.ok)throw new d(r.status);return y(r)}async postForm(e,t){const r=await m(e,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:t.toString()},{timeoutMs:f});if(!r.ok)throw new d(r.status);return y(r)}async refreshViaWorker(){if(!this.tokens.refresh_token)return!1;try{const e=await this.postJson(`${n.GOOGLE_AUTH_WORKER_URL}/refresh`,{refresh_token:this.tokens.refresh_token});return this.saveTokens(e),!0}catch(e){throw e instanceof d&&(e.status===400||e.status===401)&&(o.warn("Worker refresh failed. Token may be revoked. Logging out."),await this.logout()),e}}async refreshAccessToken(){if(!this.tokens.refresh_token)throw new Error("Missing refresh token");if(this.useWorker()){await this.refreshViaWorker();return}const{clientId:e,clientSecret:t}=this.getGoogleClientConfig();if(!e||!t)throw new Error("Google OAuth credentials are not configured");const r=new URLSearchParams({client_id:e,client_secret:t,refresh_token:this.tokens.refresh_token,grant_type:"refresh_token"});try{const s=await this.postForm("https://oauth2.googleapis.com/token",r);this.saveTokens(s)}catch(s){throw s instanceof d&&(s.status===400||s.status===401)&&(o.warn("Failed to refresh Google access token. Token may be revoked. Logging out."),await this.logout()),s}}async ensureAccessToken(){const e=!this.tokens.expiry_date||Date.now()>=this.tokens.expiry_date-6e4;(!this.tokens.access_token||e)&&await this.refreshAccessToken()}withParams(e,t){if(!t)return e;const r=new URL(e);for(const[s,a]of Object.entries(t))a!=null&&r.searchParams.set(s,String(a));return r.toString()}serializeBody(e){return e==null?{}:typeof e=="string"?{body:e}:e instanceof Uint8Array||e instanceof A?{body:e,duplex:"half"}:{body:JSON.stringify(e)}}async googleRequest(e){await this.ensureAccessToken();try{const{body:t,duplex:r}=this.serializeBody(e.data),s=await m(this.withParams(e.url,e.params),{method:e.method,headers:{Authorization:`Bearer ${this.tokens.access_token}`,...e.headers??{}},...t!==void 0?{body:t}:{},...r?{duplex:r}:{}},{timeoutMs:f});if(!s.ok){try{await s.body?.cancel()}catch{}throw new d(s.status)}if(e.responseType==="stream"){if(!s.body)throw new d(s.status,"Empty upstream body");return{data:A.from(s.body),status:s.status,headers:s.headers}}if(e.responseType==="text")return{data:await s.text(),status:s.status,headers:s.headers};const a=I(Buffer.from(await s.arrayBuffer())).toString("utf8");return{data:a?JSON.parse(a):void 0,status:s.status,headers:s.headers}}catch(t){throw t instanceof d&&(t.status===401||t.status===403)&&(o.warn("Google API request failed with auth error. Logging out."),await this.logout()),t}}isAuthenticated(){return!!this.tokens.refresh_token}async getAuthUrl(){if(this.useWorker())try{const r=new URL(`${n.GOOGLE_AUTH_WORKER_URL}/auth-url`);r.searchParams.set("redirect_uri",n.GOOGLE_REDIRECT_URI);const s=await m(r,{},{timeoutMs:f});if(!s.ok)throw new d(s.status);const a=await y(s);if(a?.url)return a.url}catch(r){o.error({err:r},"Worker /auth-url failed, falling back to local")}const{clientId:e}=this.getGoogleClientConfig();if(!e)throw new Error("Google OAuth credentials are not configured");const t=new URL("https://accounts.google.com/o/oauth2/v2/auth");return t.searchParams.set("client_id",e),t.searchParams.set("redirect_uri",n.GOOGLE_REDIRECT_URI),t.searchParams.set("response_type","code"),t.searchParams.set("access_type","offline"),t.searchParams.set("prompt","consent"),t.searchParams.set("scope",n.GOOGLE_SCOPES.join(" ")),t.toString()}async handleCallback(e){if(this.useWorker())try{const i=await this.postJson(`${n.GOOGLE_AUTH_WORKER_URL}/exchange`,{code:e,redirect_uri:n.GOOGLE_REDIRECT_URI});return this.saveTokens(i),this.tokens}catch(i){throw o.error({err:i},"Worker /exchange failed"),i}const{clientId:t,clientSecret:r}=this.getGoogleClientConfig(),s=new URLSearchParams({code:e,client_id:t,client_secret:r,redirect_uri:n.GOOGLE_REDIRECT_URI,grant_type:"authorization_code"}),a=await this.postForm("https://oauth2.googleapis.com/token",s);return this.saveTokens(a),this.tokens}async getUserProfile(){if(!this.isAuthenticated())return null;try{return(await this.googleRequest({method:"GET",url:"https://www.googleapis.com/oauth2/v2/userinfo"})).data}catch(e){return o.error({err:e},"Failed to fetch user profile"),e instanceof d&&(e.status===401||e.status===403)&&(o.warn("Google authentication token is invalid or expired. Logging out."),await this.logout()),null}}async logout(){p.existsSync(n.TOKEN_PATH)&&p.unlinkSync(n.TOKEN_PATH),this.tokens={},this.folderIdCache.clear()}async ensureFolder(e){if(!this.isAuthenticated())throw new Error("Not authenticated");const t=this.folderIdCache.get(e);if(t)return t;const r=await this.findFile(e,void 0,"application/vnd.google-apps.folder");if(r)return this.folderIdCache.set(e,r.id),r.id;const s={name:e,mimeType:"application/vnd.google-apps.folder"};try{const i=(await this.googleRequest({method:"POST",url:"https://www.googleapis.com/drive/v3/files",params:{fields:"id"},data:s,headers:{"Content-Type":"application/json"}})).data.id;return this.folderIdCache.set(e,i),i}catch(a){throw o.error({err:a},`Failed to create folder ${e}`),a}}async listFiles(e){if(!this.isAuthenticated())return[];try{return(await this.googleRequest({method:"GET",url:"https://www.googleapis.com/drive/v3/files",params:{q:`'${e}' in parents and trashed = false`,fields:"files(id, name)",spaces:"drive"}})).data.files||[]}catch(t){return o.error({err:t},"Failed to list files in folder"),[]}}async deleteFile(e){if(this.isAuthenticated())try{await this.googleRequest({method:"DELETE",url:`https://www.googleapis.com/drive/v3/files/${encodeURIComponent(e)}`})}catch(t){o.warn({err:t},`Failed to delete file ${e}`)}}async findFile(e,t,r){if(!this.isAuthenticated())return null;let a=`name = '${e.replace(/'/g,"\\'")}' and trashed = false`;if(t){const i=t.replace(/'/g,"\\'");a+=` and '${i}' in parents`}if(r){const i=r.replace(/'/g,"\\'");a+=` and mimeType = '${i}'`}try{const i=await this.googleRequest({method:"GET",url:"https://www.googleapis.com/drive/v3/files",params:{q:a,fields:"files(id, name)",spaces:"drive",orderBy:"createdTime desc"}});return i.data.files&&i.data.files.length>0?(i.data.files.length>1&&o.warn(`Multiple files found for ${e}, using the most recent one.`),{id:i.data.files[0].id,name:i.data.files[0].name}):null}catch(i){throw o.error({err:i},`Error while searching for file ${e}`),i}}async downloadFile(e,t){if(!this.isAuthenticated())throw new Error("Not authenticated");const r=p.createWriteStream(t);try{const s=await this.googleRequest({method:"GET",url:`https://www.googleapis.com/drive/v3/files/${encodeURIComponent(e)}`,params:{alt:"media"},responseType:"stream"});await C(s.data,r)}catch(s){throw r.destroy(),s}}async uploadFile(e,t,r="application/octet-stream",s,a){if(!this.isAuthenticated())throw new Error("Not authenticated");let i=a;if(!i){const l=await this.findFile(t,s,r);l&&(i=l.id)}const c={mimeType:r,body:p.createReadStream(e)};try{if(i)await this.googleRequest({method:"PATCH",url:`https://www.googleapis.com/upload/drive/v3/files/${encodeURIComponent(i)}`,params:{uploadType:"media"},data:c.body,headers:{"Content-Type":c.mimeType}});else{const l={name:t};s&&(l.parents=[s]);const u=await this.googleRequest({method:"POST",url:"https://www.googleapis.com/drive/v3/files",params:{fields:"id"},data:l,headers:{"Content-Type":"application/json"}});await this.googleRequest({method:"PATCH",url:`https://www.googleapis.com/upload/drive/v3/files/${encodeURIComponent(u.data.id)}`,params:{uploadType:"media"},data:c.body,headers:{"Content-Type":c.mimeType}})}}catch(l){throw o.error({err:l},`Failed to upload file ${t}`),l}}getSyncFilename(){return n.GOOGLE_SYNC_FILENAME||"sync.json"}getMangaSyncFilename(){return n.MANGA_GOOGLE_SYNC_FILENAME||"manga.sync.json"}getTvSyncFilename(){return n.TV_GOOGLE_SYNC_FILENAME||"tv.sync.json"}getAsmrSyncFilename(){return n.ASMR_GOOGLE_SYNC_FILENAME||"asmr.sync.json"}async findFileInAppData(e){if(!this.isAuthenticated())return null;const t=e.replace(/'/g,"\\'");try{const r=await this.googleRequest({method:"GET",url:"https://www.googleapis.com/drive/v3/files",params:{q:`name = '${t}' and trashed = false`,fields:"files(id, name)",spaces:"appDataFolder",orderBy:"createdTime desc"}});return r.data.files&&r.data.files.length>0?{id:r.data.files[0].id,name:r.data.files[0].name}:null}catch(r){throw o.error({err:r},`Error searching appDataFolder for ${e}`),r}}async downloadJson(e){const t=await this.googleRequest({method:"GET",url:`https://www.googleapis.com/drive/v3/files/${encodeURIComponent(e)}`,params:{alt:"media"},responseType:"text"});return typeof t.data=="string"?JSON.parse(t.data):t.data}async uploadJson(e,t,r){const s=JSON.stringify(t,null,2);if(r){await this.googleRequest({method:"PATCH",url:`https://www.googleapis.com/upload/drive/v3/files/${encodeURIComponent(r)}`,params:{uploadType:"media"},data:s,headers:{"Content-Type":"application/json"}});return}const a=await this.googleRequest({method:"POST",url:"https://www.googleapis.com/drive/v3/files",params:{fields:"id"},data:{name:e,parents:["appDataFolder"]},headers:{"Content-Type":"application/json"}});await this.googleRequest({method:"PATCH",url:`https://www.googleapis.com/upload/drive/v3/files/${encodeURIComponent(a.data.id)}`,params:{uploadType:"media"},data:s,headers:{"Content-Type":"application/json"}})}async getRemoteVersion(){if(!this.isAuthenticated())return 0;const e=await this.findFileInAppData(this.getSyncFilename());if(!e)return 0;try{const t=await this.downloadJson(e.id),s=t?.tables?.sync_metadata?.find(a=>a.key==="db_version")?.value;return typeof s=="number"?s:Number(s||t?.version||0)}catch{return 0}}async getMangaRemoteVersion(){if(!this.isAuthenticated())return 0;const e=await this.findFileInAppData(this.getMangaSyncFilename());if(!e)return 0;try{const t=await this.downloadJson(e.id),s=t?.tables?.sync_metadata?.find(a=>a.key==="db_version")?.value;return typeof s=="number"?s:Number(s||t?.version||0)}catch{return 0}}async syncUp(e){if(!this.isAuthenticated())return;const t=await this.exportDatabase(e),r=await this.findFileInAppData(this.getSyncFilename());await this.uploadJson(this.getSyncFilename(),t,r?.id)}async syncDown(e){if(!this.isAuthenticated())return 0;const t=await this.findFileInAppData(this.getSyncFilename());if(!t)return 0;const r=await this.downloadJson(t.id);this.importDatabase(e,r);const s=r.tables?.sync_metadata?.find(a=>a.key==="db_version");return Number(s?.value||r.version||0)}async syncMangaUp(e){if(!this.isAuthenticated())return;const t=w(e,_),r=await this.findFileInAppData(this.getMangaSyncFilename());await this.uploadJson(this.getMangaSyncFilename(),t,r?.id)}async syncMangaDown(e){if(!this.isAuthenticated())return 0;const t=await this.findFileInAppData(this.getMangaSyncFilename());if(!t)return 0;const r=await this.downloadJson(t.id),s=k(r,_,"Google manga");return b(e,_,s,{libraryTables:["manga_library","manga_progress"],backupName:"pre-sync-manga-backup.db"}),E(s)}async getTvRemoteVersion(){if(!this.isAuthenticated())return 0;const e=await this.findFileInAppData(this.getTvSyncFilename());if(!e)return 0;try{const t=await this.downloadJson(e.id),s=t?.tables?.sync_metadata?.find(a=>a.key==="db_version")?.value;return typeof s=="number"?s:Number(s||t?.version||0)}catch{return 0}}async syncTvUp(e){if(!this.isAuthenticated())return;const t=w(e,T),r=await this.findFileInAppData(this.getTvSyncFilename());await this.uploadJson(this.getTvSyncFilename(),t,r?.id)}async syncTvDown(e){if(!this.isAuthenticated())return 0;const t=await this.findFileInAppData(this.getTvSyncFilename());if(!t)return 0;const r=await this.downloadJson(t.id),s=k(r,T,"Google TV");return b(e,T,s,{libraryTables:["tv_library","tv_progress"],backupName:"pre-sync-tv-backup.db"}),E(s)}async getAsmrRemoteVersion(){if(!this.isAuthenticated())return 0;const e=await this.findFileInAppData(this.getAsmrSyncFilename());if(!e)return 0;try{const t=await this.downloadJson(e.id),s=t?.tables?.sync_metadata?.find(a=>a.key==="db_version")?.value;return typeof s=="number"?s:Number(s||t?.version||0)}catch{return 0}}async syncAsmrUp(e){if(!this.isAuthenticated())return;const t=w(e,v),r=await this.findFileInAppData(this.getAsmrSyncFilename());await this.uploadJson(this.getAsmrSyncFilename(),t,r?.id)}async syncAsmrDown(e){if(!this.isAuthenticated())return 0;const t=await this.findFileInAppData(this.getAsmrSyncFilename());if(!t)return 0;const r=await this.downloadJson(t.id),s=k(r,v,"Google ASMR");return b(e,v,s,{libraryTables:["asmr_library","asmr_progress"],backupName:"pre-sync-asmr-backup.db"}),E(s)}async exportDatabase(e){const t=["watchlist","watched_episodes","queue","settings","shows_meta","sync_metadata","dismissed_notifications","discovered_notifications"],r={};for(const a of t)r[a]=L(e,`SELECT * FROM "${a.replace(/"/g,'""')}"`).filter(i=>!S(i));const s=r.sync_metadata.find(a=>a.key==="db_version");return{version:Number(s?.value||0),exportedAt:new Date().toISOString(),tables:r}}importDatabase(e,t){const r=(t.tables.watchlist?.length??0)+(t.tables.watched_episodes?.length??0),s=P(e,"SELECT (SELECT COUNT(*) FROM watchlist) + (SELECT COUNT(*) FROM watched_episodes) AS n")?.n??0;if(r===0&&s>0)throw new Error("Sync down refused: remote library is empty while local library has data");try{e.backup(F.join(n.ROOT,"pre-sync-backup.db"))}catch(c){o.warn({err:c},"Pre-sync backup failed, continuing without backup")}const a=["watchlist","watched_episodes","queue","settings","shows_meta","sync_metadata","dismissed_notifications","discovered_notifications"],i=new Map;for(const c of a){const l=e.all(`PRAGMA table_info("${c}")`);i.set(c,new Set(l.map(u=>u.name)))}e.serialize(()=>{for(const c of a)e.run(`DELETE FROM "${c}"`);for(const c of a){const l=i.get(c);for(const u of t.tables[c]||[]){if(S(u))continue;const g=Object.keys(u).filter(h=>l?.has(h));if(g.length===0)continue;const R=g.map(h=>`"${h.replace(/"/g,'""')}"`).join(", "),G=g.map(()=>"?").join(", "),O=g.map(h=>u[h]);e.run(`INSERT INTO "${c}" (${R}) VALUES (${G})`,O)}}})}}const Y=new D;export{D as GoogleDriveService,Y as googleDriveService};
|
package/server/dist/hono/asmr.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
function t(e){if(!e||typeof e!="object")return!1;const n=e;return typeof n.browse=="function"&&typeof n.getEpisodes=="function"&&typeof n.getStreamUrls=="function"&&typeof n.getImages=="function"&&typeof n.getChapters=="function"}export{t as isJasmrApi};
|
package/server/dist/hono/auth.js
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import a from"../logger.js";import{googleDriveService as n}from"../google.js";import{updateEnvFile as l}from"../utils/env.utils.js";function w(r,i,s){r.get("/api/auth/google/callback",async e=>{const o=e.req.query("code");if(!o)return e.text("No code provided",400);await n.handleCallback(o);const c=await n.getUserProfile();await l({SYNC_PROVIDER:"google"}),a.info("User logged in. Syncing database (please wait)...");const t=i();try{await s(t.db,t.mangaDb,"google")}catch(d){a.error({err:d},"Post-login sync failed")}const p=`
|
|
2
|
-
<html>
|
|
3
|
-
<head><meta name="viewport" content="width=device-width, initial-scale=1" /></head>
|
|
4
|
-
<body>
|
|
5
|
-
<h1>Authentication Successful</h1>
|
|
6
|
-
<p>Database synced. Returning to dango...</p>
|
|
7
|
-
<p><a href="/?google_auth=success">Tap here if you are not redirected</a></p>
|
|
8
|
-
<script>
|
|
9
|
-
(function () {
|
|
10
|
-
var payload = { type: 'GOOGLE_AUTH_SUCCESS', user: ${JSON.stringify(c)} };
|
|
11
|
-
try {
|
|
12
|
-
if (window.opener && !window.opener.closed) {
|
|
13
|
-
window.opener.postMessage(payload, window.location.origin);
|
|
14
|
-
setTimeout(function () { window.close(); }, 300);
|
|
15
|
-
setTimeout(function () { window.location.href = '/?google_auth=success'; }, 1500);
|
|
16
|
-
} else {
|
|
17
|
-
window.location.href = '/?google_auth=success';
|
|
18
|
-
}
|
|
19
|
-
} catch (e) {
|
|
20
|
-
window.location.href = '/?google_auth=success';
|
|
21
|
-
}
|
|
22
|
-
})();
|
|
23
|
-
</script>
|
|
24
|
-
</body>
|
|
25
|
-
</html>
|
|
26
|
-
`;return e.html(p)})}export{w as registerAuth};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import c from"../logger.js";import{parseUmpResponse as T}from"../lib/ump.js";import{mintPoToken as M,resetPoTokens as C}from"../lib/potoken.js";import{getInnertube as D,getAuthedInnertube as R,getMusicCookie as I,refreshAuthedInnertube as v}from"../lib/ytmusic.js";const P="Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:156.0) Gecko/20100101 Firefox/156.0",w=new Map,_=300*1e3,$=16384,b=262144,N="1.20260920.16.00",g=new Map,p=new Map;function x(){const r="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";let e="";for(let t=0;t<16;t++)e+=r[Math.floor(Math.random()*r.length)];return e}function j(r){let e=p.get(r);return e||(e={cpn:x(),rn:0,carry:null},p.set(r,e)),e}function E(r,e){const t=g.get(e),i=(a,n)=>(t!==void 0&&(n=Math.min(n,t-1)),[a,Math.max(n,a)]),o=/^bytes=(\d+)-(\d*)$/.exec((r??"").trim());if(o&&o[2]!==""){const a=Number(o[1]),n=Number(o[2]);return n-a+1<=b?[a,n]:i(a,a+b-1)}const s=o?Number(o[1]):0;return i(s,s+(s===0?$:b)-1)}async function A(r,e){const s=[...(await r.music.getInfo(e)).streaming_data?.adaptive_formats??[]].filter(n=>n.has_audio&&!n.has_video).sort((n,m)=>(m.bitrate??0)-(n.bitrate??0)),a=r.session.player;for(const n of s)try{const m=await n.decipher(a);if(m&&m.startsWith("http")){const u={url:m,mimeType:n.mime_type,fetchedAt:Date.now()};return w.set(e,u),u}}catch{}throw new Error("No stream available")}async function q(r){const e=w.get(r);if(e&&Date.now()-e.fetchedAt<_)return e;const t=await R().catch(()=>null);if(t)try{return await A(t,r)}catch(i){c.warn({err:i},"[music] authed stream lookup failed, retrying with a fresh saved-cookie session");const o=await v();if(o)try{return await A(o,r)}catch(s){c.warn({err:s},"[music] refreshed stream lookup failed, falling back to public session")}}return A(await D(),r)}function Y(r){r.get("/api/music/stream",async e=>{const t=String(e.req.query("videoId")||e.req.query("id")||"").trim();if(!t)return e.json({error:"videoId is required"},400);try{const i=await q(t);return e.json({url:i.url,mimeType:i.mimeType})}catch(i){return c.error({err:i},"[music] stream failed"),e.json({error:"Stream lookup failed"},502)}}),r.get("/api/music/audio",async e=>{const t=String(e.req.query("videoId")||e.req.query("id")||"").trim();if(!t)return e.json({error:"videoId is required"},400);const i=e.req.raw.signal.aborted?e.req.raw.signal:AbortSignal.any([e.req.raw.signal,AbortSignal.timeout(3e4)]);try{for(let o=0;o<3;o++){o===1&&p.delete(t),o===2&&(w.delete(t),p.delete(t),C(t));let s;try{s=await q(t)}catch{return e.json({error:"No stream available"},502)}const a=/[?&]clen=(\d+)/.exec(s.url)?.[1];a&&!g.has(t)&&g.set(t,Number(a));const[n,m]=E(e.req.header("range")??null,t),u=j(t);let h="";try{h=await M(t)}catch(f){c.warn({err:f},"[music] PoToken mint failed, trying without attestation")}const k=`${s.url}&cpn=${u.cpn}&cver=${N}&range=${n}-${m}&rn=${u.rn}&rbuf=0`+(h?`&pot=${encodeURIComponent(h)}`:"")+"&ump=1&srfvp=1";u.rn+=1;let S;try{const f=I(),y=await fetch(k,{method:"POST",headers:{"User-Agent":P,Referer:"https://music.youtube.com/",Origin:"https://music.youtube.com/",...f?{Cookie:f}:{}},body:"x\0",signal:i});if(y.status!==200){try{await y.body?.cancel()}catch{}continue}S=Buffer.from(await y.arrayBuffer())}catch(f){if(e.req.raw.signal.aborted)return new Response(null,{status:499});c.error({err:f},"[music] audio upstream failed");continue}const d=T(S,u.carry);if(u.carry=d.carry,d.status===3){c.warn({videoId:t},"[music] stream protection blocked, refreshing token"),C(t),p.delete(t);continue}if(d.media.length===0)continue;const U=g.get(t),l=new Headers;return l.set("Content-Type",s.mimeType||"audio/webm"),l.set("Content-Length",String(d.media.length)),U&&l.set("Content-Range",`bytes ${n}-${n+d.media.length-1}/${U}`),l.set("Accept-Ranges","bytes"),l.set("Access-Control-Allow-Origin","*"),new Response(d.media,{status:206,headers:l})}return c.warn({videoId:t},"[music] audio upstream gave no media after retries"),e.json({error:"Upstream error"},502)}catch(o){return e.req.raw.signal.aborted?new Response(null,{status:499}):(c.error({err:o},"[music] audio failed"),e.json({error:"Stream lookup failed"},502))}})}export{Y as registerMusic};
|