├── .editorconfig ├── .env ├── .eslintignore ├── .eslintrc ├── .github ├── ISSUE_TEMPLATE │ ├── bug-report.zh-CN.yml │ └── feature-report.zh-CN.yml ├── PULL_REQUEST_TEMPLATE.md └── workflows │ └── main.yml ├── .gitignore ├── .husky ├── commit-msg ├── pre-commit └── prepare-commit-msg ├── .npmrc ├── .prettierignore ├── .prettierrc.yaml ├── .vscode ├── extensions.json ├── launch.json └── settings.json ├── .yarnclean ├── LICENSE ├── README.md ├── build ├── entitlements.mac.plist ├── hook │ ├── notarize.js │ ├── patch-monaco-clipboard.js │ └── removeLocales.js ├── icons │ ├── 1024x1024.png │ ├── 128x128.png │ ├── 16x16.png │ ├── 24x24.png │ ├── 256x256.png │ ├── 32x32.png │ ├── 48x48.png │ ├── 512x512.png │ ├── 64x64.png │ ├── logo.icns │ ├── logo.ico │ ├── logo.png │ └── mac_logo.png └── installer.nsh ├── desgin ├── player.sketch └── video_platform.sketch ├── dev-app-update.yml ├── electron-builder.yml ├── electron.vite.config.ts ├── package.json ├── resources ├── img │ └── icons │ │ ├── logo.png │ │ ├── tray_dark.png │ │ └── tray_light.png └── t3PyBase │ ├── base │ ├── localProxy.py │ └── spider.py │ └── main.py ├── src ├── main │ ├── core │ │ ├── db │ │ │ ├── common │ │ │ │ ├── client.ts │ │ │ │ ├── curd.ts │ │ │ │ ├── index.ts │ │ │ │ ├── schema.ts │ │ │ │ └── webdev.ts │ │ │ ├── index.ts │ │ │ ├── migration │ │ │ │ ├── index.ts │ │ │ │ └── modules │ │ │ │ │ ├── init │ │ │ │ │ ├── index.ts │ │ │ │ │ └── tbl_setting.json │ │ │ │ │ ├── update0_0_0to3_3_1.ts │ │ │ │ │ ├── update3_3_10to3_4_0.ts │ │ │ │ │ ├── update3_3_1to3_3_2.ts │ │ │ │ │ ├── update3_3_3to3_3_4.ts │ │ │ │ │ ├── update3_3_4to3_3_5.ts │ │ │ │ │ ├── update3_3_6to3_3_7.ts │ │ │ │ │ ├── update3_3_7to3_3_8.ts │ │ │ │ │ ├── update3_3_8to3_3_9.ts │ │ │ │ │ └── update3_3_9to3_3_10.ts │ │ │ └── service │ │ │ │ ├── analyze.ts │ │ │ │ ├── channel.ts │ │ │ │ ├── db.ts │ │ │ │ ├── drive.ts │ │ │ │ ├── history.ts │ │ │ │ ├── index.ts │ │ │ │ ├── iptv.ts │ │ │ │ ├── setting.ts │ │ │ │ ├── site.ts │ │ │ │ └── star.ts │ │ ├── global │ │ │ └── index.ts │ │ ├── ipc │ │ │ └── index.ts │ │ ├── logger │ │ │ └── index.ts │ │ ├── menu │ │ │ └── index.ts │ │ ├── protocol │ │ │ └── index.ts │ │ ├── server │ │ │ ├── index.ts │ │ │ └── routes │ │ │ │ └── v1 │ │ │ │ ├── db │ │ │ │ ├── db.ts │ │ │ │ ├── index.ts │ │ │ │ └── utils │ │ │ │ │ └── data.ts │ │ │ │ ├── drive │ │ │ │ ├── db.ts │ │ │ │ ├── index.ts │ │ │ │ ├── utils │ │ │ │ │ ├── alist.ts │ │ │ │ │ └── similarity.ts │ │ │ │ └── work.ts │ │ │ │ ├── file │ │ │ │ ├── index.ts │ │ │ │ └── work.ts │ │ │ │ ├── history │ │ │ │ ├── index.ts │ │ │ │ └── work.ts │ │ │ │ ├── index.ts │ │ │ │ ├── lab │ │ │ │ ├── ad │ │ │ │ │ ├── index.ts │ │ │ │ │ └── utils │ │ │ │ │ │ └── index.ts │ │ │ │ ├── ai │ │ │ │ │ ├── index.ts │ │ │ │ │ └── utils │ │ │ │ │ │ └── index.ts │ │ │ │ ├── index.ts │ │ │ │ ├── jsEdit │ │ │ │ │ └── index.ts │ │ │ │ └── staticFilter │ │ │ │ │ ├── index.ts │ │ │ │ │ └── utils │ │ │ │ │ └── index.ts │ │ │ │ ├── live │ │ │ │ ├── channel.ts │ │ │ │ ├── index.ts │ │ │ │ ├── iptv.ts │ │ │ │ └── utils │ │ │ │ │ └── index.ts │ │ │ │ ├── parse │ │ │ │ ├── db.ts │ │ │ │ ├── index.ts │ │ │ │ └── work.ts │ │ │ │ ├── player │ │ │ │ ├── barrage.ts │ │ │ │ └── index.ts │ │ │ │ ├── plugin │ │ │ │ ├── index.ts │ │ │ │ ├── utils │ │ │ │ │ ├── adapter_install.ts │ │ │ │ │ ├── adapter_install_bak.ts │ │ │ │ │ ├── adapter_link.ts │ │ │ │ │ └── types.ts │ │ │ │ └── work.ts │ │ │ │ ├── proxy │ │ │ │ ├── index.ts │ │ │ │ └── work.ts │ │ │ │ ├── setting │ │ │ │ ├── index.ts │ │ │ │ └── work.ts │ │ │ │ ├── site │ │ │ │ ├── cms │ │ │ │ │ ├── adapter │ │ │ │ │ │ ├── appysv2.ts │ │ │ │ │ │ ├── catvod.ts │ │ │ │ │ │ ├── drpy │ │ │ │ │ │ │ ├── drpy3.ts │ │ │ │ │ │ │ ├── drpy3_bak.ts │ │ │ │ │ │ │ ├── drpyInject.ts │ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ │ ├── index_bak.ts │ │ │ │ │ │ │ ├── template.ts │ │ │ │ │ │ │ ├── utils │ │ │ │ │ │ │ │ ├── cheerio.min.ts │ │ │ │ │ │ │ │ ├── gbk.ts │ │ │ │ │ │ │ │ ├── jinja.js │ │ │ │ │ │ │ │ ├── jsencrypt.js │ │ │ │ │ │ │ │ ├── json5.js │ │ │ │ │ │ │ │ └── node-rsa.js │ │ │ │ │ │ │ ├── worker-test-case.ts │ │ │ │ │ │ │ ├── worker.ts │ │ │ │ │ │ │ └── worker_bak.ts │ │ │ │ │ │ ├── drpyjs0.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── py │ │ │ │ │ │ │ └── index.ts │ │ │ │ │ │ ├── t0Xml.ts │ │ │ │ │ │ ├── t1Json.ts │ │ │ │ │ │ ├── t4Hipy.ts │ │ │ │ │ │ ├── xbpq │ │ │ │ │ │ │ ├── adapter.ts │ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ │ ├── methods.ts │ │ │ │ │ │ │ └── rule.ts │ │ │ │ │ │ └── xyq │ │ │ │ │ │ │ ├── adapter.ts │ │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ └── utils │ │ │ │ │ │ ├── cmsFilter.ts │ │ │ │ │ │ ├── crypto.ts │ │ │ │ │ │ ├── htmlParser.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── jinja.ts │ │ │ │ │ │ └── ruleParse.ts │ │ │ │ ├── config │ │ │ │ │ └── app_filter.ts │ │ │ │ ├── db.ts │ │ │ │ ├── hot │ │ │ │ │ ├── adapter.ts │ │ │ │ │ └── index.ts │ │ │ │ ├── index.ts │ │ │ │ └── recomm │ │ │ │ │ ├── index.ts │ │ │ │ │ └── utils │ │ │ │ │ └── douban.ts │ │ │ │ ├── star │ │ │ │ ├── index.ts │ │ │ │ └── work.ts │ │ │ │ └── system │ │ │ │ ├── index.ts │ │ │ │ └── work.ts │ │ ├── shortcut │ │ │ ├── global.ts │ │ │ ├── index.ts │ │ │ └── local.ts │ │ ├── tray │ │ │ └── index.ts │ │ ├── update │ │ │ └── index.ts │ │ └── winManger │ │ │ └── index.ts │ ├── index.ts │ └── utils │ │ ├── crypto │ │ └── index.ts │ │ ├── hiker │ │ ├── base.ts │ │ ├── crypto.ts │ │ ├── file.ts │ │ ├── gbk.ts │ │ ├── htmlParser.ts │ │ ├── index.ts │ │ ├── paste.ts │ │ ├── path.ts │ │ ├── request.ts │ │ ├── store.ts │ │ ├── syncFetch.ts │ │ ├── syncRequest.ts │ │ ├── ua.ts │ │ └── xpath.ts │ │ ├── lrucache │ │ └── index.ts │ │ ├── request │ │ └── index.ts │ │ ├── schedule │ │ └── index.ts │ │ ├── sniffer │ │ └── index.ts │ │ ├── tool │ │ └── index.ts │ │ └── webdev │ │ └── index.ts ├── preload │ ├── index.d.ts │ ├── index.ts │ └── utils │ │ ├── dom.ts │ │ └── loading.ts └── renderer │ ├── auto-imports.d.ts │ ├── components.d.ts │ ├── index.html │ └── src │ ├── App.vue │ ├── api │ ├── analyze.ts │ ├── drive.ts │ ├── history.ts │ ├── index.ts │ ├── iptv.ts │ ├── lab.ts │ ├── plugin.ts │ ├── proxy.ts │ ├── setting.ts │ ├── site.ts │ └── star.ts │ ├── assets │ ├── ad │ │ ├── raincloud.png │ │ └── sp.png │ ├── ai │ │ ├── openai.png │ │ ├── openai_kimi.png │ │ └── user.png │ ├── assets-setting-auto.svg │ ├── assets-setting-dark.svg │ ├── assets-setting-light.svg │ ├── copy.svg │ ├── folder.png │ ├── font │ │ ├── JetBrainsMono-Regular.woff2 │ │ └── MiSans-Medium.woff2 │ ├── icon.png │ ├── lazy.png │ ├── pay │ │ ├── ali.webp │ │ ├── qr.png │ │ └── wechat.webp │ ├── platform │ │ ├── icon │ │ │ ├── 360.svg │ │ │ ├── bilibili.svg │ │ │ ├── iqiyi.svg │ │ │ ├── le.svg │ │ │ ├── mgtv.svg │ │ │ ├── pptv.svg │ │ │ ├── sohu.svg │ │ │ ├── tencent.svg │ │ │ └── youku.svg │ │ └── logo │ │ │ ├── 360.svg │ │ │ ├── bilibili.svg │ │ │ ├── iqiyi.svg │ │ │ ├── le.svg │ │ │ ├── mgtv.svg │ │ │ ├── pptv.svg │ │ │ ├── sohu.svg │ │ │ ├── tencent.svg │ │ │ └── youku.svg │ ├── player │ │ ├── pause.svg │ │ ├── pip.svg │ │ ├── play-next.svg │ │ ├── play.svg │ │ ├── playon-green.gif │ │ ├── setting.svg │ │ ├── voice-no.svg │ │ ├── voice.svg │ │ ├── zoom-s.svg │ │ └── zoom.svg │ ├── rectangle_common-1.svg │ └── rectangle_common.svg │ ├── components │ ├── code-editor │ │ ├── index.ts │ │ └── src │ │ │ ├── code-editor-types.ts │ │ │ ├── code-editor.less │ │ │ ├── code-editor.tsx │ │ │ └── composables │ │ │ └── use-code-editor.ts │ ├── common-nav │ │ └── index.vue │ ├── common-setting │ │ ├── note │ │ │ └── index.vue │ │ └── table │ │ │ └── index.vue │ ├── markdown-render │ │ ├── index.vue │ │ ├── style │ │ │ ├── github-markdown.less │ │ │ ├── highlight.less │ │ │ └── index.less │ │ └── types.ts │ ├── player │ │ ├── CHANGELOG.md │ │ ├── index.ts │ │ └── src │ │ │ ├── assets │ │ │ ├── css │ │ │ │ └── index.less │ │ │ └── img │ │ │ │ ├── bg-player-1.jpg │ │ │ │ ├── bg-player-2.jpg │ │ │ │ ├── bg-player-3.jpg │ │ │ │ ├── bg-player-4.jpg │ │ │ │ └── bg-player.jpg │ │ │ ├── core │ │ │ ├── artplayer │ │ │ │ ├── css │ │ │ │ │ └── index.css │ │ │ │ └── index.ts │ │ │ ├── dplayer │ │ │ │ ├── css │ │ │ │ │ └── index.css │ │ │ │ └── index.ts │ │ │ ├── index.ts │ │ │ ├── nplayer │ │ │ │ ├── css │ │ │ │ │ └── index.css │ │ │ │ └── index.ts │ │ │ ├── oplayer │ │ │ │ ├── css │ │ │ │ │ └── index.css │ │ │ │ └── index.ts │ │ │ ├── vlc │ │ │ │ ├── audio-worklet-processor.js │ │ │ │ ├── experimental.js │ │ │ │ ├── experimental.wasm │ │ │ │ ├── experimental.worker.js │ │ │ │ ├── libvlc.js │ │ │ │ └── vlc.js │ │ │ └── xgplayer │ │ │ │ ├── css │ │ │ │ └── index.css │ │ │ │ ├── index.ts │ │ │ │ └── plugins │ │ │ │ └── playNext │ │ │ │ └── index.ts │ │ │ ├── modules │ │ │ └── webtorrent.ts │ │ │ ├── multi-player.tsx │ │ │ └── utils │ │ │ ├── media-stream.ts │ │ │ ├── static.ts │ │ │ └── tool.ts │ ├── share-popup │ │ └── index.vue │ ├── shortcut-input │ │ └── index.vue │ ├── split │ │ ├── hooks │ │ │ ├── use-merge-state.ts │ │ │ └── use-state.ts │ │ ├── icon │ │ │ ├── drag-dot-vertical.svg │ │ │ └── drag-dot.svg │ │ ├── index.vue │ │ ├── interface.ts │ │ ├── resize-observer.tsx │ │ ├── resize-trigger.vue │ │ ├── style │ │ │ └── index.less │ │ └── utils │ │ │ ├── convert-case.ts │ │ │ ├── dom.ts │ │ │ ├── is.ts │ │ │ ├── types.ts │ │ │ └── vue-utils.ts │ ├── tag-nav │ │ └── index.vue │ ├── terminal │ │ └── index.vue │ └── title-menu │ │ ├── index.less │ │ └── index.vue │ ├── config │ ├── ai.ts │ ├── analyze.ts │ ├── doh.ts │ ├── global.ts │ ├── hotClass.ts │ ├── play.ts │ ├── system.ts │ └── ua.ts │ ├── env.d.ts │ ├── layouts │ ├── components │ │ ├── Content.vue │ │ ├── Header.vue │ │ ├── HistoryControl.vue │ │ ├── JustLook.vue │ │ ├── Lab.vue │ │ ├── Language.vue │ │ ├── PlayShow.vue │ │ ├── SearchBar.vue │ │ ├── SideNav.vue │ │ ├── Sponsor.vue │ │ ├── SystemConfig.vue │ │ ├── SystemControl.vue │ │ ├── SystemPin.vue │ │ └── SystemSkin.vue │ └── index.vue │ ├── locales │ ├── index.ts │ ├── lang │ │ ├── en_US │ │ │ ├── index.ts │ │ │ └── pages │ │ │ │ ├── analyze.ts │ │ │ │ ├── chase.ts │ │ │ │ ├── contextMenu.ts │ │ │ │ ├── drive.ts │ │ │ │ ├── film.ts │ │ │ │ ├── index.ts │ │ │ │ ├── iptv.ts │ │ │ │ ├── justlook.ts │ │ │ │ ├── lab.ts │ │ │ │ ├── md.ts │ │ │ │ ├── md │ │ │ │ ├── custom-player.md │ │ │ │ ├── privacy-policy.md │ │ │ │ └── thumbnail-ffmpeg.md │ │ │ │ ├── playShow.ts │ │ │ │ ├── player.ts │ │ │ │ ├── search.ts │ │ │ │ ├── setting.ts │ │ │ │ ├── share.ts │ │ │ │ ├── skin.ts │ │ │ │ └── sponsor.ts │ │ └── zh_CN │ │ │ ├── index.ts │ │ │ └── pages │ │ │ ├── analyze.ts │ │ │ ├── chase.ts │ │ │ ├── contextMenu.ts │ │ │ ├── drive.ts │ │ │ ├── film.ts │ │ │ ├── index.ts │ │ │ ├── iptv.ts │ │ │ ├── justlook.ts │ │ │ ├── lab.ts │ │ │ ├── md.ts │ │ │ ├── md │ │ │ ├── custom-player.md │ │ │ ├── privacy-policy.md │ │ │ └── thumbnail-ffmpeg.md │ │ │ ├── playShow.ts │ │ │ ├── player.ts │ │ │ ├── search.ts │ │ │ ├── setting.ts │ │ │ ├── share.ts │ │ │ ├── skin.ts │ │ │ └── sponsor.ts │ └── useLocale.ts │ ├── main.ts │ ├── pages │ ├── Disclaimer.vue │ ├── analyze │ │ ├── components │ │ │ ├── DialogHistory.vue │ │ │ ├── DialogIframe.vue │ │ │ └── DialogSearch.vue │ │ ├── index.vue │ │ └── index_bak.vue │ ├── chase │ │ ├── components │ │ │ ├── binge │ │ │ │ └── index.vue │ │ │ └── history │ │ │ │ └── index.vue │ │ └── index.vue │ ├── drive │ │ └── index.vue │ ├── film │ │ ├── components │ │ │ └── Detail.vue │ │ └── index.vue │ ├── iptv │ │ └── index.vue │ ├── lab │ │ ├── components │ │ │ ├── aiBrain │ │ │ │ ├── index.vue │ │ │ │ └── index_bak.vue │ │ │ ├── dataCrypto │ │ │ │ ├── components │ │ │ │ │ ├── codeConversion.vue │ │ │ │ │ ├── encodeDecode.vue │ │ │ │ │ └── hashCalculation.vue │ │ │ │ └── index.vue │ │ │ ├── fileDiff │ │ │ │ └── index.vue │ │ │ ├── jsEdit │ │ │ │ ├── index.vue │ │ │ │ ├── index_t3.vue │ │ │ │ └── utils │ │ │ │ │ ├── crypto.ts │ │ │ │ │ ├── drpy_object_inner.ts │ │ │ │ │ └── drpy_suggestions.ts │ │ │ ├── pluginCenter │ │ │ │ └── index.vue │ │ │ ├── reqHtml │ │ │ │ └── index.vue │ │ │ ├── snifferPlay │ │ │ │ └── index.vue │ │ │ └── staticFilter │ │ │ │ └── index.vue │ │ └── index.vue │ ├── play │ │ ├── componets │ │ │ ├── AsideAnalyze.vue │ │ │ ├── AsideDrive.vue │ │ │ ├── AsideFilm.vue │ │ │ ├── AsideIptv.vue │ │ │ ├── DialogDownload.vue │ │ │ ├── DialogSetting.vue │ │ │ └── Header.vue │ │ ├── index.less │ │ └── index.vue │ ├── setting │ │ ├── components │ │ │ ├── analyze │ │ │ │ ├── components │ │ │ │ │ ├── DialogFlag.vue │ │ │ │ │ └── DialogForm.vue │ │ │ │ ├── constants.ts │ │ │ │ └── index.vue │ │ │ ├── base │ │ │ │ ├── components │ │ │ │ │ ├── DialogBarrage.vue │ │ │ │ │ ├── DialogCustomPlayer.vue │ │ │ │ │ ├── DialogData.vue │ │ │ │ │ ├── DialogDns.vue │ │ │ │ │ ├── DialogSniffer.vue │ │ │ │ │ ├── DialogThumbnail.vue │ │ │ │ │ ├── DialogUa.vue │ │ │ │ │ └── DialogUpdate.vue │ │ │ │ └── index.vue │ │ │ ├── drive │ │ │ │ ├── components │ │ │ │ │ ├── DialogAliAuth.vue │ │ │ │ │ └── DialogForm.vue │ │ │ │ ├── constants.ts │ │ │ │ └── index.vue │ │ │ ├── iptv │ │ │ │ ├── components │ │ │ │ │ └── DialogForm.vue │ │ │ │ ├── constants.ts │ │ │ │ └── index.vue │ │ │ └── site │ │ │ │ ├── components │ │ │ │ └── DialogForm.vue │ │ │ │ ├── constants.ts │ │ │ │ └── index.vue │ │ └── index.vue │ └── test │ │ ├── assets │ │ └── demo.mp4 │ │ ├── demo │ │ ├── aliplayer.html │ │ ├── artplayer.html │ │ ├── dplayer.html │ │ ├── griffith.html │ │ ├── veplayer.html │ │ └── xgplayer.html │ │ └── index.vue │ ├── router │ ├── index.ts │ └── modules │ │ └── homepage.ts │ ├── store │ ├── index.ts │ ├── modules │ │ ├── play.ts │ │ └── setting.ts │ └── plugins │ │ └── pinia-plugin-sync.ts │ ├── style │ ├── dialog.less │ ├── font-family.less │ ├── index.less │ ├── layout.less │ ├── normalize.less │ ├── player │ │ ├── artplayer.css │ │ ├── dplayer.css │ │ ├── index.less │ │ ├── nplayer.css │ │ └── veplayer.css │ ├── reset.less │ ├── theme │ │ ├── index.less │ │ └── theme.less │ └── variables.less │ ├── types │ ├── axios.d.ts │ ├── dplayer.d.ts │ ├── globals.d.ts │ └── interface.d.ts │ └── utils │ ├── channel.ts │ ├── common │ ├── chase.ts │ └── film.ts │ ├── crypto │ ├── algorithm.ts │ ├── encoding.ts │ ├── index.ts │ ├── index_bak.ts │ └── utils.ts │ ├── emitter.ts │ ├── request.ts │ ├── sniffer.ts │ └── tool.ts ├── stylelint.config.js ├── tsconfig.json ├── tsconfig.node.json └── tsconfig.web.json /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | end_of_line = lf 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | 10 | [*.md] 11 | trim_trailing_whitespace = false 12 | 13 | [*.{ts,js,vue,css}] 14 | indent_size = 2 15 | -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | ELECTRON_WEB_SERVER_PORT = 42710 2 | ELECTRON_DEV_NETEASE_API_PORT = 30001 3 | VITE_API_URL = http://127.0.0.1:9978 4 | VITE_API_URL_PREFIX = /api -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | out 4 | .gitignore 5 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug-report.zh-CN.yml: -------------------------------------------------------------------------------- 1 | name: 反馈 Bug 2 | description: 通过 github 模板进行 Bug 反馈。 3 | title: "[组件名称] 描述问题的标题" 4 | body: 5 | - type: markdown 6 | attributes: 7 | value: | 8 | # 欢迎你的参与 9 | Issue 列表接受 bug 报告或是新功能请求。 10 | 11 | 在发布一个 Issue 前,请确保: 12 | - 在 [旧Issue列表](https://github.com/Hiram-Wong/ZyPlayer/issues?q=is%3Aissue) 中搜索过你的问题。(你的问题可能已有人提出,也可能已在最新版本中被修正) 13 | - 如果你发现一个已经关闭的旧 Issue 在最新版本中仍然存在,不要在旧 Issue 下面留言,请建一个新的 issue。 14 | 15 | - type: input 16 | id: version 17 | attributes: 18 | label: zyfun 版本 19 | description: 请检查在最新项目版本中能否重现此 issue。 20 | placeholder: 请填写 21 | validations: 22 | required: true 23 | 24 | - type: textarea 25 | id: reproduceSteps 26 | attributes: 27 | label: 重现步骤 28 | description: 请清晰的描述重现该 Issue 的步骤,这能帮助我们快速定位问题。没有清晰重现步骤将不会被修复,标有 'need reproduction' 的 Issue 在 7 天内不提供相关步骤,将被关闭。 29 | placeholder: 请填写 30 | validations: 31 | required: true 32 | 33 | - type: textarea 34 | id: errorMessage 35 | attributes: 36 | label: 报错信息 37 | description: F12, 重新复现后, 复制控制台报错信息。 38 | placeholder: 请填写 39 | 40 | - type: textarea 41 | id: expect 42 | attributes: 43 | label: 期望结果 44 | placeholder: 请填写 45 | 46 | - type: textarea 47 | id: actual 48 | attributes: 49 | label: 实际结果 50 | placeholder: 请填写 51 | 52 | - type: input 53 | id: systemInfo 54 | attributes: 55 | label: 系统信息 56 | placeholder: MacOS(11.2.3)系统 M1芯片 arm64架构 57 | 58 | - type: textarea 59 | id: remarks 60 | attributes: 61 | label: 补充说明 62 | description: 可以是遇到这个 bug 的业务场景、上下文等信息。 63 | placeholder: 请填写 64 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature-report.zh-CN.yml: -------------------------------------------------------------------------------- 1 | name: 反馈新功能 2 | description: 通过 github 模板进行新功能反馈。 3 | title: "[组件名称] 描述问题的标题" 4 | body: 5 | - type: markdown 6 | attributes: 7 | value: | 8 | # 欢迎你的参与 9 | Issue 列表接受 bug 报告或是新功能请求。 10 | 11 | 在发布一个 Issue 前,请确保: 12 | - 在 [旧Issue列表](https://github.com/Hiram-Wong/ZyPlayer/issues?q=is%3Aissue) 中搜索过你的问题。(你的问题可能已有人提出,也可能已在最新版本中被修正) 13 | - 如果你发现一个已经关闭的旧 Issue 在最新版本中仍然存在,不要在旧 Issue 下面留言,请建一个新的 issue。 14 | 15 | - type: textarea 16 | id: functionContent 17 | attributes: 18 | label: 这个功能解决了什么问题 19 | description: 请详尽说明这个需求的用例和场景。最重要的是:解释清楚是怎样的用户体验需求催生了这个功能上的需求。我们将考虑添加在现有 API 无法轻松实现的功能。新功能的用例也应当足够常见。 20 | placeholder: 请填写 21 | validations: 22 | required: true 23 | 24 | - type: textarea 25 | id: functionalExpectations 26 | attributes: 27 | label: 你建议的方案是什么 28 | description: 请详尽说明这个需求的实现的方案。例如相关Electron API。 29 | placeholder: 请填写 30 | validations: 31 | required: true 32 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 6 | 7 | ### 🤔 这个 PR 的性质是? 8 | 9 | - [ ] 日常 bug 修复 10 | - [ ] 新特性提交 11 | - [ ] 文档改进 12 | - [ ] 组件样式/交互改进 13 | - [ ] 重构 14 | - [ ] 代码风格优化 15 | - [ ] 测试用例 16 | - [ ] 分支合并 17 | - [ ] 其他 18 | 19 | ### 🔗 相关 Issue 20 | 21 | 24 | 25 | ### 💡 需求背景和解决方案 26 | 27 | 32 | 33 | ### 📝 更新日志 34 | 35 | 38 | 39 | - fix(组件名称): 处理问题或特性描述 ... 40 | 41 | - [ ] 本条 PR 不需要纳入 Changelog 42 | 43 | ### ☑️ 请求合并前的自查清单 44 | 45 | ⚠️ 请自检并全部**勾选全部选项**。⚠️ 46 | 47 | - [ ] 文档已补充或无须补充 48 | - [ ] 代码演示已提供或无须提供 49 | - [ ] TypeScript 定义已补充或无须补充 50 | - [ ] Changelog 已提供或无须提供 -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Build/release 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | release: 10 | runs-on: ${{ matrix.os }} 11 | 12 | strategy: 13 | fail-fast: false 14 | matrix: 15 | os: [windows-latest, macos-latest, ubuntu-latest] 16 | 17 | steps: 18 | - name: Check out Git repository 19 | uses: actions/checkout@v4 20 | with: 21 | submodules: "recursive" 22 | 23 | - name: Install Node.js 24 | uses: actions/setup-node@v4 25 | with: 26 | node-version: 'latest' 27 | # cache: 'yarn' 28 | 29 | - name: Build/release Electron app 30 | uses: samuelmeuli/action-electron-builder@v1.6.0 31 | with: 32 | # GitHub token, automatically provided to the action 33 | # (No need to define this secret in the repo settings) 34 | github_token: ${{ secrets.access_token }} 35 | # build_script_name: 'electron:prebuild' 36 | # If the commit is tagged with a version (e.g. "v1.0.0"), 37 | # release the app after building 38 | release: ${{ startsWith(github.ref, 'refs/tags/v') }} 39 | 40 | - name: Upload Artifact (macOS) 41 | uses: actions/upload-artifact@v4 42 | with: 43 | name: zyplayer-mac 44 | path: ./dist/*-mac-*.dmg 45 | if-no-files-found: ignore 46 | 47 | - name: Upload Artifact (Windows) 48 | uses: actions/upload-artifact@v4 49 | with: 50 | name: zyplayer-win 51 | path: ./dist/*-win-*.exe 52 | if-no-files-found: ignore 53 | 54 | - name: Upload Artifact (Linux) 55 | uses: actions/upload-artifact@v4 56 | with: 57 | name: zyplayer-linux 58 | path: | 59 | ./dist/*.AppImage 60 | ./dist/*.deb 61 | ./dist/*.rpm 62 | if-no-files-found: ignore 63 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # OS specific files 2 | .DS_Store 3 | 4 | # dependencies manager 5 | node_modules/ 6 | .pnp.* 7 | .yarn/* 8 | !.yarn/patches 9 | !.yarn/plugins 10 | !.yarn/releases 11 | !.yarn/sdks 12 | !.yarn/versions 13 | 14 | # build files 15 | es/ 16 | lib/ 17 | dist/ 18 | out/ 19 | typings/ 20 | 21 | _site 22 | package 23 | tmp* 24 | temp* 25 | coverage 26 | test-report.html 27 | .idea/ 28 | yarn-error.log 29 | *.zip 30 | .history 31 | .stylelintcache 32 | 33 | .env.local 34 | .env.*.local 35 | 36 | # dev files 37 | thumbnail/ 38 | *.pyc 39 | __pycache__/ 40 | 41 | # lock文件 42 | yarn.lock 43 | package-lock.json 44 | pnpm-lock.yaml 45 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | if [[ "$OS" == "Windows_NT" ]]; then 5 | npx.cmd --no-install commitlint -e $GIT_PARAMS 6 | else 7 | npx --no-install commitlint -e $GIT_PARAMS 8 | fi -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | if [[ "$OS" == "Windows_NT" ]]; then 5 | npx.cmd lint-staged 6 | else 7 | npx lint-staged 8 | fi -------------------------------------------------------------------------------- /.husky/prepare-commit-msg: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | [[ "$(uname -a)" = *"MINGW64"* ]] && exit 0 3 | [ -n "$CI" ] && exit 0 4 | . "$(dirname "$0")/_/husky.sh" 5 | 6 | if [[ "$OS" == "Windows_NT" ]]; then 7 | exec < /dev/tty && npx.cmd git-cz --hook || true 8 | else 9 | exec < /dev/tty && npx git-cz --hook || true 10 | fi 11 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | registry=https://registry.npmmirror.com 2 | # electron_mirror=https://npmmirror.com/mirrors/electron/ 3 | # electron_builder_binaries_mirror=https://npmmirror.com/mirrors/electron-builder-binaries/ 4 | shamefully-hoist=true 5 | hoist=true 6 | engine-strict=true 7 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | out 2 | dist 3 | pnpm-lock.yaml 4 | LICENSE.md 5 | tsconfig.json 6 | tsconfig.*.json 7 | -------------------------------------------------------------------------------- /.prettierrc.yaml: -------------------------------------------------------------------------------- 1 | # 每行的最大打印宽度 2 | printWidth: 120 3 | # 使用 2 个空格缩进 4 | tabWidth: 2 5 | # 不使用缩进符,而使用空格 6 | useTabs: false 7 | # 行尾需要有分号 8 | semi: true 9 | # 使用单引号 10 | singleQuote: true 11 | # 对象的 key 仅在必要时用引号 12 | quoteProps: as-needed 13 | # jsx 不使用单引号,而使用双引号 14 | jsxSingleQuote: false 15 | # 末尾需要有逗号 16 | trailingComma: all 17 | # 大括号内的首尾需要空格 18 | bracketSpacing: true 19 | # jsx 标签的反尖括号需要换行 20 | jsxBracketSameLine: false 21 | # 箭头函数,只有一个参数的时候,也需要括号 22 | arrowParens: always 23 | # 每个文件格式化的范围是文件的全部内容 24 | rangeStart: 0 25 | # rangeEnd: Infinity 26 | # 不需要写文件开头的 @prettier 27 | requirePragma: false 28 | # 不需要自动在文件开头插入 @prettier 29 | insertPragma: false 30 | # 使用默认的折行标准 31 | proseWrap: preserve 32 | # 根据显示样式决定 html 要不要折行 33 | htmlWhitespaceSensitivity: css 34 | # vue 文件中的 script 和 style 内不用缩进 35 | vueIndentScriptAndStyle: false 36 | # 换行符使用 lf 37 | endOfLine: lf 38 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["dbaeumer.vscode-eslint"] 3 | } 4 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Debug Main Process", 6 | "type": "node", 7 | "request": "launch", 8 | "cwd": "${workspaceRoot}", 9 | "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron-vite", 10 | "windows": { 11 | "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron-vite.cmd" 12 | }, 13 | "runtimeArgs": ["--sourcemap"], 14 | "env": { 15 | "REMOTE_DEBUGGING_PORT": "9222" 16 | } 17 | }, 18 | { 19 | "name": "Debug Renderer Process", 20 | "port": 9222, 21 | "request": "attach", 22 | "type": "chrome", 23 | "webRoot": "${workspaceFolder}/src/renderer", 24 | "timeout": 60000, 25 | "presentation": { 26 | "hidden": true 27 | } 28 | } 29 | ], 30 | "compounds": [ 31 | { 32 | "name": "Debug All", 33 | "configurations": ["Debug Main Process", "Debug Renderer Process"], 34 | "presentation": { 35 | "order": 1 36 | } 37 | } 38 | ] 39 | } 40 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.eol":"\n", 3 | "editor.tabSize": 2, 4 | "eslint.format.enable": true, 5 | "eslint.validate": ["javascript", "javascriptreact", "typescript", "typescriptreact", "vue"], 6 | "[vue]": { 7 | "editor.formatOnSave": true, 8 | "editor.defaultFormatter": "dbaeumer.vscode-eslint" 9 | }, 10 | "[typescriptreact]": { 11 | "editor.formatOnSave": true, 12 | "editor.defaultFormatter": "dbaeumer.vscode-eslint" 13 | }, 14 | "[javascriptreact]": { 15 | "editor.formatOnSave": true, 16 | "editor.defaultFormatter": "dbaeumer.vscode-eslint" 17 | }, 18 | "[typescript]": { 19 | "editor.formatOnSave": true, 20 | "editor.defaultFormatter": "dbaeumer.vscode-eslint" 21 | }, 22 | "[javascript]": { 23 | "editor.formatOnSave": true, 24 | "editor.defaultFormatter": "dbaeumer.vscode-eslint" 25 | }, 26 | "html.customData": [ 27 | "./node_modules/@electron-uikit/titlebar/custom-elements.json" 28 | ], 29 | "i18n-ally.localesPaths": [ 30 | "src/renderer/src/locales", 31 | "src/renderer/src/locales/lang" 32 | ] 33 | } 34 | -------------------------------------------------------------------------------- /.yarnclean: -------------------------------------------------------------------------------- 1 | # test directories 2 | __tests__ 3 | test 4 | tests 5 | powered-test 6 | 7 | # asset directories 8 | docs 9 | doc 10 | website 11 | images 12 | 13 | 14 | # examples 15 | example 16 | examples 17 | 18 | # code coverage directories 19 | coverage 20 | .nyc_output 21 | 22 | # build scripts 23 | Makefile 24 | Gulpfile.js 25 | Gruntfile.js 26 | 27 | # configs 28 | appveyor.yml 29 | circle.yml 30 | codeship-services.yml 31 | codeship-steps.yml 32 | wercker.yml 33 | .tern-project 34 | .gitattributes 35 | .editorconfig 36 | .*ignore 37 | .eslintrc 38 | .jshintrc 39 | .flowconfig 40 | .documentup.json 41 | .yarn-metadata.json 42 | .travis.yml 43 | 44 | # misc 45 | *.md 46 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Hiram-Wong 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /build/entitlements.mac.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.cs.allow-jit 6 | 7 | com.apple.security.cs.allow-unsigned-executable-memory 8 | 9 | com.apple.security.cs.allow-dyld-environment-variables 10 | 11 | com.apple.security.cs.disable-library-validation 12 | 13 | 14 | -------------------------------------------------------------------------------- /build/hook/notarize.js: -------------------------------------------------------------------------------- 1 | const { notarize } = require('@electron/notarize') 2 | 3 | module.exports = async (context) => { 4 | if (process.platform !== 'darwin') return 5 | 6 | console.log('aftersign hook triggered, start to notarize app.') 7 | 8 | if (!process.env.CI) { 9 | console.log(`skipping notarizing, not in CI.`) 10 | return 11 | } 12 | 13 | if (!('APPLE_ID' in process.env && 'APPLE_ID_PASS' in process.env)) { 14 | console.warn('skipping notarizing, APPLE_ID and APPLE_ID_PASS env variables must be set.') 15 | return 16 | } 17 | 18 | const appId = 'com.zyfun.player' 19 | 20 | const { appOutDir } = context 21 | 22 | const appName = context.packager.appInfo.productFilename 23 | 24 | try { 25 | await notarize({ 26 | appBundleId: appId, 27 | appPath: `${appOutDir}/${appName}.app`, 28 | appleId: process.env.APPLE_ID, 29 | appleIdPassword: process.env.APPLEIDPASS 30 | }) 31 | } catch (error) { 32 | console.error(error) 33 | } 34 | 35 | console.log(`done notarizing ${appId}.`) 36 | } 37 | -------------------------------------------------------------------------------- /build/hook/patch-monaco-clipboard.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Monaco Editor Clipboard Patch for Electron 3 | * 4 | * Simple patch to fix clipboard functionality in Monaco Editor when running in Electron 5 | * GitHub Issue: 6 | * - https://github.com/microsoft/monaco-editor/issues/1046 7 | * - https://github.com/microsoft/monaco-editor-webpack-plugin/issues/17#issuecomment-408303369 8 | * - https://github.com/microsoft/monaco-editor/issues/4855 9 | */ 10 | 11 | const fs = require('fs') 12 | 13 | // Target file path 14 | const clipboardFile = 'node_modules/monaco-editor/esm/vs/editor/contrib/clipboard/browser/clipboard.js' 15 | if (fs.existsSync(clipboardFile)) { 16 | console.log(`🔍 Processing file: ${clipboardFile}`) 17 | let content = fs.readFileSync(clipboardFile, 'utf8') 18 | 19 | // Remove platform.isWeb check to make clipboard functionality work in Electron 20 | content = content.replace( 21 | /if \(!result && platform\.isWeb\) {/g, 22 | '/* MQTTX Patch: Remove platform.isWeb check to fix paste in Electron\n' + 23 | ' Issue: https://github.com/microsoft/monaco-editor/issues/4855\n' + 24 | " Electron (especially v34+) doesn't support execCommand('paste') and isn't detected as web platform */\n" + 25 | ' if (!result) {', 26 | ) 27 | 28 | fs.writeFileSync(clipboardFile, content) 29 | console.log(`✅ Monaco clipboard patch applied successfully!`) 30 | } else { 31 | console.log(`⚠️ File not found: ${clipboardFile}, please run yarn install first`) 32 | } 33 | 34 | console.log('🎉 Monaco Editor clipboard patch completed! Clipboard functionality should now work properly in Electron.') 35 | -------------------------------------------------------------------------------- /build/hook/removeLocales.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 移除部分用不到的语言、优化electron包的大小 3 | */ 4 | 5 | const fs = require("fs"); 6 | 7 | exports.default = async function (context) { 8 | let localeDir = `${context.appOutDir}/locales/`; 9 | // if (context.electronPlatformName === 'darwin') localeDir = `${context.appOutDir}/zyfun.app/Contents/Resources/`; 10 | 11 | fs.readdir(localeDir, function (err, files) { 12 | // files is array of filenames (basename form) 13 | if (!(files && files.length)) return; 14 | for (let i = 0, len = files.length; i < len; i++) { 15 | // zh 和 en 开头的都不删 16 | if (!(files[i].startsWith("en") || files[i].startsWith("zh"))) { 17 | fs.unlinkSync(localeDir + files[i]); 18 | } 19 | } 20 | }); 21 | }; 22 | -------------------------------------------------------------------------------- /build/icons/1024x1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hiram-Wong/ZyPlayer/d61392132094d06a103c9f9c979e8fe88b0810df/build/icons/1024x1024.png -------------------------------------------------------------------------------- /build/icons/128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hiram-Wong/ZyPlayer/d61392132094d06a103c9f9c979e8fe88b0810df/build/icons/128x128.png -------------------------------------------------------------------------------- /build/icons/16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hiram-Wong/ZyPlayer/d61392132094d06a103c9f9c979e8fe88b0810df/build/icons/16x16.png -------------------------------------------------------------------------------- /build/icons/24x24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hiram-Wong/ZyPlayer/d61392132094d06a103c9f9c979e8fe88b0810df/build/icons/24x24.png -------------------------------------------------------------------------------- /build/icons/256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hiram-Wong/ZyPlayer/d61392132094d06a103c9f9c979e8fe88b0810df/build/icons/256x256.png -------------------------------------------------------------------------------- /build/icons/32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hiram-Wong/ZyPlayer/d61392132094d06a103c9f9c979e8fe88b0810df/build/icons/32x32.png -------------------------------------------------------------------------------- /build/icons/48x48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hiram-Wong/ZyPlayer/d61392132094d06a103c9f9c979e8fe88b0810df/build/icons/48x48.png -------------------------------------------------------------------------------- /build/icons/512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hiram-Wong/ZyPlayer/d61392132094d06a103c9f9c979e8fe88b0810df/build/icons/512x512.png -------------------------------------------------------------------------------- /build/icons/64x64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hiram-Wong/ZyPlayer/d61392132094d06a103c9f9c979e8fe88b0810df/build/icons/64x64.png -------------------------------------------------------------------------------- /build/icons/logo.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hiram-Wong/ZyPlayer/d61392132094d06a103c9f9c979e8fe88b0810df/build/icons/logo.icns -------------------------------------------------------------------------------- /build/icons/logo.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hiram-Wong/ZyPlayer/d61392132094d06a103c9f9c979e8fe88b0810df/build/icons/logo.ico -------------------------------------------------------------------------------- /build/icons/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hiram-Wong/ZyPlayer/d61392132094d06a103c9f9c979e8fe88b0810df/build/icons/logo.png -------------------------------------------------------------------------------- /build/icons/mac_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hiram-Wong/ZyPlayer/d61392132094d06a103c9f9c979e8fe88b0810df/build/icons/mac_logo.png -------------------------------------------------------------------------------- /build/installer.nsh: -------------------------------------------------------------------------------- 1 | !macro preInit 2 | # electron win自定义安装路径完美版 3 | Var /GLOBAL installDir 4 | SetRegView 64 5 | ReadRegStr $installDir HKLM "${INSTALL_REGISTRY_KEY}" InstallLocation 6 | ${if} $installDir == "" 7 | WriteRegExpandStr HKLM "${INSTALL_REGISTRY_KEY}" InstallLocation "D:\zyplayer" 8 | ${endif} 9 | 10 | ReadRegStr $installDir HKCU "${INSTALL_REGISTRY_KEY}" InstallLocation 11 | ${if} $installDir == "" 12 | WriteRegExpandStr HKCU "${INSTALL_REGISTRY_KEY}" InstallLocation "D:\zyplayer" 13 | ${endif} 14 | 15 | # SetRegView 32 almost ... 16 | !macroend 17 | 18 | !macro customRemoveFiles 19 | ${if} ${isUpdated} 20 | !insertmacro quitSuccess 21 | ${else} 22 | RMDir /r $INSTDIR 23 | ${endIf} 24 | !macroend 25 | 26 | !macro customUnWelcomePage 27 | !define MUI_WELCOMEPAGE_TITLE "卸载本软件" 28 | !define MUI_WELCOMEPAGE_TEXT "卸载本软件后会删除本软件的所有数据。请备份好重要数据!!!" 29 | !insertmacro MUI_UNPAGE_WELCOME 30 | !macroend -------------------------------------------------------------------------------- /desgin/player.sketch: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hiram-Wong/ZyPlayer/d61392132094d06a103c9f9c979e8fe88b0810df/desgin/player.sketch -------------------------------------------------------------------------------- /desgin/video_platform.sketch: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hiram-Wong/ZyPlayer/d61392132094d06a103c9f9c979e8fe88b0810df/desgin/video_platform.sketch -------------------------------------------------------------------------------- /dev-app-update.yml: -------------------------------------------------------------------------------- 1 | provider: github 2 | owner: Hiram-Wong 3 | repo: ZyPlayer 4 | updaterCacheDirName: zyplayer-updater 5 | -------------------------------------------------------------------------------- /resources/img/icons/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hiram-Wong/ZyPlayer/d61392132094d06a103c9f9c979e8fe88b0810df/resources/img/icons/logo.png -------------------------------------------------------------------------------- /resources/img/icons/tray_dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hiram-Wong/ZyPlayer/d61392132094d06a103c9f9c979e8fe88b0810df/resources/img/icons/tray_dark.png -------------------------------------------------------------------------------- /resources/img/icons/tray_light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hiram-Wong/ZyPlayer/d61392132094d06a103c9f9c979e8fe88b0810df/resources/img/icons/tray_light.png -------------------------------------------------------------------------------- /resources/t3PyBase/base/localProxy.py: -------------------------------------------------------------------------------- 1 | class Proxy: 2 | def getUrl(self, local): 3 | return 'http://127.0.0.1:9978' 4 | 5 | def getPort(self): 6 | return 9978 7 | -------------------------------------------------------------------------------- /src/main/core/db/common/client.ts: -------------------------------------------------------------------------------- 1 | import { PGlite } from '@electric-sql/pglite'; 2 | import { drizzle } from 'drizzle-orm/pglite'; 3 | // import { migrate } from 'drizzle-orm/pglite/migrator'; 4 | import * as schema from './schema'; 5 | import { APP_DB_PATH } from '@main/utils/hiker/path'; 6 | 7 | const DB_PATH = APP_DB_PATH; 8 | 9 | const client = new PGlite(DB_PATH); 10 | const db = drizzle({ client, schema }); 11 | 12 | // const migrateAfterClientReady = async () => { 13 | // if (!client.ready) await client.waitReady; 14 | // await migrate(db, { 15 | // migrationsFolder: join(DB_PATH, '/db/drizzle/'), // set to your drizzle generated path 16 | // migrationsSchema: join(DB_PATH, '/db/schema'), // set to your schema path 17 | // migrationsTable: '__migrations', 18 | // }); 19 | // }; 20 | // migrateAfterClientReady(); 21 | 22 | const server = async () => { 23 | // @ts-ignore 24 | const { createServer } = await import('pglite-server'); 25 | await client.waitReady; 26 | const PORT = 5432; 27 | const pgServer = createServer(client); 28 | pgServer.listen(PORT, () => {}); 29 | }; 30 | 31 | export { client, db, server }; 32 | -------------------------------------------------------------------------------- /src/main/core/db/common/curd.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hiram-Wong/ZyPlayer/d61392132094d06a103c9f9c979e8fe88b0810df/src/main/core/db/common/curd.ts -------------------------------------------------------------------------------- /src/main/core/db/common/index.ts: -------------------------------------------------------------------------------- 1 | import { client, db, server } from './client'; 2 | import * as schema from './schema'; 3 | import * as webdev from './webdev'; 4 | 5 | export { client, db, schema, server, webdev }; 6 | -------------------------------------------------------------------------------- /src/main/core/db/common/webdev.ts: -------------------------------------------------------------------------------- 1 | import logger from '@main/core/logger'; 2 | import webdev from '@main/utils/webdev'; 3 | import { setting, db } from '@main/core/db/service'; 4 | import { registerSchedule, runSchedule } from '@main/utils/schedule'; 5 | 6 | const syncWebdev = async () => { 7 | logger.info('[webdev][sync][start] try'); 8 | 9 | try { 10 | const dbResWebdev = await setting.get('webdev'); 11 | if (!dbResWebdev || !dbResWebdev.sync) { 12 | logger.info('[webdev][sync][skip] not open sync'); 13 | return; 14 | } 15 | 16 | const webdevConfig = dbResWebdev.data; 17 | if (!webdevConfig.url || !webdevConfig.username || !webdevConfig.password) { 18 | logger.info('[webdev][sync][fail] incomplete config'); 19 | return; 20 | } 21 | 22 | let instance: webdev | null = new webdev({ 23 | url: webdevConfig.url, 24 | username: webdevConfig.username, 25 | password: webdevConfig.password, 26 | }); 27 | 28 | const initRes = await instance.initializeWebdavClient(); 29 | if (initRes) { 30 | const doc = await db.all(); 31 | await instance.rsyncRemote(doc); 32 | instance = null; // 释放内存 33 | logger.info('[webdev][sync][success]'); 34 | } else { 35 | logger.info('[webdev][sync][fail] init error'); 36 | } 37 | } catch (err: any) { 38 | logger.info(`[webdev][sync][fail] ${err.message}`); 39 | } 40 | }; 41 | 42 | const cronSyncWebdev = () => { 43 | registerSchedule({ 44 | name: 'syncWebdev', 45 | fun: syncWebdev, 46 | interval: 5 * 60 * 1000 47 | }); 48 | runSchedule('syncWebdev'); 49 | }; 50 | 51 | export { syncWebdev, cronSyncWebdev }; 52 | -------------------------------------------------------------------------------- /src/main/core/db/index.ts: -------------------------------------------------------------------------------- 1 | import { compare } from 'compare-versions'; 2 | import { eq } from 'drizzle-orm'; 3 | import { app } from 'electron'; 4 | import { db, schema, server, webdev } from './common'; 5 | import migration from './migration'; 6 | import * as service from './service'; 7 | import logger from '@main/core/logger'; 8 | 9 | const updates = [ 10 | { version: '3.3.2', update: migration.update3_3_1to3_3_2 }, 11 | { version: '3.3.4', update: migration.update3_3_3to3_3_4 }, 12 | { version: '3.3.5', update: migration.update3_3_4to3_3_5 }, 13 | { version: '3.3.7', update: migration.update3_3_6to3_3_7 }, 14 | { version: '3.3.8', update: migration.update3_3_7to3_3_8 }, 15 | { version: '3.3.9', update: migration.update3_3_8to3_3_9 }, 16 | { version: '3.3.10', update: migration.update3_3_9to3_3_10 }, 17 | { version: '3.4.0', update: migration.update3_3_10to3_4_0 }, 18 | ]; 19 | 20 | const magrite = async () => { 21 | const tbl_setting_exit = await db.execute( 22 | `SELECT table_name FROM information_schema.tables WHERE table_schema = 'public' AND table_name = 'tbl_setting'`, 23 | ); 24 | if (tbl_setting_exit.rows.length === 0) { 25 | await migration.update0_0_0to3_3_1(); 26 | } 27 | 28 | let dbVersion = (await db.select().from(schema.setting).where(eq(schema.setting.key, 'version')))?.[0]?.['value']?.[ 29 | 'data' 30 | ]; 31 | 32 | const appVersion = app.getVersion(); 33 | logger.info(`[db][magrite][version] db:${dbVersion} - app:${appVersion}`); 34 | 35 | if (compare(dbVersion, appVersion, '<')) { 36 | for (const { version, update } of updates) { 37 | if (compare(dbVersion, version, '<')) { 38 | try { 39 | await update(); 40 | dbVersion = version; 41 | logger.info(`[db][magrite] ${version} success`); 42 | } catch (err) { 43 | logger.error(`[db][magrite] ${version} failed`, err); 44 | } 45 | } 46 | } 47 | } 48 | 49 | logger.info(`[db][magrite] completed`); 50 | }; 51 | 52 | const setup = async () => { 53 | await magrite(); 54 | }; 55 | 56 | export { db, schema, setup, magrite, server, service, webdev }; 57 | -------------------------------------------------------------------------------- /src/main/core/db/migration/index.ts: -------------------------------------------------------------------------------- 1 | import update0_0_0to3_3_1 from './modules/update0_0_0to3_3_1'; 2 | import update3_3_1to3_3_2 from './modules/update3_3_1to3_3_2'; 3 | import update3_3_3to3_3_4 from './modules/update3_3_3to3_3_4'; 4 | import update3_3_4to3_3_5 from './modules/update3_3_4to3_3_5'; 5 | import update3_3_6to3_3_7 from './modules/update3_3_6to3_3_7'; 6 | import update3_3_7to3_3_8 from './modules/update3_3_7to3_3_8'; 7 | import update3_3_8to3_3_9 from './modules/update3_3_8to3_3_9'; 8 | import update3_3_9to3_3_10 from './modules/update3_3_9to3_3_10'; 9 | import update3_3_10to3_4_0 from './modules/update3_3_10to3_4_0'; 10 | 11 | export default { 12 | update0_0_0to3_3_1, 13 | update3_3_1to3_3_2, 14 | update3_3_3to3_3_4, 15 | update3_3_4to3_3_5, 16 | update3_3_6to3_3_7, 17 | update3_3_7to3_3_8, 18 | update3_3_8to3_3_9, 19 | update3_3_9to3_3_10, 20 | update3_3_10to3_4_0, 21 | }; 22 | -------------------------------------------------------------------------------- /src/main/core/db/migration/modules/init/index.ts: -------------------------------------------------------------------------------- 1 | import tblSetting from './tbl_setting.json'; 2 | 3 | export { tblSetting }; 4 | -------------------------------------------------------------------------------- /src/main/core/db/migration/modules/update3_3_1to3_3_2.ts: -------------------------------------------------------------------------------- 1 | import { eq } from 'drizzle-orm'; 2 | import { db, schema } from '../../common'; 3 | import logger from '@main/core/logger'; 4 | 5 | const update = async () => { 6 | await db.insert(schema.setting).values({ 7 | key: 'windowPosition', 8 | value: { data: { status: false, position: { width: 1000, height: 640 } } }, 9 | }); 10 | await db.delete(schema.setting).where(eq(schema.setting.key, 'restoreWindowPositionAndSize')); 11 | 12 | const old_version = await db.select().from(schema.setting).where(eq(schema.setting.key, 'version')); 13 | if (old_version.length > 0) { 14 | await db.delete(schema.setting).where(eq(schema.setting.key, 'version')); 15 | } 16 | await db.insert(schema.setting).values({ key: 'version', value: { data: '3.3.2' } }); 17 | 18 | logger.info('[db][magrite][update3_3_1to3_3_2]completed'); 19 | }; 20 | 21 | export default update; 22 | -------------------------------------------------------------------------------- /src/main/core/db/migration/modules/update3_3_4to3_3_5.ts: -------------------------------------------------------------------------------- 1 | import { eq } from 'drizzle-orm'; 2 | import { db, schema } from '../../common'; 3 | import logger from '@main/core/logger'; 4 | 5 | const update = async () => { 6 | const old_analyze = await db.select().from(schema.analyze); 7 | if (old_analyze.length > 0) { 8 | old_analyze.forEach(async (item: { [key: string]: any }) => { 9 | if (item?.type === undefined) 10 | await db 11 | .update(schema.analyze) 12 | .set({ type: item?.type || 0 }) 13 | .where(eq(schema.analyze.id, item.id)); 14 | }); 15 | } 16 | 17 | const old_timeout = await db.select().from(schema.setting).where(eq(schema.setting.key, 'timeout')); 18 | if (old_timeout.length > 0) { 19 | await db.delete(schema.setting).where(eq(schema.setting.key, 'timeout')); 20 | } 21 | await db.insert(schema.setting).values({ 22 | key: 'timeout', 23 | value: { data: 5000 }, 24 | }); 25 | 26 | const old_ai = await db.select().from(schema.setting).where(eq(schema.setting.key, 'ai')); 27 | if (old_ai.length > 0) { 28 | await db.delete(schema.setting).where(eq(schema.setting.key, 'ai')); 29 | } 30 | await db.insert(schema.setting).values({ 31 | key: 'ai', 32 | value: { 33 | data: { 34 | server: '', 35 | key: '', 36 | model: '', 37 | }, 38 | }, 39 | }); 40 | 41 | const old_version = await db.select().from(schema.setting).where(eq(schema.setting.key, 'version')); 42 | if (old_version.length > 0) { 43 | await db.delete(schema.setting).where(eq(schema.setting.key, 'version')); 44 | } 45 | await db.insert(schema.setting).values({ key: 'version', value: { data: '3.3.5' } }); 46 | 47 | logger.info('[db][magrite][update3_3_4_to3_3_5]completed'); 48 | }; 49 | 50 | export default update; 51 | -------------------------------------------------------------------------------- /src/main/core/db/migration/modules/update3_3_7to3_3_8.ts: -------------------------------------------------------------------------------- 1 | import { eq } from 'drizzle-orm'; 2 | import { db, schema } from '../../common'; 3 | import logger from '@main/core/logger'; 4 | 5 | const update = async () => { 6 | const old_site = await db.select().from(schema.site); 7 | if (old_site.length > 0) { 8 | old_site.forEach(async (item: { [key: string]: any }) => { 9 | if (item.type === 3 || item.type === 4) { 10 | await db.update(schema.site).set({ type: 11 }).where(eq(schema.site.id, item.id)); 11 | } 12 | }); 13 | } 14 | 15 | await db.delete(schema.setting).where(eq(schema.setting.key, 'iptvSkipIpv6')); 16 | await db.insert(schema.setting).values({ key: 'iptvMarkIp', value: { data: true } }); 17 | 18 | await db.delete(schema.setting).where(eq(schema.setting.key, 'iptvStatus')); 19 | await db.insert(schema.setting).values({ key: 'iptvDelay', value: { data: false } }); 20 | 21 | await db.delete(schema.setting).where(eq(schema.setting.key, 'analyzeFlag')); 22 | await db.insert(schema.setting).values({ 23 | key: 'analyzeFlag', 24 | value: { data: ['youku', 'qq', 'iqiyi', 'qiyi', 'letv', 'leshi', 'sohu', 'tudou', 'pptv', 'mgtv', 'imgo'] }, 25 | }); 26 | 27 | const old_version = await db.select().from(schema.setting).where(eq(schema.setting.key, 'version')); 28 | if (old_version.length > 0) { 29 | await db.delete(schema.setting).where(eq(schema.setting.key, 'version')); 30 | } 31 | await db.insert(schema.setting).values({ key: 'version', value: { data: '3.3.8' } }); 32 | 33 | logger.info('[db][magrite][update3_3_7to3_3_8]completed'); 34 | }; 35 | 36 | export default update; 37 | -------------------------------------------------------------------------------- /src/main/core/db/migration/modules/update3_3_8to3_3_9.ts: -------------------------------------------------------------------------------- 1 | import { eq } from 'drizzle-orm'; 2 | import { db, client, schema } from '../../common'; 3 | import logger from '@main/core/logger'; 4 | 5 | const update = async () => { 6 | await client.exec(` 7 | ALTER TABLE tbl_site ADD COLUMN search_new integer not null default 0; 8 | UPDATE tbl_site SET search_new = CASE WHEN search = TRUE THEN 1 ELSE 0 END; 9 | ALTER TABLE tbl_site DROP COLUMN search; 10 | ALTER TABLE tbl_site rename search_new to search; 11 | 12 | ALTER TABLE tbl_drive ADD COLUMN "showAll" boolean default false; 13 | 14 | ALTER TABLE tbl_channel ALTER COLUMN url TYPE varchar(510); 15 | `); 16 | 17 | const old_version = await db.select().from(schema.setting).where(eq(schema.setting.key, 'version')); 18 | if (old_version.length > 0) { 19 | await db.delete(schema.setting).where(eq(schema.setting.key, 'version')); 20 | } 21 | await db.insert(schema.setting).values({ key: 'version', value: { data: '3.3.9' } }); 22 | 23 | logger.info('[db][magrite][update3_3_8to3_3_9]completed'); 24 | }; 25 | 26 | export default update; 27 | -------------------------------------------------------------------------------- /src/main/core/db/service/analyze.ts: -------------------------------------------------------------------------------- 1 | import { asc, eq, ilike, inArray } from 'drizzle-orm'; 2 | import { db, schema } from '../common'; 3 | 4 | export default { 5 | async all() { 6 | return await db.select().from(schema.analyze); 7 | }, 8 | async active() { 9 | return await db 10 | .select() 11 | .from(schema.analyze) 12 | .where(eq(schema.analyze.isActive, true)) 13 | .orderBy(asc(schema.analyze.name)); 14 | }, 15 | async update(ids, doc) { 16 | return await db.update(schema.analyze).set(doc).where(inArray(schema.analyze.id, ids)).returning(); 17 | }, 18 | async clear() { 19 | return await db.delete(schema.analyze); 20 | }, 21 | async get(id) { 22 | const res = await db.select().from(schema.analyze).where(eq(schema.analyze.id, id)); 23 | return res?.[0]; 24 | }, 25 | async findByKey(key) { 26 | const res = await db.select().from(schema.analyze).where(eq(schema.analyze.key, key)); 27 | return res?.[0]; 28 | }, 29 | async set(doc) { 30 | await db.delete(schema.analyze); 31 | return await db.insert(schema.analyze).values(doc); 32 | }, 33 | async add(doc) { 34 | return await db.insert(schema.analyze).values(doc).returning(); 35 | }, 36 | async remove(ids) { 37 | return await db.delete(schema.analyze).where(inArray(schema.analyze.id, ids)); 38 | }, 39 | async page(page = 1, pageSize = 20, kw = '') { 40 | let query = db.select().from(schema.analyze); 41 | let count = db.$count(schema.analyze); 42 | 43 | if (kw) { 44 | query = query.where(ilike(schema.analyze.name, `%${kw}%`)); 45 | count = db.$count(schema.analyze, ilike(schema.analyze.name, `%${kw}%`)); 46 | } 47 | query = query 48 | .limit(pageSize) 49 | .offset((page - 1) * pageSize) 50 | .orderBy(asc(schema.analyze.name)); 51 | 52 | const list = await query; 53 | const total = await count; 54 | return { 55 | list: list, 56 | total: total, 57 | }; 58 | }, 59 | }; 60 | -------------------------------------------------------------------------------- /src/main/core/db/service/channel.ts: -------------------------------------------------------------------------------- 1 | import { and, asc, eq, like, inArray } from 'drizzle-orm'; 2 | import { db, schema } from '../common'; 3 | 4 | export default { 5 | async all() { 6 | return await db.select().from(schema.channel); 7 | }, 8 | async update(ids, doc) { 9 | return await db.update(schema.channel).set(doc).where(inArray(schema.channel.id, ids)).returning(); 10 | }, 11 | async clear() { 12 | return await db.delete(schema.channel); 13 | }, 14 | async get(id) { 15 | const res = await db.select().from(schema.channel).where(eq(schema.channel.id, id)); 16 | return res?.[0]; 17 | }, 18 | async set(doc) { 19 | await db.delete(schema.channel); 20 | return await db.insert(schema.channel).values(doc); 21 | }, 22 | async add(doc) { 23 | return await db.insert(schema.channel).values(doc).returning(); 24 | }, 25 | async remove(ids) { 26 | return await db.delete(schema.channel).where(inArray(schema.channel.id, ids)); 27 | }, 28 | async page(page = 1, pageSize = 20, kw = '', group = '全部') { 29 | const baseQuery = db.select().from(schema.channel); 30 | const conditions: any[] = []; 31 | 32 | if (group !== '全部') { 33 | conditions.push(eq(schema.channel.group, group)); 34 | } 35 | if (kw) { 36 | conditions.push(like(schema.channel.name, `%${kw}%`)); 37 | } 38 | 39 | let query = conditions.length > 0 ? baseQuery.where(and(...conditions)) : baseQuery; 40 | query = query 41 | .limit(pageSize) 42 | .offset((page - 1) * pageSize) 43 | .orderBy(asc(schema.channel.name)); 44 | const list = await query; 45 | 46 | const countQuery = conditions.length > 0 ? baseQuery.where(and(...conditions)) : baseQuery; 47 | const total = await countQuery; 48 | 49 | return { 50 | list: list, 51 | total: total.length, 52 | }; 53 | }, 54 | async group() { 55 | const res = await db.select({ group: schema.channel.group }).from(schema.channel).groupBy(schema.channel['group']); 56 | const resFormat = res.map((item) => ({ type_id: item.group, type_name: item.group })); 57 | return resFormat; 58 | }, 59 | }; 60 | -------------------------------------------------------------------------------- /src/main/core/db/service/db.ts: -------------------------------------------------------------------------------- 1 | import { client } from '../common'; 2 | import { channel, history, site, star, iptv, drive, analyze, setting } from '.'; 3 | 4 | export default { 5 | async drop(doc) { 6 | let sql = ``; 7 | for (let i = 0; i < doc.length; i++) { 8 | sql += `DROP TABLE IF EXISTS ${doc[i]};`; 9 | } 10 | await client.exec(sql); 11 | }, 12 | async all() { 13 | const res: { [key: string]: any } = {}; 14 | res.tbl_setting = await setting.all(); 15 | res.tbl_site = await site.all(); 16 | res.tbl_analyze = await analyze.all(); 17 | res.tbl_channel = await channel.all(); 18 | res.tbl_iptv = await iptv.all(); 19 | res.tbl_drive = await drive.all(); 20 | res.tbl_history = await history.all(); 21 | res.tbl_star = await star.all(); 22 | return res; 23 | }, 24 | async source(doc) { 25 | const tableSetters = { 26 | site: site.set, 27 | iptv: iptv.set, 28 | channel: channel.set, 29 | analyze: analyze.set, 30 | drive: drive.set, 31 | history: history.set, 32 | star: star.set, 33 | setting: setting.set, 34 | }; 35 | 36 | for (const key in doc) { 37 | const prefix = key.substring(4); 38 | if (tableSetters[prefix]) { 39 | await tableSetters[prefix](doc[key]); 40 | } 41 | } 42 | }, 43 | }; 44 | -------------------------------------------------------------------------------- /src/main/core/db/service/drive.ts: -------------------------------------------------------------------------------- 1 | import { asc, eq, ilike, inArray } from 'drizzle-orm'; 2 | import { db, schema } from '../common'; 3 | 4 | export default { 5 | async all() { 6 | return await db.select().from(schema.drive); 7 | }, 8 | async active() { 9 | return await db.select().from(schema.drive).where(eq(schema.drive.isActive, true)).orderBy(asc(schema.drive.name)); 10 | }, 11 | async update(ids, doc) { 12 | return await db.update(schema.drive).set(doc).where(inArray(schema.drive.id, ids)).returning(); 13 | }, 14 | async clear() { 15 | return await db.delete(schema.drive); 16 | }, 17 | async get(id) { 18 | const res = await db.select().from(schema.drive).where(eq(schema.drive.id, id)); 19 | return res?.[0]; 20 | }, 21 | async findByKey(key) { 22 | const res = await db.select().from(schema.drive).where(eq(schema.drive.key, key)); 23 | return res?.[0]; 24 | }, 25 | async set(doc) { 26 | await db.delete(schema.drive); 27 | return await db.insert(schema.drive).values(doc); 28 | }, 29 | async add(doc) { 30 | return await db.insert(schema.drive).values(doc).returning(); 31 | }, 32 | async remove(ids) { 33 | return await db.delete(schema.drive).where(inArray(schema.drive.id, ids)); 34 | }, 35 | async page(page = 1, pageSize = 20, kw = '') { 36 | let query = db.select().from(schema.drive); 37 | let count = db.$count(schema.drive); 38 | 39 | if (kw) { 40 | query = query.where(ilike(schema.drive.name, `%${kw}%`)); 41 | count = db.$count(schema.drive, ilike(schema.drive.name, `%${kw}%`)); 42 | } 43 | query = query 44 | .limit(pageSize) 45 | .offset((page - 1) * pageSize) 46 | .orderBy(asc(schema.drive.name)); 47 | 48 | const list = await query; 49 | const total = await count; 50 | return { 51 | list: list, 52 | total: total, 53 | }; 54 | }, 55 | }; 56 | -------------------------------------------------------------------------------- /src/main/core/db/service/index.ts: -------------------------------------------------------------------------------- 1 | import analyze from './analyze'; 2 | import drive from './drive'; 3 | import history from './history'; 4 | import setting from './setting'; 5 | import iptv from './iptv'; 6 | import channel from './channel'; 7 | import star from './star'; 8 | import site from './site'; 9 | import db from './db'; 10 | 11 | export { history, setting, star, site, iptv, channel, analyze, drive, db }; 12 | -------------------------------------------------------------------------------- /src/main/core/db/service/iptv.ts: -------------------------------------------------------------------------------- 1 | import { asc, eq, ilike, inArray } from 'drizzle-orm'; 2 | import { db, schema } from '../common'; 3 | 4 | export default { 5 | async all() { 6 | return await db.select().from(schema.iptv); 7 | }, 8 | async active() { 9 | return await db.select().from(schema.iptv).where(eq(schema.iptv.isActive, true)).orderBy(asc(schema.iptv.name)); 10 | }, 11 | async update(ids, doc) { 12 | return await db.update(schema.iptv).set(doc).where(inArray(schema.iptv.id, ids)).returning(); 13 | }, 14 | async clear() { 15 | return await db.delete(schema.iptv); 16 | }, 17 | async get(id) { 18 | const res = await db.select().from(schema.iptv).where(eq(schema.iptv.id, id)); 19 | return res?.[0]; 20 | }, 21 | async findByKey(key) { 22 | const res = await db.select().from(schema.iptv).where(eq(schema.iptv.key, key)); 23 | return res?.[0]; 24 | }, 25 | async set(doc) { 26 | await db.delete(schema.iptv); 27 | return await db.insert(schema.iptv).values(doc); 28 | }, 29 | async add(doc) { 30 | return await db.insert(schema.iptv).values(doc).returning(); 31 | }, 32 | async remove(ids) { 33 | return await db.delete(schema.iptv).where(inArray(schema.iptv.id, ids)); 34 | }, 35 | async page(page = 1, pageSize = 20, kw = '') { 36 | let query = db.select().from(schema.iptv); 37 | let count = db.$count(schema.iptv); 38 | 39 | if (kw) { 40 | query = query.where(ilike(schema.iptv.name, `%${kw}%`)); 41 | count = db.$count(schema.iptv, ilike(schema.iptv.name, `%${kw}%`)); 42 | } 43 | query = query 44 | .limit(pageSize) 45 | .offset((page - 1) * pageSize) 46 | .orderBy(asc(schema.iptv.name)); 47 | 48 | const list = await query; 49 | const total = await count; 50 | return { 51 | list: list, 52 | total: total, 53 | }; 54 | }, 55 | }; 56 | -------------------------------------------------------------------------------- /src/main/core/db/service/setting.ts: -------------------------------------------------------------------------------- 1 | import { eq, inArray } from 'drizzle-orm'; 2 | import { db, schema } from '../common'; 3 | 4 | export default { 5 | async all() { 6 | const res = await db.select().from(schema.setting); 7 | let resFormat = {}; 8 | res.map((item) => { 9 | resFormat[item['key']] = item['value']?.['data']; 10 | }); 11 | return resFormat; 12 | }, 13 | async update(ids, doc) { 14 | return await db 15 | .update(schema.setting) 16 | .set({ value: { data: doc } }) 17 | .where(inArray(schema.setting.key, ids)) 18 | .returning(); 19 | }, 20 | async clear() { 21 | return await db.delete(schema.setting); 22 | }, 23 | async get(id) { 24 | const res = await db.select().from(schema.setting).where(eq(schema.setting.key, id)); 25 | const resFormat = res?.[0]?.['value']?.['data']; 26 | return resFormat || null; 27 | }, 28 | async set(doc) { 29 | await db.delete(schema.setting); 30 | let newDoc: any[] = []; 31 | for (const key in doc) { 32 | newDoc.push({ key: key, value: { data: doc[key] } }); 33 | } 34 | return await db.insert(schema.setting).values(newDoc); 35 | }, 36 | async add(doc) { 37 | return await db.insert(schema.setting).values(doc).returning(); 38 | }, 39 | async remove(ids) { 40 | return await db.delete(schema.setting).where(inArray(schema.setting.key, ids)); 41 | }, 42 | }; 43 | -------------------------------------------------------------------------------- /src/main/core/db/service/site.ts: -------------------------------------------------------------------------------- 1 | import { asc, eq, ilike, inArray } from 'drizzle-orm'; 2 | import { db, schema } from '../common'; 3 | 4 | export default { 5 | async all() { 6 | return await db.select().from(schema.site); 7 | }, 8 | async active() { 9 | return await db.select().from(schema.site).where(eq(schema.site.isActive, true)).orderBy(asc(schema.site.name)); 10 | }, 11 | async update(ids, doc) { 12 | return await db.update(schema.site).set(doc).where(inArray(schema.site.id, ids)).returning(); 13 | }, 14 | async clear() { 15 | return await db.delete(schema.site); 16 | }, 17 | async get(id) { 18 | const res = await db.select().from(schema.site).where(eq(schema.site.id, id)); 19 | return res?.[0]; 20 | }, 21 | async findByKey(key) { 22 | const res = await db.select().from(schema.site).where(eq(schema.site.key, key)); 23 | return res?.[0]; 24 | }, 25 | async set(doc) { 26 | await db.delete(schema.site); 27 | return await db.insert(schema.site).values(doc); 28 | }, 29 | async add(doc) { 30 | return await db.insert(schema.site).values(doc).returning(); 31 | }, 32 | async remove(ids) { 33 | return await db.delete(schema.site).where(inArray(schema.site.id, ids)); 34 | }, 35 | async page(page = 1, pageSize = 20, kw = '') { 36 | let query = db.select().from(schema.site); 37 | let count = db.$count(schema.site); 38 | 39 | if (kw) { 40 | query = query.where(ilike(schema.site.name, `%${kw}%`)); 41 | count = db.$count(schema.site, ilike(schema.site.name, `%${kw}%`)); 42 | } 43 | query = query 44 | .limit(pageSize) 45 | .offset((page - 1) * pageSize) 46 | .orderBy(asc(schema.site.name)); 47 | 48 | const list = await query; 49 | const total = await count; 50 | return { 51 | list: list, 52 | total: total, 53 | }; 54 | }, 55 | async group() { 56 | const res = await db.select({ group: schema.site.group }).from(schema.site).groupBy(schema.site.group); 57 | const resFormat = res.map((item) => ({ label: item.group, value: item.group })); 58 | return resFormat; 59 | }, 60 | }; 61 | -------------------------------------------------------------------------------- /src/main/core/global/index.ts: -------------------------------------------------------------------------------- 1 | import { setting } from '@main/core/db/service'; 2 | import logger from '@main/core/logger'; 3 | 4 | const setup = async () => { 5 | const variable = { 6 | hardwareAcceleration: (await setting.get('hardwareAcceleration')) || false, 7 | ua: 8 | (await setting.get('ua')) || 9 | 'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Mobile Safari/537.36 Edg/131.0.0.0', 10 | dns: (await setting.get('dns')) || '', 11 | selfBoot: (await setting.get('selfBoot')) || false, 12 | recordShortcut: (await setting.get('recordShortcut')) || '', 13 | timeout: (await setting.get('timeout')) || 5000, 14 | debug: (await setting.get('debug')) || false, 15 | }; 16 | 17 | globalThis.variable = variable; 18 | logger.info(`[global][variable]`, variable); 19 | }; 20 | 21 | export default setup; 22 | -------------------------------------------------------------------------------- /src/main/core/logger/index.ts: -------------------------------------------------------------------------------- 1 | /** By default, it writes logs to the following locations: 2 | * Linux: ~/.config/{app name}/logs/main.log ~/.config/zyfun/logs/main.log 3 | * Linux: $XDG_CONFIG_HOME/{app name}/logs/main.log $XDG_CONFIG_HOME/zyfun/logs/main.log 4 | * macOS: ~/Library/Logs/{app name}/main.log ~/Library/Logs/zyfun/main.log 5 | * Windows: %USERPROFILE%\AppData\Roaming\{app name}\logs\main.log %USERPROFILE%\AppData\Roaming\zyfun\logs\main.log 6 | * @see https://www.npmjs.com/package/electron-log 7 | */ 8 | import logger from 'electron-log'; 9 | import { join } from 'path'; 10 | import { APP_LOG_PATH } from '@main/utils/hiker/path'; 11 | 12 | const LOG_PATH = join(APP_LOG_PATH, 'main.log'); 13 | 14 | // 日志级别error, warn, info, verbose, debug, silly。默认是silly最低级别否则不生成日志 15 | logger.transports.file.level = 'silly'; 16 | logger.transports.file.maxSize = 10024300; // 文件最大不超过 10M 17 | logger.transports.file.format = '[{y}-{m}-{d} {h}:{i}:{s}.{ms}] [{level}] {text}'; // 输出格式 18 | logger.transports.file.resolvePathFn = () => LOG_PATH; 19 | 20 | logger.info(`[log][init] path:${LOG_PATH}`); 21 | 22 | export default logger; 23 | -------------------------------------------------------------------------------- /src/main/core/protocol/index.ts: -------------------------------------------------------------------------------- 1 | import { app } from 'electron'; 2 | import { resolve } from 'path'; 3 | import logger from '@main/core/logger'; 4 | 5 | const PROTOCOL = 'zy'; 6 | 7 | const protocolHandler = () => { 8 | const args: string[] = []; 9 | 10 | if (!app.isPackaged) { 11 | args.push(resolve(process.argv[1])); 12 | } 13 | args.push('--'); 14 | 15 | app.setAsDefaultProtocolClient(PROTOCOL, process.execPath, args); 16 | 17 | // 如果打开协议时,没有其他实例,则当前实例当做主实例,处理参数 18 | handleArgv(process.argv); 19 | 20 | // 其他实例启动时,主实例会通过 second-instance 事件接收其他实例的启动参数 `argv` 21 | app.on('second-instance', (_, argv) => { 22 | // Windows 下通过协议 URL 启动时,URL 会作为参数,所以需要在这个事件里处理 23 | if (process.platform === 'win32') { 24 | handleArgv(argv); 25 | } 26 | }); 27 | 28 | // macOS 下通过协议 URL 启动时,主实例会通过 open-url 事件接收这个 URL 29 | app.on('open-url', (_, urlStr) => { 30 | handleUrl(urlStr); 31 | }); 32 | 33 | // 处理参数 34 | function handleArgv(argv: string[]) { 35 | const prefix = `${PROTOCOL}:`; 36 | // 开发阶段,跳过前两个参数(`electron.exe .`) 37 | // 打包后,跳过第一个参数(`myapp.exe`) 38 | const offset = app.isPackaged ? 1 : 2; 39 | const url = argv.find((arg, i) => i >= offset && arg.startsWith(prefix)); 40 | if (url) handleUrl(url); 41 | } 42 | 43 | // 解析 URL 44 | function handleUrl(urlStr: string) { 45 | // 解析 URL 参数 46 | const urlObj = new URL(urlStr); 47 | const searchParams = urlObj.searchParams; 48 | 49 | // 使用 querystring 模块解析查询参数 50 | const query = Object.fromEntries(searchParams.entries()); 51 | logger.info(query); 52 | 53 | // 根据需要做其他事情 54 | } 55 | }; 56 | 57 | export default protocolHandler; 58 | -------------------------------------------------------------------------------- /src/main/core/server/index.ts: -------------------------------------------------------------------------------- 1 | import fastify, { FastifyInstance } from 'fastify'; 2 | import fastifyCors from '@fastify/cors'; 3 | import fastifyMultipart from '@fastify/multipart'; 4 | import fastifyPlugin from 'fastify-plugin'; 5 | 6 | import { JsonDB, Config } from 'node-json-db'; 7 | import { join } from 'path'; 8 | import logger from '@main/core/logger'; 9 | import { APP_TMP_PATH, APP_LOG_PATH } from '@main/utils/hiker/path'; 10 | import routesV1Modules from './routes/v1'; 11 | 12 | async function jsonDbPlugin(fastify: FastifyInstance): Promise { 13 | const db = new JsonDB(new Config(join(APP_TMP_PATH, 'cache.json'), true, true, '/')); 14 | fastify.decorate('db', db); 15 | }; 16 | 17 | const wrappedJsonDbPlugin = fastifyPlugin(jsonDbPlugin, { 18 | fastify: '5.x', 19 | name: 'json-db-plugin', 20 | }); 21 | 22 | const setup = async () => { 23 | const server = fastify({ 24 | logger: { 25 | level: 'info', // 日志级别(可选:trace, debug, info, warn, error, fatal) 26 | file: join(APP_LOG_PATH, 'fastify.log') // 日志文件路径 27 | }, // 日志 28 | forceCloseConnections: true, // 强制关闭连接 29 | ignoreTrailingSlash: true, // 忽略斜杠 30 | maxParamLength: 10240, // 参数长度限制 31 | bodyLimit: 1024 * 1024 * 3, // 限制请求体大小为 3MB 32 | }); 33 | 34 | try { 35 | server.setErrorHandler((err, _request, reply) => { 36 | server.log.error(err); 37 | 38 | reply.status(500).send({ 39 | code: -1, 40 | msg: `Internal Server Error - ${err.name}`, 41 | data: err.message, 42 | }); 43 | }); 44 | 45 | server.register(wrappedJsonDbPlugin); 46 | server.register(fastifyMultipart); 47 | server.register(fastifyCors); 48 | 49 | // 注册 v1 路由 50 | Object.keys(routesV1Modules).forEach((key: string) => { 51 | server.register(routesV1Modules[key]); 52 | }); 53 | 54 | await server.ready(); 55 | await server.listen({ port: 9978, host: '0.0.0.0' }); 56 | logger.info('[server][init] listen: http://0.0.0.0:9978'); 57 | } catch (err) { 58 | server.log.error(err); 59 | process.exit(1); 60 | } 61 | }; 62 | 63 | export default setup; 64 | -------------------------------------------------------------------------------- /src/main/core/server/routes/v1/db/index.ts: -------------------------------------------------------------------------------- 1 | import db from './db'; 2 | 3 | export { db }; 4 | -------------------------------------------------------------------------------- /src/main/core/server/routes/v1/drive/index.ts: -------------------------------------------------------------------------------- 1 | import db from './db'; 2 | import work from './work'; 3 | 4 | export { db, work }; 5 | -------------------------------------------------------------------------------- /src/main/core/server/routes/v1/file/index.ts: -------------------------------------------------------------------------------- 1 | import work from './work'; 2 | 3 | export { work }; 4 | -------------------------------------------------------------------------------- /src/main/core/server/routes/v1/history/index.ts: -------------------------------------------------------------------------------- 1 | import work from './work'; 2 | 3 | export { work }; 4 | -------------------------------------------------------------------------------- /src/main/core/server/routes/v1/index.ts: -------------------------------------------------------------------------------- 1 | import { db } from './db'; 2 | import { db as driveDb, work as driveWork } from './drive'; 3 | import { work as fileWork } from './file'; 4 | import { work as historyWork } from './history'; 5 | import { ad, ai, jsEdit, staticFilter } from './lab'; 6 | import { channel, iptv } from './live'; 7 | import { db as analyzeDb, work as analyzeWork } from './parse'; 8 | import { barrage } from './player'; 9 | import { work as pluginWork } from './plugin'; 10 | import { work as proxyWork } from './proxy'; 11 | import { work as settingWork } from './setting'; 12 | import { cms, hot, db as sietDb, recomm } from './site'; 13 | import { work as systemWork } from './system'; 14 | import { work as starWork } from './star'; 15 | 16 | const routesModules = { 17 | analyzeDb, 18 | analyzeWork, 19 | cms, 20 | sietDb, 21 | starWork, 22 | historyWork, 23 | hot, 24 | recomm, 25 | driveDb, 26 | driveWork, 27 | settingWork, 28 | iptv, 29 | channel, 30 | db, 31 | proxyWork, 32 | ad, 33 | ai, 34 | jsEdit, 35 | staticFilter, 36 | fileWork, 37 | barrage, 38 | systemWork, 39 | pluginWork, 40 | }; 41 | 42 | export default routesModules; 43 | -------------------------------------------------------------------------------- /src/main/core/server/routes/v1/lab/ad/index.ts: -------------------------------------------------------------------------------- 1 | import { FastifyPluginAsync, FastifyRequest, FastifyReply } from 'fastify'; 2 | import { completeRequest } from '@main/utils/request'; 3 | import fixAdM3u8Ai from './utils'; 4 | 5 | const API_PREFIX = 'api/v1/lab/ad'; 6 | 7 | const api: FastifyPluginAsync = async (fastify): Promise => { 8 | fastify.get( 9 | `/${API_PREFIX}`, 10 | async (req: FastifyRequest<{ Querystring: { [key: string]: string } }>, reply: FastifyReply) => { 11 | const m3u8ContentType = ['application/vnd.apple.mpegurl', 'application/x-mpegURL', 'application/octet-stream']; 12 | 13 | let { url, headers = '{}' } = req.query; 14 | 15 | if (!url || !/^(http:\/\/|https:\/\/)/.test(url)) { 16 | reply.code(500).send({ code: -1, msg: 'Invalid m3u8 url' }); 17 | return; 18 | } else { 19 | url = decodeURI(url); 20 | headers = JSON.parse(headers); 21 | 22 | const res = await completeRequest({ url, method: 'HEAD', headers } as any); 23 | 24 | if (res?.headers?.['content-type'] && m3u8ContentType.includes(res.headers['content-type'])) { 25 | const content = (await fixAdM3u8Ai.latest(url, headers as any)) || ''; 26 | if (content.includes('.ts')) { 27 | reply.code(200).header('Content-Type', 'application/vnd.apple.mpegurl').send(content); 28 | return; 29 | } else { 30 | reply.redirect(url); 31 | return; 32 | } 33 | } else { 34 | reply.code(500).send({ code: -1, msg: 'Invalid url type provided' }); 35 | return; 36 | } 37 | } 38 | }, 39 | ); 40 | }; 41 | 42 | export default api; 43 | -------------------------------------------------------------------------------- /src/main/core/server/routes/v1/lab/index.ts: -------------------------------------------------------------------------------- 1 | import staticFilter from './staticFilter'; 2 | import jsEdit from './jsEdit'; 3 | import ai from './ai'; 4 | import ad from './ad'; 5 | 6 | export { ad, ai, jsEdit, staticFilter }; 7 | -------------------------------------------------------------------------------- /src/main/core/server/routes/v1/lab/jsEdit/index.ts: -------------------------------------------------------------------------------- 1 | import { FastifyPluginAsync, FastifyRequest } from 'fastify'; 2 | import { pdfa, pdfh } from '@main/utils/hiker/htmlParser'; 3 | import { getMubans } from '../../site/cms/adapter/drpy/template'; 4 | import { site } from '@main/core/db/service'; 5 | 6 | const API_PREFIX = 'api/v1/lab/js-edit'; 7 | 8 | const api: FastifyPluginAsync = async (fastify): Promise => { 9 | fastify.post(`/${API_PREFIX}/pdfa`, async (req: FastifyRequest<{ Body: { [key: string]: any } }>) => { 10 | const { html, rule } = req.body; 11 | 12 | const res = pdfa(html, rule); 13 | return { 14 | code: 0, 15 | msg: 'ok', 16 | data: res, 17 | }; 18 | }); 19 | fastify.post(`/${API_PREFIX}/pdfh`, async (req: FastifyRequest<{ Body: { [key: string]: string } }>) => { 20 | const { html, rule, baseUrl } = req.body; 21 | const res = pdfh(html, rule, baseUrl); 22 | return { 23 | code: 0, 24 | msg: 'ok', 25 | data: res, 26 | }; 27 | }); 28 | fastify.post(`/${API_PREFIX}/muban`, async () => { 29 | return { 30 | code: 0, 31 | msg: 'ok', 32 | data: getMubans(), 33 | }; 34 | }); 35 | fastify.get(`/${API_PREFIX}/debug`, async () => { 36 | const dbResSite = await site.findByKey('debug'); 37 | return { 38 | code: 0, 39 | msg: 'ok', 40 | data: dbResSite, 41 | }; 42 | }); 43 | }; 44 | 45 | export default api; 46 | -------------------------------------------------------------------------------- /src/main/core/server/routes/v1/lab/staticFilter/index.ts: -------------------------------------------------------------------------------- 1 | import { FastifyPluginAsync, FastifyRequest } from 'fastify'; 2 | import { getFilters, processCategories } from './utils'; 3 | 4 | const API_PREFIX = 'api/v1/lab/static-filter'; 5 | 6 | const api: FastifyPluginAsync = async (fastify): Promise => { 7 | fastify.post(`/${API_PREFIX}/filter`, async (req: FastifyRequest<{ Body: { [key: string]: any } }>) => { 8 | const { html, ci, f, f1, matchs, exclude_keys } = req.body; 9 | 10 | const res = getFilters(html, ci, f, f1, matchs, exclude_keys); 11 | return { 12 | code: 0, 13 | msg: 'ok', 14 | data: res, 15 | }; 16 | }); 17 | fastify.post(`/${API_PREFIX}/category`, async (req: FastifyRequest<{ Body: { [key: string]: string } }>) => { 18 | const { contentHtml, class_parse, cate_exclude, reurl, url } = req.body; 19 | const res = processCategories(contentHtml, class_parse, cate_exclude, reurl, url); 20 | return { 21 | code: 0, 22 | msg: 'ok', 23 | data: res, 24 | }; 25 | }); 26 | }; 27 | 28 | export default api; 29 | -------------------------------------------------------------------------------- /src/main/core/server/routes/v1/live/index.ts: -------------------------------------------------------------------------------- 1 | import channel from './channel'; 2 | import iptv from './iptv'; 3 | 4 | export { channel, iptv }; 5 | -------------------------------------------------------------------------------- /src/main/core/server/routes/v1/parse/index.ts: -------------------------------------------------------------------------------- 1 | import db from './db'; 2 | import work from './work'; 3 | 4 | export { db, work }; 5 | -------------------------------------------------------------------------------- /src/main/core/server/routes/v1/parse/work.ts: -------------------------------------------------------------------------------- 1 | import { FastifyPluginAsync, FastifyRequest } from 'fastify'; 2 | import * as cheerio from 'cheerio'; 3 | import request from '@main/utils/request'; 4 | 5 | const API_PREFIX = 'api/v1/analyze'; 6 | 7 | const api: FastifyPluginAsync = async (fastify): Promise => { 8 | fastify.get(`/${API_PREFIX}/title`, async (req: FastifyRequest<{ Querystring: { [key: string]: string } }>) => { 9 | const { url } = req.query; 10 | const reqRes = await request({ 11 | url, 12 | method: 'GET', 13 | }); 14 | const html = Buffer.from(reqRes).toString('utf-8'); 15 | const $ = cheerio.load(html); 16 | const titleRes = $('title').text(); 17 | const res = { 18 | title: titleRes, 19 | }; 20 | return { 21 | code: 0, 22 | msg: 'ok', 23 | data: res, 24 | }; 25 | }); 26 | }; 27 | 28 | export default api; 29 | -------------------------------------------------------------------------------- /src/main/core/server/routes/v1/player/index.ts: -------------------------------------------------------------------------------- 1 | import barrage from './barrage'; 2 | 3 | export { barrage }; 4 | -------------------------------------------------------------------------------- /src/main/core/server/routes/v1/plugin/index.ts: -------------------------------------------------------------------------------- 1 | import work from './work'; 2 | 3 | export { work }; 4 | -------------------------------------------------------------------------------- /src/main/core/server/routes/v1/plugin/utils/types.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 插件管理器配置 3 | * @param baseDir 插件安装目录 4 | * @param registry 插件下载源 即 npm 源 5 | * @export 6 | * @interface AdapterHandlerOptions 7 | */ 8 | export interface AdapterHandlerOptions { 9 | baseDir: string; 10 | registry?: string; 11 | } 12 | 13 | /** 14 | * 插件信息, 对应 plugin.json 15 | * @export 16 | * @interface AdapterInfo 17 | */ 18 | export interface AdapterInfo { 19 | // 插件类型 20 | type: 'adapter'; 21 | // 插件名称 zy-adapter-xxx 22 | name: string; 23 | // 可读插件名称 24 | pluginName: string; 25 | // 作者 26 | author: string; 27 | // 描述 28 | description: string; 29 | // 插件使用文档 30 | readme: string; 31 | // 入口文件 32 | main: string; 33 | // 版本 34 | version: string; 35 | // logo地址 36 | logo: string; 37 | // 插件状态 38 | status: AdapterStatus; 39 | } 40 | 41 | // 插件运行状态 42 | export type AdapterStatus = 'RUNNING' | 'STOPED' | 'ERROR'; 43 | -------------------------------------------------------------------------------- /src/main/core/server/routes/v1/proxy/index.ts: -------------------------------------------------------------------------------- 1 | import work from './work'; 2 | 3 | export { work }; 4 | -------------------------------------------------------------------------------- /src/main/core/server/routes/v1/setting/index.ts: -------------------------------------------------------------------------------- 1 | import work from './work'; 2 | 3 | export { work }; 4 | -------------------------------------------------------------------------------- /src/main/core/server/routes/v1/site/cms/adapter/index.ts: -------------------------------------------------------------------------------- 1 | import AppYsV2Adapter from './appysv2'; 2 | import CatvodAdapter from './catvod'; 3 | import DrpyJs0Adapter from './drpyjs0'; 4 | import XbpqAdapter from './xbpq'; 5 | import XyqAdapter from './xyq'; 6 | import T1Adapter from './t1Json'; 7 | import T0Adapter from './t0Xml'; 8 | import T3DrpyAdapter from './drpy'; 9 | import T4Adapter from './t4Hipy'; 10 | import T3PyAdapter from './py'; 11 | 12 | export { 13 | AppYsV2Adapter, 14 | CatvodAdapter, 15 | DrpyJs0Adapter, 16 | T0Adapter, 17 | T1Adapter, 18 | T3DrpyAdapter, 19 | T3PyAdapter, 20 | T4Adapter, 21 | XbpqAdapter, 22 | XyqAdapter, 23 | }; 24 | -------------------------------------------------------------------------------- /src/main/core/server/routes/v1/site/cms/adapter/xbpq/index.ts: -------------------------------------------------------------------------------- 1 | import XbpqAdapter from './adapter'; 2 | 3 | export default XbpqAdapter; 4 | -------------------------------------------------------------------------------- /src/main/core/server/routes/v1/site/cms/adapter/xyq/index.ts: -------------------------------------------------------------------------------- 1 | import XyqAdapter from './adapter'; 2 | 3 | export default XyqAdapter; 4 | -------------------------------------------------------------------------------- /src/main/core/server/routes/v1/site/cms/utils/cmsFilter.ts: -------------------------------------------------------------------------------- 1 | export default [ 2 | { 3 | key: 'area', 4 | desc: '地区', 5 | }, 6 | { 7 | key: 'class', 8 | desc: '剧情', 9 | }, 10 | { 11 | key: 'director', 12 | desc: '导演', 13 | }, 14 | { 15 | key: 'lang', 16 | desc: '语言', 17 | }, 18 | { 19 | key: 'star', 20 | desc: '明星', 21 | }, 22 | { 23 | key: 'state', 24 | desc: '状态', 25 | }, 26 | { 27 | key: 'version', 28 | desc: '版本', 29 | }, 30 | { 31 | key: 'year', 32 | desc: '年份', 33 | }, 34 | { 35 | key: 'sort', 36 | desc: '排序', 37 | }, 38 | ]; 39 | -------------------------------------------------------------------------------- /src/main/core/server/routes/v1/site/cms/utils/index.ts: -------------------------------------------------------------------------------- 1 | const buildUrl = (url, paramsStr) => { 2 | const u = new URL(url); 3 | const api = u.origin + u.pathname.replace(/\/$/, ''); 4 | const params = new URLSearchParams(u.search); 5 | 6 | if (paramsStr.startsWith('?') || paramsStr.startsWith('&')) { 7 | const p = new URLSearchParams(paramsStr); 8 | p.forEach((value, key) => params.set(key, value)); 9 | return api + '?' + params.toString(); 10 | } else { 11 | const cleanParamsStr = paramsStr.startsWith('/') ? paramsStr.slice(1) : paramsStr; 12 | return api + (cleanParamsStr ? '/' + cleanParamsStr : ''); 13 | } 14 | }; 15 | 16 | const singleton = any>(className: T): T => { 17 | let instance: InstanceType | null = null; 18 | const proxy = new Proxy(className, { 19 | construct(target, args) { 20 | if (!instance) { 21 | instance = Reflect.construct(target, args); 22 | } 23 | return instance as InstanceType; 24 | }, 25 | }); 26 | proxy.prototype.construct = proxy; 27 | return proxy; 28 | }; 29 | 30 | export { buildUrl, singleton }; 31 | -------------------------------------------------------------------------------- /src/main/core/server/routes/v1/site/config/app_filter.ts: -------------------------------------------------------------------------------- 1 | export default [ 2 | { 3 | key: 'area', 4 | desc: '地区', 5 | }, 6 | { 7 | key: 'class', 8 | desc: '剧情', 9 | }, 10 | { 11 | key: 'director', 12 | desc: '导演', 13 | }, 14 | { 15 | key: 'lang', 16 | desc: '语言', 17 | }, 18 | { 19 | key: 'star', 20 | desc: '明星', 21 | }, 22 | { 23 | key: 'state', 24 | desc: '状态', 25 | }, 26 | { 27 | key: 'version', 28 | desc: '版本', 29 | }, 30 | { 31 | key: 'year', 32 | desc: '年份', 33 | }, 34 | { 35 | key: 'sort', 36 | desc: '排序', 37 | }, 38 | ]; 39 | -------------------------------------------------------------------------------- /src/main/core/server/routes/v1/site/hot/index.ts: -------------------------------------------------------------------------------- 1 | import { FastifyPluginAsync, FastifyRequest } from 'fastify'; 2 | import { setting } from '@main/core/db/service'; 3 | import hotAdapter from './adapter'; 4 | 5 | const API_PREFIX = 'api/v1/hot'; 6 | 7 | const api: FastifyPluginAsync = async (fastify): Promise => { 8 | fastify.get(`/${API_PREFIX}/page`, async (req: FastifyRequest<{ Querystring: { [key: string]: string } }>) => { 9 | const parms = req.query; 10 | const dbResHot = await setting.get('defaultHot'); 11 | const res = await hotAdapter?.[dbResHot](parms); 12 | return { 13 | code: 0, 14 | msg: 'ok', 15 | data: res, 16 | }; 17 | }); 18 | }; 19 | 20 | export default api; 21 | -------------------------------------------------------------------------------- /src/main/core/server/routes/v1/site/index.ts: -------------------------------------------------------------------------------- 1 | import db from './db'; 2 | import cms from './cms'; 3 | import hot from './hot'; 4 | import recomm from './recomm'; 5 | 6 | export { cms, hot, db, recomm }; 7 | -------------------------------------------------------------------------------- /src/main/core/server/routes/v1/site/recomm/index.ts: -------------------------------------------------------------------------------- 1 | import { FastifyPluginAsync, FastifyRequest } from 'fastify'; 2 | import { fetchDoubanRecomm } from './utils/douban'; 3 | 4 | const API_PREFIX = 'api/v1/recommend'; 5 | 6 | const api: FastifyPluginAsync = async (fastify): Promise => { 7 | fastify.get(`/${API_PREFIX}/douban`, async (req: FastifyRequest<{ Querystring: { [key: string]: string } }>) => { 8 | const { name, year, id, type } = req.query; 9 | const res = await fetchDoubanRecomm(name, year, id, type); 10 | 11 | return { 12 | code: 0, 13 | msg: 'ok', 14 | data: res, 15 | }; 16 | }); 17 | }; 18 | 19 | export default api; 20 | -------------------------------------------------------------------------------- /src/main/core/server/routes/v1/star/index.ts: -------------------------------------------------------------------------------- 1 | import work from './work'; 2 | 3 | export { work }; 4 | -------------------------------------------------------------------------------- /src/main/core/server/routes/v1/system/index.ts: -------------------------------------------------------------------------------- 1 | import work from './work'; 2 | 3 | export { work }; 4 | -------------------------------------------------------------------------------- /src/main/core/server/routes/v1/system/work.ts: -------------------------------------------------------------------------------- 1 | import { FastifyPluginAsync, FastifyRequest } from 'fastify'; 2 | import request, { completeRequest } from '@main/utils/request'; 3 | import { ipVersion } from 'is-ip'; 4 | import iconv from 'iconv-lite'; 5 | 6 | const API_PREFIX = 'api/v1/system'; 7 | 8 | const api: FastifyPluginAsync = async (fastify): Promise => { 9 | fastify.get(`/${API_PREFIX}/status`, async () => { 10 | return { code: 0, msg: 'ok', data: 'run' }; 11 | }); 12 | fastify.get(`/${API_PREFIX}/ip`, async () => { 13 | const urls = ['https://ipv6.icanhazip.com', 'https://ipv4.icanhazip.com']; 14 | const res: any = { 15 | ip: '', 16 | version: undefined, 17 | }; 18 | 19 | for (const url of urls) { 20 | try { 21 | const response = await request({ url, method: 'GET' }); 22 | if (response.trim()) res.ip = response.trim(); 23 | res.version = ipVersion(res.ip); 24 | } catch {} 25 | } 26 | return { code: 0, msg: 'ok', data: res }; 27 | }); 28 | fastify.post(`/${API_PREFIX}/config`, async (req: FastifyRequest<{ Body: { [key: string]: string } }>) => { 29 | const options = req.body; 30 | const res = await completeRequest({ ...options }); 31 | return { code: 0, msg: 'ok', data: res }; 32 | }); 33 | fastify.post(`/${API_PREFIX}/html`, async (req: FastifyRequest<{ Body: { [key: string]: string } }>) => { 34 | const options = req.body; 35 | const encode = options.encode || 'UTF-8'; 36 | delete options.encode; 37 | const response = await request({ ...options, responseType: 'arraybuffer' }); 38 | const res = iconv.decode(Buffer.from(response), encode); // 假设后端编码为GBK 39 | 40 | return { code: 0, msg: 'ok', data: res || '' }; 41 | }); 42 | }; 43 | 44 | export default api; 45 | -------------------------------------------------------------------------------- /src/main/core/shortcut/index.ts: -------------------------------------------------------------------------------- 1 | import globalShortcut from './global'; 2 | import localShortcut from './local'; 3 | 4 | export { globalShortcut, localShortcut }; 5 | -------------------------------------------------------------------------------- /src/main/core/tray/index.ts: -------------------------------------------------------------------------------- 1 | import { platform } from '@electron-toolkit/utils'; 2 | import { Tray, Menu, shell, nativeImage } from 'electron'; 3 | import { join } from 'path'; 4 | import logger from '@main/core/logger'; 5 | import { APP_PUBLIC_PATH, APP_STORE_PATH } from '@main/utils/hiker/path'; 6 | import { toggleWinVisable } from '@main/utils/tool'; 7 | 8 | const createTrayMenu = () => { 9 | return Menu.buildFromTemplate([ 10 | { 11 | label: '打开zyfun', 12 | click: () => toggleWinVisable(true), 13 | }, 14 | { 15 | label: '打开数据目录', 16 | click: () => shell.openPath(APP_STORE_PATH), 17 | }, 18 | { label: '关于', role: 'about', }, 19 | { label: '退出', role: 'quit', }, 20 | ]); 21 | }; 22 | 23 | /** 24 | * Create system tray 25 | */ 26 | const createSystemTray = () => { 27 | // const lightIcon = join(APP_PUBLIC_PATH, 'img/icons/tray_light.png'); 28 | const darkIcon = join(APP_PUBLIC_PATH, 'img/icons/tray_dark.png'); 29 | const colorIcon = join(APP_PUBLIC_PATH, 'img/icons/logo.png'); 30 | 31 | // Create tray icon 32 | const icon = nativeImage.createFromPath(platform.isMacOS ? darkIcon : colorIcon); 33 | const trayIcon = icon.resize({ width: 16, height: 16 }); 34 | if (platform.isMacOS) trayIcon.setTemplateImage(true); 35 | const mainTray = new Tray(trayIcon); 36 | 37 | if (!mainTray) { 38 | logger.error('[tray] Failed to create tray'); 39 | return; 40 | } 41 | 42 | // Set application menu 43 | Menu.setApplicationMenu(createTrayMenu()); 44 | mainTray.setToolTip('zyfun'); 45 | 46 | // Left-click event 47 | mainTray.on('click', () => toggleWinVisable(true)); 48 | 49 | // Tray menu 50 | if (!platform.isMacOS) mainTray.setContextMenu(createTrayMenu()); 51 | logger.info('[tray][init] completed'); 52 | }; 53 | 54 | export default createSystemTray; 55 | -------------------------------------------------------------------------------- /src/main/utils/hiker/index.ts: -------------------------------------------------------------------------------- 1 | /**! 2 | * @module hikerFunction 3 | * @brief 海阔适配器通用方法 4 | * @version 0.0.1 5 | * @update 2024-10-25 13:22:56 6 | */ 7 | 8 | import * as base from './base'; 9 | import * as crypto from './crypto'; 10 | import * as file from './file'; 11 | import * as htmlParser from './htmlParser'; 12 | import * as path from './path'; 13 | import * as request from './request'; 14 | import * as store from './store'; 15 | import * as ua from './ua'; 16 | 17 | export { ua, base, request, crypto, store, htmlParser, path, file }; 18 | -------------------------------------------------------------------------------- /src/main/utils/hiker/paste.ts: -------------------------------------------------------------------------------- 1 | const getPastes = () => { 2 | return ['云剪贴板1', '云剪贴板2']; 3 | }; 4 | const sharePaste = (content, paste) => {}; 5 | const parsePaste = (url: string) => {}; 6 | 7 | export { getPastes, sharePaste, parsePaste }; 8 | -------------------------------------------------------------------------------- /src/main/utils/hiker/request.ts: -------------------------------------------------------------------------------- 1 | import * as syncFetch from './syncFetch'; 2 | import * as syncRequest from './syncRequest'; 3 | 4 | // 单独导出 `syncFetch` 和 `syncRequest` 5 | const module = { syncFetch, syncRequest }; 6 | export { module }; 7 | 8 | // 导出 `syncFetch` 中的所有内容 9 | export * from './syncFetch'; 10 | // export * from './syncRequest'; 11 | -------------------------------------------------------------------------------- /src/main/utils/hiker/store.ts: -------------------------------------------------------------------------------- 1 | let store = {}; 2 | const putVar = (key: string, value: string | number) => { 3 | if (typeof value === 'string' || typeof value === 'number') store[key] = value; 4 | }; 5 | const putMyVar = putVar; 6 | const getVar = (key: string, value: string | number) => { 7 | if (typeof value === 'string' || typeof value === 'number') return store[key] || value; 8 | return value; 9 | }; 10 | const getMyVar = getVar; 11 | const clearVar = () => (store = {}); 12 | const clearMyVar = clearVar; 13 | const listVarKeys = () => Object.keys(store); 14 | const listMyVarKeys = listVarKeys; 15 | const storage0 = { 16 | putMyVar: (key: string, value: any) => (store[key] = value), 17 | getMyVar: (key: string, value: any) => store[key] || value, 18 | clearMyVar: (key: string) => (store[key] = {}), 19 | listMyKeys: () => Object.keys(store), 20 | putVar: (key: string, value: any) => (store[key] = value), 21 | getVar: (key: string, value: any) => store[key] || value, 22 | clearVar: (key: string) => (store[key] = {}), 23 | listVarKeys: () => Object.keys(store), 24 | setItem: (key: string, value: any) => (store[key] = value), 25 | getItem: (key: string, value: any) => store[key] || value, 26 | clearItem: (key: string) => (store[key] = {}), 27 | listItemKeys: () => Object.keys(store), 28 | }; 29 | 30 | export { putVar, putMyVar, getVar, getMyVar, clearVar, clearMyVar, listVarKeys, listMyVarKeys, storage0 }; 31 | -------------------------------------------------------------------------------- /src/main/utils/hiker/ua.ts: -------------------------------------------------------------------------------- 1 | const MOBILE_UA = 2 | 'Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.91 Mobile Safari/537.36'; 3 | const PC_UA = 4 | 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.54 Safari/537.36'; 5 | const UA = 'Mozilla/5.0'; 6 | const UC_UA = 7 | 'Mozilla/5.0 (Linux; U; Android 9; zh-CN; MI 9 Build/PKQ1.181121.001) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/57.0.2987.108 UCBrowser/12.5.5.1035 Mobile Safari/537.36'; 8 | const IOS_UA = 9 | 'Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1'; 10 | 11 | export { MOBILE_UA, PC_UA, UA, UC_UA, IOS_UA }; 12 | -------------------------------------------------------------------------------- /src/main/utils/hiker/xpath.ts: -------------------------------------------------------------------------------- 1 | import xpathModule from 'xpath'; 2 | import xmldom from '@xmldom/xmldom'; 3 | 4 | const xpath = (html: string, rule: string) => { 5 | const dom = xmldom.DOMParser; 6 | const doc = new dom().parseFromString(html, 'text/xml'); 7 | const nodes = xpathModule.select(rule, doc); 8 | }; 9 | const xpathArray = (html: string, rule: string) => {}; 10 | const xpa = xpathArray; 11 | 12 | export { xpath, xpathArray, xpa }; 13 | -------------------------------------------------------------------------------- /src/main/utils/lrucache/index.ts: -------------------------------------------------------------------------------- 1 | class LruCache { 2 | cache: Map = new Map(); 3 | capacity: number = 3; 4 | constructor(capacity: number) { 5 | this.capacity = capacity; 6 | this.cache = new Map(); 7 | } 8 | has(key: string): boolean { 9 | return this.cache.has(key); 10 | } 11 | get(key: string): any { 12 | if (!this.cache.has(key)) { 13 | return; 14 | } 15 | const value = this.cache.get(key); 16 | this.cache.delete(key); 17 | this.cache.set(key, value); 18 | return value; 19 | } 20 | put(key: string, value: any): string | Promise { 21 | if (this.cache.has(key)) { 22 | this.cache.delete(key); 23 | } else if (this.cache.size >= this.capacity) { 24 | this.cache.delete(this.cache.keys().next().value); 25 | } 26 | this.cache.set(key, value); 27 | return value; 28 | } 29 | delete(key: string): boolean | Promise { 30 | return this.cache.delete(key); 31 | } 32 | } 33 | 34 | export default LruCache; 35 | -------------------------------------------------------------------------------- /src/preload/index.d.ts: -------------------------------------------------------------------------------- 1 | import { ElectronAPI } from '@electron-toolkit/preload' 2 | 3 | declare global { 4 | interface Window { 5 | electron: ElectronAPI 6 | api: unknown 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/preload/index.ts: -------------------------------------------------------------------------------- 1 | import { contextBridge } from 'electron'; 2 | import { electronAPI } from '@electron-toolkit/preload'; 3 | import { domReady } from './utils/dom'; 4 | import { useLoading } from './utils/loading'; 5 | 6 | const { appendLoading, removeLoading } = useLoading(); 7 | 8 | domReady().then(appendLoading); 9 | 10 | // Custom APIs for renderer 11 | const api = {}; 12 | 13 | // Use `contextBridge` APIs to expose Electron APIs to 14 | // renderer only if context isolation is enabled, otherwise 15 | // just add to the DOM global. 16 | if (process.contextIsolated) { 17 | try { 18 | contextBridge.exposeInMainWorld('electron', electronAPI); 19 | contextBridge.exposeInMainWorld('api', api); 20 | contextBridge.exposeInMainWorld('removeLoading', removeLoading); 21 | } catch (error) { 22 | console.error(error); 23 | } 24 | } else { 25 | // @ts-ignore (define in dts) 26 | window.electron = electronAPI; 27 | // @ts-ignore (define in dts) 28 | window.api = api; 29 | // @ts-ignore (define in dts) 30 | window.removeLoading = removeLoading; 31 | } 32 | -------------------------------------------------------------------------------- /src/preload/utils/dom.ts: -------------------------------------------------------------------------------- 1 | /** docoment ready */ 2 | export function domReady(condition: DocumentReadyState[] = ['complete', 'interactive']) { 3 | return new Promise((resolve) => { 4 | if (condition.includes(document.readyState)) { 5 | resolve(true); 6 | } else { 7 | document.addEventListener('readystatechange', () => { 8 | if (condition.includes(document.readyState)) { 9 | resolve(true); 10 | } 11 | }); 12 | } 13 | }); 14 | } 15 | -------------------------------------------------------------------------------- /src/renderer/auto-imports.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | /* prettier-ignore */ 3 | // @ts-nocheck 4 | // noinspection JSUnusedGlobalSymbols 5 | // Generated by unplugin-auto-import 6 | // biome-ignore lint: disable 7 | export {} 8 | declare global { 9 | const DialogPlugin: typeof import('tdesign-vue-next')['DialogPlugin'] 10 | const MessagePlugin: typeof import('tdesign-vue-next')['MessagePlugin'] 11 | const TBSCertificate: typeof import('tdesign-vue-next')['BSCertificate'] 12 | const TCPlayer: typeof import('tdesign-vue-next')['CPlayer'] 13 | const TDesign: typeof import('tdesign-vue-next')['Design'] 14 | const TDesignChat: typeof import('tdesign-vue-next')['DesignChat'] 15 | const TEN: typeof import('tdesign-vue-next')['EN'] 16 | const THREE: typeof import('tdesign-vue-next')['HREE'] 17 | const TICKING: typeof import('tdesign-vue-next')['ICKING'] 18 | const TIMEOUT: typeof import('tdesign-vue-next')['IMEOUT'] 19 | const TWENTYFOUR: typeof import('tdesign-vue-next')['WENTYFOUR'] 20 | const TWO: typeof import('tdesign-vue-next')['WO'] 21 | } 22 | -------------------------------------------------------------------------------- /src/renderer/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | zyfun 6 | 7 | 11 | 12 | 13 | 14 | 15 | 16 |
读屏标签已关闭
17 |
18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/renderer/src/api/analyze.ts: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request'; 2 | 3 | export function addAnalyze(doc: object) { 4 | return request({ 5 | url: `/v1/analyze`, 6 | method: 'post', 7 | data: doc, 8 | }); 9 | } 10 | 11 | export function delAnalyze(doc: object) { 12 | return request({ 13 | url: `/v1/analyze`, 14 | method: 'delete', 15 | data: doc, 16 | }); 17 | } 18 | 19 | export function putAnalyze(doc: object) { 20 | return request({ 21 | url: `/v1/analyze`, 22 | method: 'put', 23 | data: doc, 24 | }); 25 | } 26 | 27 | export function putAnalyzeDefault(id: string) { 28 | return request({ 29 | url: `/v1/analyze/default/${id}`, 30 | method: 'put', 31 | }); 32 | } 33 | 34 | export function fetchAnalyzeActive() { 35 | return request({ 36 | url: '/v1/analyze/active', 37 | method: 'get', 38 | }); 39 | } 40 | 41 | export function fetchAnalyzePage(doc: object) { 42 | return request({ 43 | url: `/v1/analyze/page`, 44 | method: 'get', 45 | params: doc, 46 | }); 47 | } 48 | 49 | export function fetchAnalyzeTitle(url: string) { 50 | return request({ 51 | url: '/v1/analyze/title', 52 | method: 'get', 53 | params: { 54 | url, 55 | }, 56 | }); 57 | } 58 | -------------------------------------------------------------------------------- /src/renderer/src/api/drive.ts: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request'; 2 | 3 | export function addDrive(doc: object) { 4 | return request({ 5 | url: `/v1/drive`, 6 | method: 'post', 7 | data: doc, 8 | }); 9 | } 10 | 11 | export function delDrive(doc: object) { 12 | return request({ 13 | url: `/v1/drive`, 14 | method: 'delete', 15 | data: doc, 16 | }); 17 | } 18 | 19 | export function putDrive(doc: object) { 20 | return request({ 21 | url: `/v1/drive`, 22 | method: 'put', 23 | data: doc, 24 | }); 25 | } 26 | 27 | export function putDriveDefault(id: string) { 28 | return request({ 29 | url: `/v1/drive/default/${id}`, 30 | method: 'put', 31 | }); 32 | } 33 | 34 | export function fetchDriveActive() { 35 | return request({ 36 | url: '/v1/drive/active', 37 | method: 'get', 38 | }); 39 | } 40 | 41 | export function fetchDrivePage(doc: object) { 42 | return request({ 43 | url: `/v1/drive/page`, 44 | method: 'get', 45 | params: doc, 46 | }); 47 | } 48 | 49 | export function putAlistInit(doc: object) { 50 | return request({ 51 | url: `/v1/alist/init`, 52 | method: 'get', 53 | params: doc, 54 | }); 55 | } 56 | 57 | export function fetchAlistDir(doc: object) { 58 | return request({ 59 | url: `/v1/alist/dir`, 60 | method: 'get', 61 | params: doc, 62 | }); 63 | } 64 | 65 | export function fetchAlistFile(doc: object) { 66 | return request({ 67 | url: `/v1/alist/file`, 68 | method: 'get', 69 | params: doc, 70 | }); 71 | } 72 | -------------------------------------------------------------------------------- /src/renderer/src/api/history.ts: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request'; 2 | 3 | export function fetchHistoryPage(doc: object) { 4 | return request({ 5 | url: '/v1/history/page', 6 | method: 'get', 7 | params: doc, 8 | }); 9 | } 10 | 11 | export function addHistory(doc: object) { 12 | return request({ 13 | url: '/v1/history', 14 | method: 'post', 15 | data: doc, 16 | }); 17 | } 18 | 19 | export function delHistory(doc: object) { 20 | return request({ 21 | url: `/v1/history`, 22 | method: 'delete', 23 | data: doc, 24 | }); 25 | } 26 | 27 | export function putHistory(doc: object) { 28 | return request({ 29 | url: `/v1/history`, 30 | method: 'put', 31 | data: doc, 32 | }); 33 | } 34 | 35 | export function findHistory(doc: object) { 36 | return request({ 37 | url: `/v1/history/find`, 38 | method: 'post', 39 | data: doc, 40 | }); 41 | } 42 | -------------------------------------------------------------------------------- /src/renderer/src/api/index.ts: -------------------------------------------------------------------------------- 1 | import './analyze'; 2 | import './history'; 3 | import './iptv'; 4 | import './setting'; 5 | import './site'; 6 | import './star'; 7 | -------------------------------------------------------------------------------- /src/renderer/src/api/iptv.ts: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request'; 2 | 3 | export function addIptv(doc: object) { 4 | return request({ 5 | url: `/v1/iptv`, 6 | method: 'post', 7 | data: doc, 8 | }); 9 | } 10 | 11 | export function delIptv(doc: object) { 12 | return request({ 13 | url: `/v1/iptv`, 14 | method: 'delete', 15 | data: doc, 16 | }); 17 | } 18 | 19 | export function putIptv(doc: object) { 20 | return request({ 21 | url: `/v1/iptv`, 22 | method: 'put', 23 | data: doc, 24 | }); 25 | } 26 | 27 | export function putIptvDefault(id: string) { 28 | return request({ 29 | url: `/v1/iptv/default/${id}`, 30 | method: 'put', 31 | }); 32 | } 33 | 34 | export function fetchIptvActive() { 35 | return request({ 36 | url: '/v1/iptv/active', 37 | method: 'get', 38 | }); 39 | } 40 | 41 | export function fetchIptvPage(doc: object) { 42 | return request({ 43 | url: `/v1/iptv/page`, 44 | method: 'get', 45 | params: doc, 46 | }); 47 | } 48 | 49 | export function addChannel(doc: object) { 50 | return request({ 51 | url: '/v1/channel', 52 | method: 'post', 53 | data: doc, 54 | }); 55 | } 56 | 57 | export function delChannel(doc: object) { 58 | return request({ 59 | url: `/v1/channel`, 60 | method: 'delete', 61 | data: doc, 62 | }); 63 | } 64 | 65 | export function fetchChannelPage(doc: object) { 66 | return request({ 67 | url: '/v1/channel/page', 68 | method: 'get', 69 | params: doc, 70 | }); 71 | } 72 | 73 | export function fetchChannelDetail(id) { 74 | return request({ 75 | url: `/v1/channel/${id}`, 76 | method: 'get', 77 | }); 78 | } 79 | 80 | export function fetchChannelEpg(doc: object) { 81 | return request({ 82 | url: `/v1/channel/epg`, 83 | method: 'get', 84 | params: doc, 85 | }); 86 | } 87 | -------------------------------------------------------------------------------- /src/renderer/src/api/plugin.ts: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request'; 2 | 3 | export function info(doc: object) { 4 | return request({ 5 | url: `/v1/plugin/info`, 6 | method: 'post', 7 | data: doc, 8 | }); 9 | } 10 | 11 | export function start(doc: object) { 12 | return request({ 13 | url: `/v1/plugin/start`, 14 | method: 'post', 15 | data: doc, 16 | }); 17 | } 18 | 19 | export function stop(doc: object) { 20 | return request({ 21 | url: `/v1/plugin/stop`, 22 | method: 'post', 23 | data: doc, 24 | }); 25 | } 26 | 27 | export function install(doc: object) { 28 | return request({ 29 | url: `/v1/plugin/install`, 30 | method: 'post', 31 | data: doc, 32 | }); 33 | } 34 | 35 | export function uninstall(doc: object) { 36 | return request({ 37 | url: `/v1/plugin/uninstall`, 38 | method: 'post', 39 | data: doc, 40 | }); 41 | } 42 | 43 | export function fetchLog(id: string) { 44 | return request({ 45 | url: `/v1/plugin/log/${id}`, 46 | method: 'get', 47 | }); 48 | } 49 | 50 | export function clearLog(id: string) { 51 | return request({ 52 | url: `/v1/plugin/log/${id}`, 53 | method: 'delete', 54 | }); 55 | } 56 | 57 | export function list() { 58 | return request({ 59 | url: `/v1/plugin/list`, 60 | method: 'get', 61 | }); 62 | } 63 | -------------------------------------------------------------------------------- /src/renderer/src/api/proxy.ts: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request'; 2 | 3 | export function setT3Proxy(doc) { 4 | return request({ 5 | url: 'http://127.0.0.1:9978/proxy', 6 | method: 'post', 7 | data: doc, 8 | }); 9 | } 10 | -------------------------------------------------------------------------------- /src/renderer/src/api/star.ts: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request'; 2 | export function addStar(doc: object) { 3 | return request({ 4 | url: `/v1/star`, 5 | method: 'post', 6 | data: doc, 7 | }); 8 | } 9 | export function delStar(doc: object) { 10 | return request({ 11 | url: `/v1/star`, 12 | method: 'delete', 13 | data: doc, 14 | }); 15 | } 16 | export function putStar(doc: object) { 17 | return request({ 18 | url: `/v1/star`, 19 | method: 'put', 20 | data: doc, 21 | }); 22 | } 23 | export function fetchStarPage(doc: object) { 24 | return request({ 25 | url: '/v1/star/page', 26 | method: 'get', 27 | params: doc, 28 | }); 29 | } 30 | 31 | export function fetchStarDetail(id: string) { 32 | return request({ 33 | url: `/v1/star/${id}`, 34 | method: 'get', 35 | }); 36 | } 37 | 38 | export function findStar(doc: object) { 39 | return request({ 40 | url: `/v1/star/find`, 41 | method: 'post', 42 | data: doc, 43 | }); 44 | } 45 | -------------------------------------------------------------------------------- /src/renderer/src/assets/ad/raincloud.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hiram-Wong/ZyPlayer/d61392132094d06a103c9f9c979e8fe88b0810df/src/renderer/src/assets/ad/raincloud.png -------------------------------------------------------------------------------- /src/renderer/src/assets/ad/sp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hiram-Wong/ZyPlayer/d61392132094d06a103c9f9c979e8fe88b0810df/src/renderer/src/assets/ad/sp.png -------------------------------------------------------------------------------- /src/renderer/src/assets/ai/openai.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hiram-Wong/ZyPlayer/d61392132094d06a103c9f9c979e8fe88b0810df/src/renderer/src/assets/ai/openai.png -------------------------------------------------------------------------------- /src/renderer/src/assets/ai/openai_kimi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hiram-Wong/ZyPlayer/d61392132094d06a103c9f9c979e8fe88b0810df/src/renderer/src/assets/ai/openai_kimi.png -------------------------------------------------------------------------------- /src/renderer/src/assets/ai/user.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hiram-Wong/ZyPlayer/d61392132094d06a103c9f9c979e8fe88b0810df/src/renderer/src/assets/ai/user.png -------------------------------------------------------------------------------- /src/renderer/src/assets/assets-setting-dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/renderer/src/assets/copy.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/renderer/src/assets/folder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hiram-Wong/ZyPlayer/d61392132094d06a103c9f9c979e8fe88b0810df/src/renderer/src/assets/folder.png -------------------------------------------------------------------------------- /src/renderer/src/assets/font/JetBrainsMono-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hiram-Wong/ZyPlayer/d61392132094d06a103c9f9c979e8fe88b0810df/src/renderer/src/assets/font/JetBrainsMono-Regular.woff2 -------------------------------------------------------------------------------- /src/renderer/src/assets/font/MiSans-Medium.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hiram-Wong/ZyPlayer/d61392132094d06a103c9f9c979e8fe88b0810df/src/renderer/src/assets/font/MiSans-Medium.woff2 -------------------------------------------------------------------------------- /src/renderer/src/assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hiram-Wong/ZyPlayer/d61392132094d06a103c9f9c979e8fe88b0810df/src/renderer/src/assets/icon.png -------------------------------------------------------------------------------- /src/renderer/src/assets/lazy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hiram-Wong/ZyPlayer/d61392132094d06a103c9f9c979e8fe88b0810df/src/renderer/src/assets/lazy.png -------------------------------------------------------------------------------- /src/renderer/src/assets/pay/ali.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hiram-Wong/ZyPlayer/d61392132094d06a103c9f9c979e8fe88b0810df/src/renderer/src/assets/pay/ali.webp -------------------------------------------------------------------------------- /src/renderer/src/assets/pay/qr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hiram-Wong/ZyPlayer/d61392132094d06a103c9f9c979e8fe88b0810df/src/renderer/src/assets/pay/qr.png -------------------------------------------------------------------------------- /src/renderer/src/assets/pay/wechat.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hiram-Wong/ZyPlayer/d61392132094d06a103c9f9c979e8fe88b0810df/src/renderer/src/assets/pay/wechat.webp -------------------------------------------------------------------------------- /src/renderer/src/assets/platform/icon/pptv.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/renderer/src/assets/player/pause.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/renderer/src/assets/player/pip.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/renderer/src/assets/player/play-next.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /src/renderer/src/assets/player/play.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/renderer/src/assets/player/playon-green.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hiram-Wong/ZyPlayer/d61392132094d06a103c9f9c979e8fe88b0810df/src/renderer/src/assets/player/playon-green.gif -------------------------------------------------------------------------------- /src/renderer/src/assets/player/setting.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/renderer/src/assets/player/voice-no.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 8 | 10 | 12 | -------------------------------------------------------------------------------- /src/renderer/src/assets/player/voice.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | 11 | 14 | -------------------------------------------------------------------------------- /src/renderer/src/assets/player/zoom-s.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /src/renderer/src/assets/player/zoom.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /src/renderer/src/assets/rectangle_common-1.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 矩形 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/renderer/src/assets/rectangle_common.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | 矩形备份 2 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/renderer/src/components/code-editor/index.ts: -------------------------------------------------------------------------------- 1 | import CodeEditor from './src/code-editor'; 2 | export * from './src/code-editor-types'; 3 | 4 | export { CodeEditor }; 5 | -------------------------------------------------------------------------------- /src/renderer/src/components/code-editor/src/code-editor-types.ts: -------------------------------------------------------------------------------- 1 | import { ExtractPropTypes, PropType, Ref } from 'vue'; 2 | 3 | export type Mode = 'normal' | 'diff' | 'review'; 4 | export type Theme = 'light' | 'dark'; 5 | 6 | export interface Decoration { 7 | lineNumber: number; 8 | icon?: string; 9 | customClasses?: string; 10 | glyphClassName?: string; 11 | } 12 | export interface Comment { 13 | lineNumber: number; 14 | isExpanded: boolean; 15 | domNode?: HTMLElement; 16 | heightInPx?: number; 17 | allowEditorOverflow?: boolean; 18 | offsetLeft?: number; 19 | } 20 | 21 | export const codeEditorProps = { 22 | modelValue: { 23 | type: String, 24 | default: '', 25 | }, 26 | mode: { 27 | type: String as PropType, 28 | default: 'normal', 29 | }, 30 | originalText: { 31 | type: String, 32 | default: '', 33 | }, 34 | theme: { 35 | type: String as PropType, 36 | default: 'light', 37 | }, 38 | autoHeight: { 39 | type: Boolean, 40 | default: false, 41 | }, 42 | refreshAll: { 43 | type: Boolean, 44 | default: false, 45 | }, 46 | offsetLeft: { 47 | type: Number, 48 | }, 49 | addCommentIcon: { 50 | type: String, 51 | default: '', 52 | }, 53 | expandCommentIcon: { 54 | type: String, 55 | default: '', 56 | }, 57 | options: { 58 | type: Object, 59 | default: () => ({}), 60 | }, 61 | mouseTargetTypes: { 62 | type: Array as PropType, 63 | default: () => [2, 4], 64 | }, 65 | editorDecorations: { 66 | type: Array as PropType, 67 | default: () => [], 68 | }, 69 | comments: { 70 | type: Array as PropType, 71 | default: () => [], 72 | }, 73 | }; 74 | 75 | export type CodeEditorProps = ExtractPropTypes; 76 | 77 | export interface UseCodeEditor { 78 | editorEl: Ref; 79 | } 80 | 81 | export interface PositionInfo { 82 | top?: number; 83 | height?: number; 84 | } 85 | 86 | export interface LayoutInfo extends PositionInfo { 87 | minimapWidth?: number; 88 | offsetLeft?: number; 89 | } 90 | -------------------------------------------------------------------------------- /src/renderer/src/components/code-editor/src/code-editor.less: -------------------------------------------------------------------------------- 1 | .code-editor { 2 | display: block; 3 | position: relative; 4 | width: 100%; 5 | height: 100%; 6 | 7 | div.icon-pointer { 8 | cursor: pointer; 9 | } 10 | 11 | .monaco-editor { 12 | .find-widget { 13 | & > .button.codicon-widget-close { 14 | top: 8px; 15 | right: 8px; 16 | } 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/renderer/src/components/code-editor/src/code-editor.tsx: -------------------------------------------------------------------------------- 1 | import { defineComponent } from 'vue'; 2 | import type { SetupContext } from 'vue'; 3 | import { codeEditorProps, CodeEditorProps } from './code-editor-types'; 4 | import { useCodeEditor } from './composables/use-code-editor'; 5 | import './code-editor.less'; 6 | 7 | export default defineComponent({ 8 | name: 'CodeEditor', 9 | props: codeEditorProps, 10 | emits: ['update:modelValue', 'afterEditorInit', 'click', 'monacoObject'], 11 | setup(props: CodeEditorProps, ctx: SetupContext) { 12 | const { editorEl } = useCodeEditor(props, ctx); 13 | return () =>
; 14 | } 15 | }); 16 | -------------------------------------------------------------------------------- /src/renderer/src/components/common-setting/note/index.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 59 | 60 | 93 | -------------------------------------------------------------------------------- /src/renderer/src/components/markdown-render/style/index.less: -------------------------------------------------------------------------------- 1 | @import './highlight.less'; 2 | @import './github-markdown.less'; 3 | 4 | .markdown-body { 5 | background-color: transparent !important; 6 | font-size: 14px; 7 | 8 | blockquote { 9 | border-left: 0.25em solid var(--td-text-color-disabled); 10 | color: var(--td-text-color-secondary); 11 | } 12 | 13 | img { 14 | max-width: 35% !important; 15 | } 16 | 17 | p { 18 | white-space: pre-wrap; 19 | } 20 | 21 | pre code, 22 | pre tt { 23 | line-height: 1.65; 24 | } 25 | 26 | .highlight pre, 27 | pre { 28 | background-color: #f1f1f1; 29 | } 30 | 31 | code.hljs { 32 | padding: 0; 33 | } 34 | 35 | .code-block { 36 | &-wrapper { 37 | position: relative; 38 | padding-top: 24px; 39 | } 40 | 41 | &-header { 42 | position: absolute; 43 | top: 5px; 44 | right: 0; 45 | width: 100%; 46 | padding: 0 1rem; 47 | display: flex; 48 | justify-content: flex-end; 49 | align-items: center; 50 | color: #b3b3b3; 51 | 52 | &__copy{ 53 | cursor: pointer; 54 | margin-left: 0.5rem; 55 | user-select: none; 56 | &:hover { 57 | color: #65a665; 58 | } 59 | } 60 | } 61 | } 62 | } 63 | 64 | :root[theme-mode='dark'] { 65 | .highlight pre, 66 | pre { 67 | background-color: #282c34; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/renderer/src/components/markdown-render/types.ts: -------------------------------------------------------------------------------- 1 | export interface Label { 2 | copy?: string; 3 | lang?: string; 4 | copySuccess?: string; 5 | copyError?: string; 6 | }; 7 | -------------------------------------------------------------------------------- /src/renderer/src/components/player/index.ts: -------------------------------------------------------------------------------- 1 | import MultiPlayer from './src/multi-player'; 2 | import { mediaUtils } from './src/utils/tool'; 3 | 4 | export { MultiPlayer, mediaUtils }; 5 | export type { MultiPlayerInstance } from './src/multi-player'; 6 | -------------------------------------------------------------------------------- /src/renderer/src/components/player/src/assets/css/index.less: -------------------------------------------------------------------------------- 1 | .multi-player { 2 | position: relative; 3 | width: 100%; 4 | height: 100%; 5 | background: url(../img/bg-player.jpg) center center no-repeat; 6 | background-size: cover; 7 | 8 | .multi-mse { 9 | width: 100%; 10 | height: 100%; 11 | }; 12 | } 13 | -------------------------------------------------------------------------------- /src/renderer/src/components/player/src/assets/img/bg-player-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hiram-Wong/ZyPlayer/d61392132094d06a103c9f9c979e8fe88b0810df/src/renderer/src/components/player/src/assets/img/bg-player-1.jpg -------------------------------------------------------------------------------- /src/renderer/src/components/player/src/assets/img/bg-player-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hiram-Wong/ZyPlayer/d61392132094d06a103c9f9c979e8fe88b0810df/src/renderer/src/components/player/src/assets/img/bg-player-2.jpg -------------------------------------------------------------------------------- /src/renderer/src/components/player/src/assets/img/bg-player-3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hiram-Wong/ZyPlayer/d61392132094d06a103c9f9c979e8fe88b0810df/src/renderer/src/components/player/src/assets/img/bg-player-3.jpg -------------------------------------------------------------------------------- /src/renderer/src/components/player/src/assets/img/bg-player-4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hiram-Wong/ZyPlayer/d61392132094d06a103c9f9c979e8fe88b0810df/src/renderer/src/components/player/src/assets/img/bg-player-4.jpg -------------------------------------------------------------------------------- /src/renderer/src/components/player/src/assets/img/bg-player.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hiram-Wong/ZyPlayer/d61392132094d06a103c9f9c979e8fe88b0810df/src/renderer/src/components/player/src/assets/img/bg-player.jpg -------------------------------------------------------------------------------- /src/renderer/src/components/player/src/core/index.ts: -------------------------------------------------------------------------------- 1 | import ArtPlayerAdapter from './artplayer'; 2 | // import DPlayerAdapter from './dplayer'; 3 | // import NPlayerAdapter from './nplayer'; 4 | import XgPlayerAdapter from './xgplayer'; 5 | import OPlayerAdapter from './oplayer'; 6 | 7 | // export { ArtPlayerAdapter, DPlayerAdapter, NPlayerAdapter, XgPlayerAdapter, OPlayerAdapter }; 8 | export { ArtPlayerAdapter, XgPlayerAdapter, OPlayerAdapter }; 9 | -------------------------------------------------------------------------------- /src/renderer/src/components/player/src/core/oplayer/css/index.css: -------------------------------------------------------------------------------- 1 | .css-wb262f button { 2 | background-color: transparent !important; 3 | } 4 | 5 | /* process */ 6 | .css-njx1sw { 7 | background-color: transparent; 8 | background-image: linear-gradient(270deg, #00e038, #32ccff); 9 | } 10 | 11 | .css-nyks6b .css-6ae98g, .css-nyks6b .css-njx1sw { 12 | border-radius: 10px; 13 | } 14 | 15 | .css-f4aimg { 16 | padding: 5px 0 !important; 17 | } 18 | 19 | /* volume setting tool-tip */ 20 | .css-fy6n4p, .css-1y41a61, .css-12rog9r, .css-181s0gc::after { 21 | border-radius: 8px !important; 22 | backdrop-filter: saturate(180%) blur(20px); 23 | background-color: #000000bf !important; 24 | } 25 | 26 | /* volume */ 27 | .css-1jiyo3p { 28 | background-color: transparent; 29 | background-image: linear-gradient(90deg, #00e038, #32ccff); 30 | } 31 | 32 | /* danmuku */ 33 | .css-xpnf3v { 34 | display: flex; 35 | margin: 0 10px; 36 | } 37 | 38 | /* button */ 39 | .css-wb262f .css-rte19r { 40 | width: 20px !important; 41 | height: 20px !important; 42 | margin-right: 16px !important; 43 | } 44 | 45 | .css-76twjl > .css-rte19r:last-child { 46 | margin-right: 0px !important; 47 | } 48 | -------------------------------------------------------------------------------- /src/renderer/src/components/player/src/core/vlc/experimental.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hiram-Wong/ZyPlayer/d61392132094d06a103c9f9c979e8fe88b0810df/src/renderer/src/components/player/src/core/vlc/experimental.wasm -------------------------------------------------------------------------------- /src/renderer/src/components/player/src/core/xgplayer/plugins/playNext/index.ts: -------------------------------------------------------------------------------- 1 | import { Plugin, Sniffer } from 'xgplayer'; 2 | import { publicIcons } from '../../../../utils/static'; 3 | import emitter from '@/utils/emitter'; 4 | 5 | const { POSITIONS } = Plugin; 6 | const Next = publicIcons.playNext; 7 | 8 | function xgIconTips (plugin, textKey, isShow) { 9 | try { 10 | return `
11 | ${plugin.i18n[textKey]} 12 |
` 13 | } catch (e) { 14 | return '
' 15 | } 16 | } 17 | 18 | export default class PlayNextIcon extends Plugin { 19 | static get pluginName () { 20 | return 'videoNext' 21 | } 22 | 23 | static get defaultConfig () { 24 | return { 25 | position: POSITIONS.CONTROLS_LEFT, 26 | index: 1 27 | } 28 | } 29 | 30 | constructor (options) { 31 | super(options) 32 | } 33 | 34 | afterCreate () { 35 | this.appendChild('.xgplayer-icon', this.icons.playNext) 36 | this.initEvents() 37 | } 38 | 39 | registerIcons () { 40 | return { 41 | playNext: Next 42 | } 43 | } 44 | 45 | initEvents () { 46 | // this.nextHandler = this.hook('nextClick', this.changeSrc) 47 | const event = Sniffer.device === 'mobile' ? 'touchend' : 'click' 48 | this.bind(event, this.playNext) 49 | this.show() 50 | } 51 | 52 | playNext = () => { 53 | emitter.emit('nextVideo'); 54 | } 55 | 56 | destroy () { 57 | this.unbind(['touchend', 'click'], this.playNext) 58 | } 59 | 60 | render () { 61 | return ` 62 | 63 |
64 |
65 | ${xgIconTips(this, 'PLAYNEXT_TIPS', this.playerConfig.isHideTips)} 66 |
67 | ` 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/renderer/src/components/split/hooks/use-merge-state.ts: -------------------------------------------------------------------------------- 1 | import { Ref, toRefs, computed, watch, ComputedRef } from 'vue'; 2 | import { isUndefined } from '../utils/is'; 3 | import useState from './use-state'; 4 | 5 | export default function useMergeState( 6 | defaultValue: T, 7 | props: { value: E } 8 | ): [ComputedRef, (val: E) => void, Ref] { 9 | const { value } = toRefs(props); 10 | const [localValue, setLocalValue] = useState( 11 | !isUndefined(value.value) ? value.value : defaultValue 12 | ); 13 | watch(value, (newVal) => { 14 | isUndefined(newVal) && setLocalValue(undefined); 15 | }); 16 | 17 | const mergeValue = computed(() => 18 | !isUndefined(value.value) ? value.value : localValue.value 19 | ); 20 | 21 | return [mergeValue, setLocalValue, localValue]; 22 | } 23 | -------------------------------------------------------------------------------- /src/renderer/src/components/split/hooks/use-state.ts: -------------------------------------------------------------------------------- 1 | import { Ref, ref } from 'vue'; 2 | 3 | export default function useState( 4 | defaultValue?: T 5 | ): [Ref, (newValue: T) => void] { 6 | const value = ref(defaultValue) as Ref; 7 | const setValue = (newValue: T) => { 8 | value.value = newValue; 9 | }; 10 | 11 | return [value, setValue]; 12 | } 13 | -------------------------------------------------------------------------------- /src/renderer/src/components/split/icon/drag-dot-vertical.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/renderer/src/components/split/icon/drag-dot.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/renderer/src/components/split/interface.ts: -------------------------------------------------------------------------------- 1 | export interface SplitProps { 2 | component: string; 3 | direction: 'horizontal' | 'vertical'; 4 | size: number | string | undefined; 5 | defaultSize: number | string; 6 | min: number | string | undefined; 7 | max: number | string | undefined; 8 | disabled: boolean; 9 | } 10 | -------------------------------------------------------------------------------- /src/renderer/src/components/split/resize-trigger.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 65 | -------------------------------------------------------------------------------- /src/renderer/src/components/split/style/index.less: -------------------------------------------------------------------------------- 1 | /** 公共前缀 */ 2 | @starter-prefix: zy; 3 | @split-prefix-cls: ~'@{starter-prefix}-split'; 4 | @split-trigger-prefix-cls: ~'@{split-prefix-cls}-trigger'; 5 | @split-trigger-color-icon: var(--td-text-color-primary); 6 | @split-trigger-font-size-icon: 12px; 7 | @split-trigger-color-background: var(--td-border-level-1-color); 8 | 9 | .@{split-prefix-cls} { 10 | display: flex; 11 | 12 | &-pane { 13 | overflow: auto; 14 | } 15 | 16 | &-pane-second { 17 | flex: 1; 18 | } 19 | 20 | &-horizontal { 21 | flex-direction: row; 22 | } 23 | 24 | &-vertical { 25 | flex-direction: column; 26 | } 27 | } 28 | 29 | // 伸缩触发杆 30 | .@{split-trigger-prefix-cls} { 31 | &-icon-wrapper { 32 | display: flex; 33 | align-items: center; 34 | justify-content: center; 35 | height: 100%; 36 | color: @split-trigger-color-icon; 37 | font-size: @split-trigger-font-size-icon; 38 | line-height: 1; 39 | background-color: @split-trigger-color-background; 40 | } 41 | 42 | &-icon { 43 | display: inline-block; 44 | margin: -3px; 45 | width: 1em; 46 | height: 1em; 47 | color: inherit; 48 | font-style: normal; 49 | vertical-align: -2px; 50 | outline: none; 51 | stroke: currentColor; 52 | } 53 | 54 | &-vertical { 55 | height: 100%; 56 | cursor: col-resize; 57 | } 58 | 59 | &-horizontal { 60 | width: 100%; 61 | cursor: row-resize; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/renderer/src/components/split/utils/convert-case.ts: -------------------------------------------------------------------------------- 1 | // 转换大小写 2 | export const toKebabCase = (string: string): string => { 3 | return string.replace(/\B([A-Z])/g, '-$1').toLowerCase(); 4 | }; 5 | 6 | // 连字符转驼峰 7 | export const toPascalCase = (string: string): string => { 8 | return string 9 | .replace(/^./, (match) => match.toUpperCase()) 10 | .replace(/-(\w)/g, (_, p1: string) => { 11 | return p1?.toUpperCase() ?? ''; 12 | }); 13 | }; 14 | 15 | // 驼峰转连字符 16 | export const toCamelCase = (string: string): string => { 17 | return string 18 | .replace(/^./, (match) => match.toLowerCase()) 19 | .replace(/-(\w)/g, (_, p1: string) => { 20 | return p1?.toUpperCase() ?? ''; 21 | }); 22 | }; 23 | -------------------------------------------------------------------------------- /src/renderer/src/components/split/utils/types.ts: -------------------------------------------------------------------------------- 1 | import type { App, RenderFunction } from 'vue'; 2 | import { VNode } from 'vue'; 3 | 4 | export interface ArcoOptions { 5 | classPrefix?: string; 6 | componentPrefix?: string; 7 | } 8 | 9 | export interface ArcoIconOptions { 10 | iconPrefix?: string; 11 | } 12 | 13 | export interface ArcoGlobalConfig { 14 | classPrefix?: string; 15 | } 16 | 17 | type UnionToIntersection = (U extends any ? (k: U) => void : never) extends ( 18 | k: infer I 19 | ) => void 20 | ? I 21 | : never; 22 | 23 | export type BaseType = string | number; 24 | export type UnionType = BaseType | Record; 25 | export type Data = Record; 26 | export type RenderContent = string | RenderFunction; 27 | 28 | export type EmitFn = (event: T, ...args: any[]) => void; 29 | 30 | export type EmitFn2< 31 | Options = Record, 32 | Event extends keyof Options = keyof Options 33 | > = UnionToIntersection< 34 | { 35 | [key in Event]: Options[key] extends (...args: infer Args) => any 36 | ? (event: key, ...args: Args) => void 37 | : (event: key, ...args: any[]) => void; 38 | }[Event] 39 | >; 40 | 41 | export type EmitType = T | T[]; 42 | 43 | export type SFCWithInstall> = T & 44 | D & { 45 | install: (app: App, opt?: ArcoOptions) => void; 46 | }; 47 | 48 | export type ClassName = 49 | | string 50 | | Record 51 | | (string | Record)[]; 52 | 53 | export type FieldString = { 54 | [K in keyof T]?: string; 55 | }; 56 | 57 | export interface SlotChildren { 58 | value?: VNode[]; 59 | } 60 | 61 | export interface ValueData { 62 | value: string | number; 63 | label: string; 64 | closable?: boolean; 65 | 66 | [other: string]: any; 67 | } 68 | 69 | export type AnimationDuration = 70 | | number 71 | | { 72 | enter: number; 73 | leave: number; 74 | }; 75 | -------------------------------------------------------------------------------- /src/renderer/src/components/tag-nav/index.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 46 | 47 | 52 | -------------------------------------------------------------------------------- /src/renderer/src/config/ai.ts: -------------------------------------------------------------------------------- 1 | import { computed } from 'vue'; 2 | import { t } from '@/locales'; 3 | 4 | const platform = computed(() => { 5 | return [ 6 | { 7 | id: 'openai', 8 | name: t('pages.lab.aiBrain.platform.openai'), 9 | url: 'https://platform.openai.com/api-keys', 10 | }, 11 | { 12 | id: 'deepseek', 13 | name: t('pages.lab.aiBrain.platform.deepseek'), 14 | url: 'https://platform.deepseek.com/api_keys', 15 | }, 16 | { 17 | id: 'kimi', 18 | name: t('pages.lab.aiBrain.platform.kimi'), 19 | url: 'https://platform.moonshot.cn/console/api-keys', 20 | }, 21 | { 22 | id: 'free', 23 | name: t('pages.lab.aiBrain.platform.free'), 24 | url: 'https://github.com/chatanywhere/GPT_API_free', 25 | }, 26 | ]; 27 | }); 28 | 29 | export { platform }; 30 | -------------------------------------------------------------------------------- /src/renderer/src/config/doh.ts: -------------------------------------------------------------------------------- 1 | export default [ 2 | { 3 | name: 'Tencent', 4 | dns: 'https://sm2.doh.pub/dns-query', 5 | }, 6 | { 7 | name: 'AliYun', 8 | dns: 'https://dns.alidns.com/dns-query', 9 | }, 10 | { 11 | name: '360', 12 | dns: 'https://doh.360.cn/dns-query', 13 | }, 14 | { 15 | name: 'Cloudflare', 16 | dns: 'https://cloudflare-dns.com/dns-query', 17 | }, 18 | { 19 | name: 'OpenDNS', 20 | dns: 'https://doh.opendns.com/dns-query', 21 | }, 22 | ]; 23 | -------------------------------------------------------------------------------- /src/renderer/src/config/global.ts: -------------------------------------------------------------------------------- 1 | export const prefix = 'zy'; 2 | -------------------------------------------------------------------------------- /src/renderer/src/config/play.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | type: '', 3 | status: false, 4 | setting: { 5 | playerMode: { 6 | type: 'artplayer', 7 | external: '', 8 | }, 9 | snifferMode: { 10 | type: 'pie', 11 | url: '', 12 | }, 13 | playConf: { 14 | skipHeadAndEnd: false, 15 | playNextPreload: false, 16 | playNextEnabled: true, 17 | skipAd: false, 18 | }, 19 | barrage: { 20 | url: '', 21 | key: '', 22 | support: [], 23 | start: '', 24 | mode: '', 25 | color: '', 26 | content: '', 27 | }, 28 | }, 29 | data: { 30 | info: {}, 31 | ext: {}, 32 | }, 33 | }; 34 | -------------------------------------------------------------------------------- /src/renderer/src/config/system.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | mode: 'light', 3 | theme: 'light', 4 | brandTheme: '#85d46e', 5 | sysConfigSwitch: 'configBase', 6 | timeout: 5000 7 | }; 8 | -------------------------------------------------------------------------------- /src/renderer/src/config/ua.ts: -------------------------------------------------------------------------------- 1 | export default [ 2 | { 3 | name: 'Chrome', 4 | ua: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36', 5 | }, 6 | // { 7 | // name: 'Edge', 8 | // ua: 'Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36 Edg/112.0.1722.34', 9 | // }, 10 | { 11 | name: 'Safari', 12 | ua: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.5 Safari/605.1.15', 13 | }, 14 | // { 15 | // name: 'Firefox', 16 | // ua: 'Mozilla/5.0 (Windows NT 5.1; rv:5.0) Gecko/20100101 Firefox/5.0', 17 | // }, 18 | // { 19 | // name: 'MOBILE', 20 | // ua: 'Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.91 Mobile Safari/537.36', 21 | // }, 22 | // { 23 | // name: 'UC', 24 | // ua: 'Mozilla/5.0 (Linux; U; Android 9; zh-CN; MI 9 Build/PKQ1.181121.001) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/57.0.2987.108 UCBrowser/12.5.5.1035 Mobile Safari/537.36', 25 | // }, 26 | { 27 | name: 'Android', 28 | ua: 'Mozilla/5.0 (Linux; U; Android 9; zh-CN; MI 9 Build/PKQ1.181121.001) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/57.0.2987.108 UCBrowser/12.5.5.1035 Mobile Safari/537.36', 29 | }, 30 | { 31 | name: 'IPhone', 32 | ua: 'Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1', 33 | }, 34 | { 35 | name: 'Mpv', 36 | ua: 'libmpv', 37 | }, 38 | { 39 | name: 'OkHttp', 40 | ua: 'okhttp/4.9.1', 41 | }, 42 | { 43 | name: 'Dart', 44 | ua: 'Dart/2.14 (dart:io)', 45 | }, 46 | ]; 47 | -------------------------------------------------------------------------------- /src/renderer/src/env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | declare module '*.vue' { 4 | import type { DefineComponent } from 'vue' 5 | // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types 6 | const component: DefineComponent<{}, {}, any> 7 | export default component 8 | } 9 | -------------------------------------------------------------------------------- /src/renderer/src/layouts/components/Content.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 33 | 44 | -------------------------------------------------------------------------------- /src/renderer/src/layouts/components/HistoryControl.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 41 | 42 | 78 | -------------------------------------------------------------------------------- /src/renderer/src/layouts/components/Lab.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 19 | -------------------------------------------------------------------------------- /src/renderer/src/layouts/components/Language.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /src/renderer/src/layouts/components/SystemConfig.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 33 | -------------------------------------------------------------------------------- /src/renderer/src/layouts/components/SystemControl.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 9 | 10 | 25 | -------------------------------------------------------------------------------- /src/renderer/src/layouts/components/SystemPin.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 25 | -------------------------------------------------------------------------------- /src/renderer/src/layouts/components/SystemSkin.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 45 | 46 | 57 | -------------------------------------------------------------------------------- /src/renderer/src/layouts/index.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /src/renderer/src/locales/index.ts: -------------------------------------------------------------------------------- 1 | import { useLocalStorage, usePreferredLanguages } from '@vueuse/core'; 2 | import { DropdownOption } from 'tdesign-vue-next'; 3 | import { computed } from 'vue'; 4 | import { createI18n } from 'vue-i18n'; 5 | 6 | // 导入语言文件 7 | const langModules = import.meta.glob('./lang/*/index.ts', { eager: true }); 8 | 9 | const langModuleMap = new Map(); 10 | 11 | export const langCode: Array = []; 12 | 13 | export const localeConfigKey = 'zy-locale'; 14 | 15 | // 获取浏览器默认语言环境 16 | const languages = usePreferredLanguages(); 17 | 18 | // 生成语言模块列表 19 | const generateLangModuleMap = () => { 20 | const fullPaths = Object.keys(langModules); 21 | fullPaths.forEach((fullPath) => { 22 | const k = fullPath.replace('./lang', ''); 23 | const startIndex = 1; 24 | const lastIndex = k.lastIndexOf('/'); 25 | const code = k.substring(startIndex, lastIndex); 26 | langCode.push(code); 27 | langModuleMap.set(code, langModules[fullPath]); 28 | }); 29 | }; 30 | 31 | // 导出 Message 32 | const importMessages = computed(() => { 33 | generateLangModuleMap(); 34 | 35 | const message: Recordable = {}; 36 | langModuleMap.forEach((value: any, key) => { 37 | message[key] = value.default; 38 | }); 39 | return message; 40 | }); 41 | 42 | export const i18n = createI18n({ 43 | legacy: false, 44 | locale: useLocalStorage(localeConfigKey, 'zh_CN').value || languages.value[0] || 'zh_CN', 45 | fallbackLocale: 'zh_CN', 46 | messages: importMessages.value, 47 | globalInjection: true, 48 | }); 49 | 50 | export const langList = computed(() => { 51 | if (langModuleMap.size === 0) generateLangModuleMap(); 52 | 53 | const list: DropdownOption[] = []; 54 | langModuleMap.forEach((value: any, key) => { 55 | list.push({ 56 | content: value.default.lang, 57 | value: key, 58 | }); 59 | }); 60 | 61 | return list; 62 | }); 63 | 64 | // @ts-ignore 65 | export const { t } = i18n.global; 66 | 67 | export default i18n; 68 | -------------------------------------------------------------------------------- /src/renderer/src/locales/lang/en_US/index.ts: -------------------------------------------------------------------------------- 1 | import componentsLocale from 'tdesign-vue-next/es/locale/en_US'; 2 | 3 | import pages from './pages'; 4 | 5 | export default { 6 | lang: 'English', 7 | pages, 8 | componentsLocale, 9 | }; 10 | -------------------------------------------------------------------------------- /src/renderer/src/locales/lang/en_US/pages/analyze.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | name: 'Analyze', 3 | noPlay: 'Not Play', 4 | play: 'Play', 5 | source: 'Source', 6 | support: 'SupportPlatform', 7 | history: { 8 | title: 'History', 9 | clear: 'Clear', 10 | cancel: 'Cancel', 11 | }, 12 | dialog: { 13 | cancel: 'Cancel', 14 | confirm: 'Confirm', 15 | header: 'Delete the record', 16 | body: 'Are you sure you want to delete all records? Recovery is not supported after deletion.', 17 | }, 18 | search: { 19 | input: 'Enter keyword to search', 20 | enter: 'Enter', 21 | clear: 'Clear', 22 | tip: 'Enter {0} to quickly search source, for example', 23 | watch: 'watch all about', 24 | content: 'content', 25 | }, 26 | platform: { 27 | 360: '360', 28 | iqiyi: 'Iqiyi', 29 | youku: 'YouKu', 30 | tencent: 'Tencent', 31 | sohu: 'Sohu', 32 | mgtv: 'MgTV', 33 | pptv: 'PPTV', 34 | letv: 'LeTV', 35 | }, 36 | message: { 37 | invalidApi: 'Invalid parsing interface', 38 | info: 'The current video is loading, please be patient', 39 | error: 'Load failed, try switching to another interface', 40 | empty: 'Please select the parsing interface or enter the address first', 41 | }, 42 | inputUrl: 'Enter a link to make the world full of love', 43 | infiniteLoading: { 44 | complete: 'Load Complete', 45 | error: 'Load Error', 46 | }, 47 | }; 48 | -------------------------------------------------------------------------------- /src/renderer/src/locales/lang/en_US/pages/chase.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | name: 'Moment', 3 | history: { 4 | clearAll: 'Clear', 5 | title: 'History', 6 | }, 7 | type: { 8 | film: 'Film', 9 | iptv: 'Iptv', 10 | drive: 'Drive', 11 | analyze: 'Parse', 12 | }, 13 | binge: { 14 | title: 'Favorite', 15 | checkUpdate: 'Update', 16 | isUpdate: 'Updated', 17 | clearAll: 'Clear', 18 | message: { 19 | noCheckData: 'No detected data, skip this operation', 20 | }, 21 | }, 22 | dialog: { 23 | clearAll: { 24 | header: 'Delete the record', 25 | body: 'Are you sure you want to delete all records? Recovery is not supported after deletion.', 26 | }, 27 | cancel: 'Cancel', 28 | confirm: 'Confirm', 29 | }, 30 | date: { 31 | today: 'Today', 32 | week: 'Weekly', 33 | ago: 'Earlier', 34 | }, 35 | progress: { 36 | watched: 'Finished', 37 | watching: 'Seen', 38 | }, 39 | infiniteLoading: { 40 | complete: 'Load Complete', 41 | noMore: 'Nothing More', 42 | error: 'Load Error', 43 | }, 44 | sourceDeleted: 'Source not exist', 45 | reqError: 'The request resource station failed, please check the network', 46 | }; 47 | -------------------------------------------------------------------------------- /src/renderer/src/locales/lang/en_US/pages/contextMenu.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | add: 'Add', 3 | copy: 'Copy', 4 | default: 'Default', 5 | delete: 'Delete', 6 | edit: 'Edit', 7 | debug: 'Debug' 8 | } 9 | -------------------------------------------------------------------------------- /src/renderer/src/locales/lang/en_US/pages/drive.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | name: 'Drive', 3 | message: { 4 | reqError: 'Request network error, Please try to retry', 5 | skipOp: 'The last request was not completed. Skip this operation', 6 | }, 7 | infiniteLoading: { 8 | noMore: 'Nothing more', 9 | noData: 'No data, please go to [Settings->CloudConfig] to configure the data', 10 | }, 11 | }; 12 | -------------------------------------------------------------------------------- /src/renderer/src/locales/lang/en_US/pages/film.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | name: 'Film', 3 | noDesc: 'No Synopsis', 4 | info: { 5 | unknown: 'Unknown', 6 | type: 'Type', 7 | area: 'Area', 8 | release: 'Release', 9 | }, 10 | infiniteLoading: { 11 | complete: 'Load Complete', 12 | error: 'Load Error', 13 | noMore: 'Nothing More', 14 | noData: 'No data, please go to [Settings->MovieConfig] to configure the data', 15 | networkError: 'Network request failed, Please try to refresh manually', 16 | categoryError: 'Set category exception, Please go to Setting to check source category then try to refresh manually', 17 | }, 18 | message: { 19 | formatSeasonError: 'Data source formatting episode error, check if the data source is normal', 20 | notSelectAnalyze: 'Recognized official data, but no analyze selected', 21 | notSelectSourceBeforeSearch: 'No data source selected or Site Mode data source search status is off', 22 | }, 23 | }; 24 | -------------------------------------------------------------------------------- /src/renderer/src/locales/lang/en_US/pages/index.ts: -------------------------------------------------------------------------------- 1 | import player from './player'; 2 | import iptv from './iptv'; 3 | import chase from './chase'; 4 | import analyze from './analyze'; 5 | import film from './film'; 6 | import drive from './drive'; 7 | import skin from './skin'; 8 | import setting from './setting'; 9 | import sponsor from './sponsor'; 10 | import search from './search'; 11 | import md from './md'; 12 | import justlook from './justlook'; 13 | import share from './share'; 14 | import contextMenu from './contextMenu'; 15 | import playShow from './playShow'; 16 | import lab from './lab'; 17 | 18 | export default { 19 | player, 20 | iptv, 21 | analyze, 22 | chase, 23 | film, 24 | drive, 25 | skin, 26 | setting, 27 | sponsor, 28 | search, 29 | md, 30 | justlook, 31 | share, 32 | contextMenu, 33 | playShow, 34 | lab, 35 | }; 36 | -------------------------------------------------------------------------------- /src/renderer/src/locales/lang/en_US/pages/iptv.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | name: 'IPTV', 3 | delay: 'Delay', 4 | unknown: 'Unknown', 5 | infiniteLoading: { 6 | complete: 'Load Complete', 7 | error: 'Load Error', 8 | noMore: 'Nothing More', 9 | noData: 'No data, please go to [Settings->IptvConfig] to configure the data', 10 | }, 11 | contextMenu: { 12 | copyChannel: 'Copy Channel', 13 | delChannel: 'Delete Channel', 14 | }, 15 | message: { 16 | setSuccess: 'Setup success', 17 | setFail: 'Setup failure, error message', 18 | copySuccess: 'Copy success, quickly share to friends', 19 | copyFail: "Current environment does't support copy. Please copy link manually", 20 | }, 21 | }; 22 | -------------------------------------------------------------------------------- /src/renderer/src/locales/lang/en_US/pages/justlook.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | header: 'Follow your heart', 3 | confirm: 'Change it', 4 | message: { 5 | noData: 'No data, please go setting-BaseSetting-ViewCasual set interface' 6 | } 7 | } -------------------------------------------------------------------------------- /src/renderer/src/locales/lang/en_US/pages/md.ts: -------------------------------------------------------------------------------- 1 | import privacyPolicyMD from './md/privacy-policy.md?raw'; 2 | import thumbnailFfmpegMD from './md/thumbnail-ffmpeg.md?raw'; 3 | import customPlayerMD from './md/custom-player.md?raw'; 4 | 5 | export default { 6 | thumbanilFfmpeg: { 7 | title: 'Thumbnail usage note', 8 | content: thumbnailFfmpegMD, 9 | confirm: 'Check', 10 | cancel: 'Ok', 11 | }, 12 | customPlayer: { 13 | title: 'Custom player usage note', 14 | content: customPlayerMD, 15 | confirm: 'Ok', 16 | }, 17 | privacyPolicy: { 18 | title: 'User Instructions', 19 | content: privacyPolicyMD, 20 | quitTip: 'Auto quit app after 5 seconds', 21 | confirm: 'Aagree', 22 | cancel: 'Disagree', 23 | }, 24 | label: { 25 | copy: 'Copy', 26 | lang: 'Lang', 27 | copySuccess: 'Copy success', 28 | copyError: 'Copy failed, please check your browser settings', 29 | }, 30 | }; 31 | -------------------------------------------------------------------------------- /src/renderer/src/locales/lang/en_US/pages/md/custom-player.md: -------------------------------------------------------------------------------- 1 | > This function is only for tasting, not yet stable: 2 | 3 | 1. This function depends on system commands, and you can call environment variables or specify a path。 4 | 2. How to set up: 5 | ``` 6 | Windows: Software Path (you can generally view the path by [ soft icon-> right-click properties-> Target ]) 7 | Mac: open-a/Applications/software name. App (you can generally view the path through [ access-> Application-> Copy Software ]) 8 | Linux: Software path (generally the path can be viewed through [whereis software name]) 9 | ``` 10 | 3. Common player [ each computer path needs to be changed according to the actual path ] 11 | ``` 12 | iina(Mac) open -a /Applications/IINA.app 13 | PotPlayer(Window) "C:\Program Files (x86)\PotPlayer\PotPlayerMini64.exe" 14 | VLC(Linux) /usr/bin/vlc 15 | iina(Mac) open -a /Applications/IINA.app 16 | VLC(Mac) open -a /Applications/VLC.app 17 | VLC(Window) "C:\Program Files (x86)\VideoLAN\VLC\vlc.exe" 18 | ``` 19 | -------------------------------------------------------------------------------- /src/renderer/src/locales/lang/en_US/pages/md/thumbnail-ffmpeg.md: -------------------------------------------------------------------------------- 1 | > This function is only for tasting, not yet stable: 2 | 3 | 1. This function depends on ffmpeg, please make sure to install the software and configure the environment variables. 4 | 2. Enter ffmpeg -version to see if the installation is successful. 5 | 3. After installing ffmpeg module, you need to restart the software to detect it. 6 | 4. this function will occupy some disk for storage, and some cpu/gpu when generating. 7 | 5. The following are the installation methods for different operating systems. 8 | ``` 9 | Windows: 10 | 1. Open the official website of FFmpeg, select Windows platform, select Windows builds from gyan.dev to download. 11 | 2. In the first green box of release builds, choose a version full package to download. 12 | 3. After the download is complete, unzip the zip file, there will be three exe files in the bin file, copy the address at this time. 13 | 4. Right-click on the computer, click Properties, click Advanced System Settings in Properties. 14 | 5. In System Variables, select Path, and then Edit 15 | 6. In the [Edit Environment Variables] table, create a new one, paste the copied path of the bin directory into it, and save it. 16 | ``` 17 | 18 | ``` 19 | Mac: 20 | 1.Installation Homebrew 21 | /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)" 22 | 2.Installation ffmpeg 23 | brew install ffmpeg 24 | ``` 25 | 26 | ``` 27 | Linux: 28 | Ubuntu:sudo apt install FFmpeg 29 | Arch Linux:pacman -S ffmpeg 30 | Fedora: 31 | sudo dnf install https://download1.rpmfusion.org/free/fedora/rpmfusion-free-release-$(rpm -E %fedora).noarch.rpm 32 | sudo dnf install https://download1.rpmfusion.org/nonfree/fedora/rpmfusion-nonfree- 33 | sudo dnf install ffmpeg 34 | ``` 35 | 36 | > Other operating systems suggest Baidu to find installation methods.! -------------------------------------------------------------------------------- /src/renderer/src/locales/lang/en_US/pages/playShow.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | noPlayTitle: 'Failed get play name', 3 | }; 4 | -------------------------------------------------------------------------------- /src/renderer/src/locales/lang/en_US/pages/search.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | film: 'film', 3 | iptv: 'iptv', 4 | site: 'Local', 5 | group: 'Group', 6 | all: 'All', 7 | searchHistory: 'Search History', 8 | searchSource: 'Search Source', 9 | searchPlaceholder: 'Search resources', 10 | hotNoData: 'No data for the past three days', 11 | }; 12 | -------------------------------------------------------------------------------- /src/renderer/src/locales/lang/en_US/pages/share.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | headerName: 'Scan, phone continue', 3 | headerInfoRecommend: 'Use', 4 | headerInfoBrowser: 'Quark', 5 | headerInfoScan: '-Camera-Scan', 6 | headerCopyright: "support, don't spread it", 7 | copyUrl: 'Copy', 8 | message: { 9 | copySuccess: 'Copy success, quickly share to friends!', 10 | copyFail: "Env does't support copy, please manually copy the link!" 11 | } 12 | }; -------------------------------------------------------------------------------- /src/renderer/src/locales/lang/en_US/pages/skin.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | auto: 'auto', 3 | light: 'light', 4 | dark: 'dark' 5 | } -------------------------------------------------------------------------------- /src/renderer/src/locales/lang/en_US/pages/sponsor.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | title: 'Buy me a coffee', 3 | desc: 'Scan qr code', 4 | }; 5 | -------------------------------------------------------------------------------- /src/renderer/src/locales/lang/zh_CN/index.ts: -------------------------------------------------------------------------------- 1 | import componentsLocale from 'tdesign-vue-next/es/locale/zh_CN'; 2 | 3 | import pages from './pages'; 4 | 5 | export default { 6 | lang: '简体中文', 7 | pages, 8 | componentsLocale, 9 | }; 10 | -------------------------------------------------------------------------------- /src/renderer/src/locales/lang/zh_CN/pages/analyze.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | name: '解析', 3 | noPlay: '暂无播放', 4 | play: '播放', 5 | source: '原始', 6 | support: '支持平台', 7 | history: { 8 | title: '历史', 9 | clear: '清空', 10 | cancel: '取消', 11 | }, 12 | dialog: { 13 | cancel: '取消', 14 | confirm: '确定', 15 | header: '删除记录', 16 | body: '确定删除所有记录吗?删除后不支持找回。', 17 | }, 18 | search: { 19 | input: '输入关键词搜索', 20 | enter: '回车', 21 | clear: '清空', 22 | tip: '输入 {0} 快速指定搜索源, 例如', 23 | watch: '查看', 24 | content: '的所有内容', 25 | }, 26 | platform: { 27 | 360: '360影视', 28 | iqiyi: '爱奇艺', 29 | youku: '优酷视频', 30 | tencent: '腾讯视频', 31 | sohu: '搜狐视频', 32 | mgtv: '芒果tv', 33 | pptv: '聚力网', 34 | letv: '乐视视频', 35 | }, 36 | message: { 37 | invalidApi: '无效的解析接口', 38 | info: '正在加载当前视频, 请耐心等待', 39 | error: '加载失败, 请尝试切换其他接口', 40 | empty: '请选择解析接口或输入需要解析的地址', 41 | }, 42 | inputUrl: '输个链接, 让世界充满爱', 43 | infiniteLoading: { 44 | complete: '人家是有底线的', 45 | error: '哎呀,出了点差错', 46 | }, 47 | }; 48 | -------------------------------------------------------------------------------- /src/renderer/src/locales/lang/zh_CN/pages/chase.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | name: '时刻', 3 | history: { 4 | clearAll: '清空', 5 | title: '历史', 6 | }, 7 | type: { 8 | film: '影视', 9 | iptv: '电视', 10 | drive: '网盘', 11 | analyze: '解析', 12 | }, 13 | binge: { 14 | title: '收藏', 15 | checkUpdate: '更新', 16 | isUpdate: '有更新', 17 | clearAll: '清空', 18 | message: { 19 | noCheckData: '无检测数据, 跳过此操作', 20 | }, 21 | }, 22 | dialog: { 23 | clearAll: { 24 | header: '删除记录', 25 | body: '确定删除所有记录吗?删除后不支持找回。', 26 | }, 27 | cancel: '取消', 28 | confirm: '确定', 29 | }, 30 | date: { 31 | today: '今天', 32 | week: '七天内', 33 | ago: '更早', 34 | }, 35 | progress: { 36 | watched: '已看完', 37 | watching: '已看', 38 | }, 39 | infiniteLoading: { 40 | complete: '人家是有底线的', 41 | noMore: '人家是有底线的', 42 | error: '哎呀,出了点差错', 43 | }, 44 | sourceDeleted: '该源应该被删除了哦', 45 | reqError: '请求资源站失败,请检查网络', 46 | }; 47 | -------------------------------------------------------------------------------- /src/renderer/src/locales/lang/zh_CN/pages/contextMenu.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | add: '添加', 3 | copy: '复制', 4 | default: '默认', 5 | delete: '删除', 6 | edit: '编辑', 7 | debug: '调试' 8 | } 9 | -------------------------------------------------------------------------------- /src/renderer/src/locales/lang/zh_CN/pages/drive.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | name: '网盘', 3 | message: { 4 | reqError: '网络请求错误, 可尝试重试', 5 | skipOp: '上次请求未结束, 跳过此次操作', 6 | }, 7 | infiniteLoading: { 8 | noMore: '人家是有底线的', 9 | noData: '暂无数据, 请前往 [设置->网盘配置] 配置数据', 10 | }, 11 | }; 12 | -------------------------------------------------------------------------------- /src/renderer/src/locales/lang/zh_CN/pages/film.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | name: '影视', 3 | noDesc: '暂无剧情简介', 4 | info: { 5 | unknown: '未知', 6 | type: '类型', 7 | area: '地区', 8 | release: '上映', 9 | }, 10 | infiniteLoading: { 11 | complete: '人家是有底线的', 12 | error: '哎呀, 出了点差错', 13 | noMore: '人家是有底线的', 14 | noData: '暂无数据, 请前往 [设置->影视配置] 配置数据', 15 | networkError: '网络请求失败, 请尝试手动刷新', 16 | categoryError: '设置分类异常, 请前往设置检查源分类后尝试手动刷新', 17 | }, 18 | message: { 19 | formatSeasonError: '数据源格式化剧集错误, 确认数据源是否正常', 20 | notSelectAnalyze: '识别到官解数据, 但未选择解析线路', 21 | notSelectSourceBeforeSearch: '未选择数据源或本站模式数据源搜索状态为关闭', 22 | }, 23 | }; 24 | -------------------------------------------------------------------------------- /src/renderer/src/locales/lang/zh_CN/pages/index.ts: -------------------------------------------------------------------------------- 1 | import player from './player'; 2 | import iptv from './iptv'; 3 | import chase from './chase'; 4 | import analyze from './analyze'; 5 | import film from './film'; 6 | import drive from './drive'; 7 | import skin from './skin'; 8 | import setting from './setting'; 9 | import sponsor from './sponsor'; 10 | import search from './search'; 11 | import md from './md'; 12 | import justlook from './justlook'; 13 | import share from './share'; 14 | import contextMenu from './contextMenu'; 15 | import playShow from './playShow'; 16 | import lab from './lab'; 17 | 18 | export default { 19 | player, 20 | iptv, 21 | analyze, 22 | chase, 23 | film, 24 | drive, 25 | skin, 26 | setting, 27 | sponsor, 28 | search, 29 | md, 30 | justlook, 31 | share, 32 | contextMenu, 33 | playShow, 34 | lab, 35 | }; 36 | -------------------------------------------------------------------------------- /src/renderer/src/locales/lang/zh_CN/pages/iptv.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | name: '电视', 3 | delay: '超时', 4 | unknown: '未知', 5 | infiniteLoading: { 6 | complete: '人家是有底线的', 7 | error: '哎呀,出了点差错', 8 | noMore: '人家是有底线的', 9 | noData: '暂无数据, 请前往 [设置->电视配置] 配置数据', 10 | loading: '加载中...', 11 | }, 12 | contextMenu: { 13 | copyChannel: '复制频道', 14 | delChannel: '删除频道', 15 | }, 16 | message: { 17 | setSuccess: '设置成功', 18 | setFail: '设置失败, 错误信息', 19 | copySuccess: '复制成功,快分享给好友吧', 20 | copyFail: '当前环境不支持一键复制,请手动复制链接', 21 | }, 22 | }; 23 | -------------------------------------------------------------------------------- /src/renderer/src/locales/lang/zh_CN/pages/justlook.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | header: '随心看', 3 | confirm: '换一个', 4 | message: { 5 | noData: '暂无数据, 请前往设置-基础配置-设置随性看接口' 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/renderer/src/locales/lang/zh_CN/pages/md.ts: -------------------------------------------------------------------------------- 1 | import privacyPolicyMD from './md/privacy-policy.md?raw'; 2 | import thumbnailFfmpegMD from './md/thumbnail-ffmpeg.md?raw'; 3 | import customPlayerMD from './md/custom-player.md?raw'; 4 | 5 | export default { 6 | thumbanilFfmpeg: { 7 | title: '缩略图使用说明', 8 | content: thumbnailFfmpegMD, 9 | confirm: '安装检测', 10 | cancel: '知道了', 11 | }, 12 | customPlayer: { 13 | title: '自定义播放器说明', 14 | content: customPlayerMD, 15 | confirm: '知道了', 16 | }, 17 | privacyPolicy: { 18 | title: '用户须知', 19 | content: privacyPolicyMD, 20 | quitTip: '5秒后自动退出软件', 21 | confirm: '同意并继续', 22 | cancel: '不同意', 23 | }, 24 | label: { 25 | copy: '复制', 26 | lang: '语言', 27 | copySuccess: '复制成功', 28 | copyError: '复制失败,请检查您的浏览器设置', 29 | }, 30 | }; 31 | -------------------------------------------------------------------------------- /src/renderer/src/locales/lang/zh_CN/pages/md/custom-player.md: -------------------------------------------------------------------------------- 1 | > 该功能仅供尝鲜, 尚不稳定: 2 | 3 | 1. 该功能依赖于系统命令, 可以调用环境变量 或 指定路径。 4 | 2. 如何设置: 5 | ``` 6 | Windows: 软件路径(一般可通过[软解图标->右键属性->目标]查看路径) 7 | Mac: open -a /Applications/软件名.app(一般可通过[访达->应用程序->复制软件]查看路径) 8 | Linux: 软件路径(一般可通过[whereis 软件名]查看路径) 9 | ``` 10 | 3. 常见播放器[每台电脑路径需根据实际路径更改] 11 | ``` 12 | iina(Mac) open -a /Applications/IINA.app 13 | PotPlayer(Window) "C:\Program Files (x86)\PotPlayer\PotPlayerMini64.exe" 14 | VLC(Linux) /usr/bin/vlc 15 | iina(Mac) open -a /Applications/IINA.app 16 | VLC(Mac) open -a /Applications/VLC.app 17 | VLC(Window) "C:\Program Files (x86)\VideoLAN\VLC\vlc.exe" 18 | ``` 19 | -------------------------------------------------------------------------------- /src/renderer/src/locales/lang/zh_CN/pages/md/privacy-policy.md: -------------------------------------------------------------------------------- 1 | > 感谢您选择使用zyfun(以下简称本软件),在使用产品和服务之前,请您仔细阅读和理解以下声明: 2 | 3 | 1. 若您不同意本声明的任何内容,请您立即停止使用本软件。一旦您开始使用本软件产品和服务,则表示您已同意本声明的所有内容。 4 | 2. 本软件仅供个人学习、研究和技术交流使用,仅提供展示功能,所有数据资源均由用户自身制作提供,包括但不限于视频网站、媒体分享站点等。本软件无法控制这些资源的合法性、准确性、完整性或可用性,因此不对资源内容的真实性、合法性或适用性负责。 5 | 3. 由于数据源为用户自行制作,我们在此特别提醒, 视频或弹幕中可能出现的任何第三方广告、产品推广信息等相关内容,均系第三方(含用户)行为植入,非本软件策划或添加。请您在体验过程中保持警惕,对这类信息的真实性及合法性进行自主甄别,如用户遇诈骗因此产生的损失,本平台不承担任何责任。 6 | 4. 本软件利用网络爬虫技术获取部分数据,旨在为用户提供更全面的信息服务。包括不限于豆瓣(douban.com)、酷云(ky.live)、云合(enlightent.cn)、移动爱家(komect.com)、112114(112114.xyz), 值得注意的是这些网站的API未经过授权。用户在使用这些数据时可能面临法律风险,如因此导致的法律责任,用户应自行承担。 7 | 5. 本软件仅使用Iframe嵌入多家视频平台网站内容,包括但不限于爱奇艺(iqiyi.com)、腾讯视频(v.qq.com)、搜狐视频(tv.sohu.com)、聚力网(pptv.com)、360影视(360kan.com)及芒果TV(mgtv.com)等。对于用户在使用本软件过程中对如上网站进行的任何操作,本软件不承担任何责任。 8 | 6. 本软件具备资源嗅探特性,可能会引发第三方数据的隐私和安全风险。用户在使用该特性时,需自行承担可能产生的信息泄露或滥用风险,并对其后果负全部责任。 9 | 7. 本软件含“去广告”选项以增强体验,我们不鼓励任何侵犯版权或违反服务提供商条款的行为。启用前,请确保您的操作符合法律及服务商规则,并知悉可能的兼容性局限。 10 | 8. 为遵守网络安全法的内容审核要求,本软件不提供弹幕发送服务。关于弹幕展示,受限于本地性能未做数据清理,可能存在不良言论,请勿相信因此引起非必的要麻烦。同时如果用户通过任何渠道发表不良言论行为,该行为与本软件无关。我们呼吁用户文明用语,共同维护网络健康环境。 11 | 9. 我们深知您的隐私无价。因此,本软件绝不收集任何用户数据,除了必要的WebDev备份(此过程由专业第三方严格管理)外,所有信息均严格本地存储,确保您的数据仅在您掌控之中。此软件不与任何第三方共享您的任何信息。 12 | 10. 使用AI特性时数据将与OpenAI服务器共享。AI生成内容仅供参考,不保证其准确性、完整性或适用性,不代表我方观点。用户应对AI生成内容自行验证,并承担使用风险。重要决策需独立验证或咨询专业人士。继续使用即表示同意上述免责条款。 13 | 11. 赞赏行为纯属自愿,旨在表达对开源软件作者或贡献者的支持和感谢,并非购买商品或服务的交易行为。赞赏者应当清楚理解,赞赏款项不享有任何商品或服务的保证,也不构成任何形式的合同关系。 14 | 12. 您在使用本软件时需自行负责所有操作和使用结果。本软件不对您通过使用本软件获取的任何内容负责,包括但不限于媒体资源的准确性、版权合规性、完整性、安全性和可用性。对于任何因使用本软件导致的损失、损害或法律纠纷,不承担任何责任。 15 | 13. 您在使用本软件时必须遵守您所在国家/地区的相关法律法规,禁止使用本软件进行任何违反法律法规的活动,包括但不限于制作、上传、传播、存储任何违法、侵权、淫秽、诽谤、恶意软件等内容。如您违反相关法律法规,需自行承担法律责任。 16 | 14. 本免责声明适用于本软件的所有用户。本软件保留随时修改、更新本声明的权利,并以Github Readme、软件更新等形式通知用户。请您定期查阅并遵守最新的免责声明。 17 | 18 | > 请您在使用本软件之前认真阅读并理解本免责声明的所有内容,感谢您的理解和支持 19 | -------------------------------------------------------------------------------- /src/renderer/src/locales/lang/zh_CN/pages/md/thumbnail-ffmpeg.md: -------------------------------------------------------------------------------- 1 | > 该功能仅供尝鲜, 尚不稳定: 2 | 3 | 1. 该功能依赖于ffmpeg, 请确保安装该软件并配置了环境变量. 4 | 2. 命令行输入ffmpeg -version查看安装是否成功。 5 | 3. 安装完ffmpeg模块后需重启软件后才可以检测到。 6 | 4. 该功能会占用部分磁盘用于存储, 生成时会占用部分cpu/gpu。 7 | 5. 如下为不同操作系统安装方法: 8 | ``` 9 | Windows: 10 | 1.打开FFmpeg官网,选择windows平台 选择Windows builds from gyan.dev下载 11 | 2.在release builds第一个绿框里面选择一个版本full包下载 12 | 3.下载完成后解压该压缩包,在bin文件里会有三个exe文件,复制此时的地址 13 | 4.右键单击此电脑, 点击属性, 在属性里面点击高级系统设置 14 | 5.在系统变量中, 选择 Path, 然后编辑 15 | 6.[编辑环境变量]表中, 新建, 将复制的bin目录路径粘贴进去, 保存 16 | ``` 17 | 18 | ``` 19 | Mac: 20 | 1.安装Homebrew 21 | /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)" 22 | 2.安装ffmpeg 23 | brew install ffmpeg 24 | ``` 25 | 26 | ``` 27 | Linux: 28 | Ubuntu:sudo apt install FFmpeg 29 | Arch Linux:pacman -S ffmpeg 30 | Fedora: 31 | sudo dnf install https://download1.rpmfusion.org/free/fedora/rpmfusion-free-release-$(rpm -E %fedora).noarch.rpm 32 | sudo dnf install https://download1.rpmfusion.org/nonfree/fedora/rpmfusion-nonfree- 33 | sudo dnf install ffmpeg 34 | ``` 35 | 36 | > 其他操作系统建议百度查找安装方法! -------------------------------------------------------------------------------- /src/renderer/src/locales/lang/zh_CN/pages/playShow.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | noPlayTitle: '获取剧名失败', 3 | }; 4 | -------------------------------------------------------------------------------- /src/renderer/src/locales/lang/zh_CN/pages/player.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | noApi: '无接口', 3 | noData: '无数据', 4 | header: { 5 | backMain: '回到主界面', 6 | }, 7 | function: { 8 | like: '收藏', 9 | download: '下载', 10 | share: '分享', 11 | setting: '设置', 12 | }, 13 | film: { 14 | desc: '简介', 15 | background: '背景', 16 | analyze: '解析', 17 | anthology: '选集', 18 | line: '线路', 19 | recommend: '猜你喜欢', 20 | actors: '演职员', 21 | director: '导演', 22 | actor: '主演', 23 | }, 24 | iptv: { 25 | epg: '节目', 26 | channel: '频道', 27 | group: '分组', 28 | }, 29 | drive: { 30 | anthology: '选集', 31 | }, 32 | status: { 33 | unplay: '未播放', 34 | played: '已播放', 35 | playing: '播放中', 36 | useing: '使用中', 37 | unuse: '未使用', 38 | }, 39 | infiniteLoading: { 40 | complete: '人家是有底线的', 41 | error: '哎呀,出了点差错', 42 | }, 43 | message: { 44 | success: '成功', 45 | error: '失败', 46 | play: '请稍后,数据处理中,如长时间未播放请换源', 47 | official: '解析流程处理完成,数据来源于:{0}', 48 | sniiferError: '嗅探失败,请换源', 49 | noDefaultAnalyze: '未配置默认解析,自行选择解析线路', 50 | noRecommendSearch: '该源未找到相关推荐,不执行本次切换', 51 | noPlayUrl: '播放地址获取失败,请换源或线路', 52 | next: '请稍候,正在切换下集', 53 | }, 54 | placeholder: { 55 | analyze: '解析接口', 56 | }, 57 | setting: { 58 | title: '设置', 59 | skipHeadAndEnd: '跳进度', 60 | playNextEnabled: '续下集', 61 | playNextPreload: '预加载', 62 | skipStart: '开始', 63 | skipEnd: '结束', 64 | skipSeconds: '秒', 65 | skipAd: '过广告', 66 | tip: '开关全局生效', 67 | }, 68 | download: { 69 | title: '离线缓存', 70 | copy: '复制链接', 71 | copyCurrentUrl: '复制当前地址', 72 | recommendDownloaderName: 'M3U8-Downloader', 73 | recommendDownloaderTip: '推荐使用开源下载器', 74 | statusAwaitDownload: '待下载', 75 | statusRequireDownload: '需下载', 76 | soureceSelect: '请选下载源', 77 | copySuccess: '复制成功,快到下载器里下载吧', 78 | copyFail: '复制失败,当前环境不支持一键复制', 79 | copyEmpty: '请先选择需要下载的内容', 80 | copyError: '当前环境不支持一键复制, 请手动复制链接', 81 | copyCheck: '当前复制的内容可能需要嗅探', 82 | }, 83 | }; 84 | -------------------------------------------------------------------------------- /src/renderer/src/locales/lang/zh_CN/pages/search.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | film: '影视', 3 | iptv: '电视', 4 | site: '本站', 5 | group: '组内', 6 | all: '全站', 7 | searchHistory: '搜索历史', 8 | searchSource: '搜索源', 9 | searchPlaceholder: '搜索全网资源', 10 | hotNoData: '暂无近三天数据, 请查看其他分类', 11 | }; 12 | -------------------------------------------------------------------------------- /src/renderer/src/locales/lang/zh_CN/pages/share.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | headerName: '扫一扫, 手机继续看', 3 | headerInfoRecommend: '推荐', 4 | headerInfoBrowser: '夸克APP', 5 | headerInfoScan: '-相机-扫码', 6 | headerCopyright: '提供支持, 严禁传播资源', 7 | copyUrl: '复制地址', 8 | message: { 9 | copySuccess: '复制成功,快分享给好友吧!', 10 | copyFail: '当前环境不支持一键复制,请手动复制链接!' 11 | } 12 | }; -------------------------------------------------------------------------------- /src/renderer/src/locales/lang/zh_CN/pages/skin.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | auto: '自动', 3 | light: '浅色', 4 | dark: '深色' 5 | } -------------------------------------------------------------------------------- /src/renderer/src/locales/lang/zh_CN/pages/sponsor.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | title: '请作者喝杯咖啡吧', 3 | desc: '扫码完成赞助' 4 | } -------------------------------------------------------------------------------- /src/renderer/src/locales/useLocale.ts: -------------------------------------------------------------------------------- 1 | import { useLocalStorage } from '@vueuse/core'; 2 | import { computed } from 'vue'; 3 | import { useI18n } from 'vue-i18n'; 4 | 5 | import { i18n, langCode, localeConfigKey } from '@/locales/index'; 6 | 7 | export function useLocale() { 8 | const { locale } = useI18n({ useScope: 'global' }); 9 | function changeLocale(lang: string) { 10 | // 如果切换的语言不在对应语言文件里则默认为简体中文 11 | if (!langCode.includes(lang)) { 12 | lang = 'zh_CN'; 13 | } 14 | 15 | locale.value = lang; 16 | useLocalStorage(localeConfigKey, 'zh_CN').value = lang; 17 | } 18 | 19 | const getComponentsLocale = computed(() => { 20 | return i18n.global.getLocaleMessage(locale.value).componentsLocale; 21 | }); 22 | 23 | return { 24 | changeLocale, 25 | getComponentsLocale, 26 | locale, 27 | }; 28 | } 29 | -------------------------------------------------------------------------------- /src/renderer/src/main.ts: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue'; 2 | 3 | import App from './App.vue'; 4 | import router from './router'; 5 | import { store } from './store'; 6 | import i18n from './locales'; 7 | 8 | import 'tdesign-vue-next/es/style/index.css'; 9 | import '@/style/index.less'; 10 | 11 | import { Tooltip as TTooltip } from 'tdesign-vue-next'; 12 | 13 | const app = createApp(App); 14 | 15 | app.use(store); 16 | app.use(router); 17 | app.use(i18n); 18 | app.use(TTooltip); 19 | 20 | app.mount('#app').$nextTick(window.removeLoading); 21 | -------------------------------------------------------------------------------- /src/renderer/src/pages/setting/components/analyze/constants.ts: -------------------------------------------------------------------------------- 1 | import { t } from '@/locales'; 2 | import { PrimaryTableCol, TableRowData } from 'tdesign-vue-next'; 3 | 4 | export const COLUMNS: PrimaryTableCol[] = [ 5 | { 6 | type: 'multiple', 7 | fixed: 'left', 8 | colKey: 'row-select', 9 | }, 10 | { 11 | title: t('pages.setting.table.header.name'), 12 | align: 'left', 13 | colKey: 'name', 14 | ellipsis: true, 15 | }, 16 | { 17 | title: t('pages.setting.table.header.type'), 18 | align: 'center', 19 | colKey: 'type' 20 | }, 21 | { 22 | title: t('pages.setting.table.header.status'), 23 | align: 'center', 24 | colKey: 'isActive', 25 | }, 26 | { 27 | title: t('pages.setting.table.header.operate'), 28 | align: 'center', 29 | fixed: 'right', 30 | width: 200, 31 | colKey: 'op', 32 | }, 33 | ]; 34 | -------------------------------------------------------------------------------- /src/renderer/src/pages/setting/components/base/components/DialogCustomPlayer.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /src/renderer/src/pages/setting/components/drive/constants.ts: -------------------------------------------------------------------------------- 1 | import { t } from '@/locales'; 2 | import { PrimaryTableCol, TableRowData } from 'tdesign-vue-next'; 3 | 4 | export const COLUMNS: PrimaryTableCol[] = [ 5 | { 6 | type: 'multiple', 7 | fixed: 'left', 8 | colKey: 'row-select', 9 | }, 10 | { 11 | title: t('pages.setting.table.header.name'), 12 | align: 'left', 13 | colKey: 'name', 14 | ellipsis: true, 15 | }, 16 | { 17 | title: t('pages.setting.table.header.startPath'), 18 | align: 'left', 19 | colKey: 'startPage', 20 | ellipsis: true, 21 | }, 22 | { 23 | title: t('pages.setting.drive.showAll'), 24 | align: 'center', 25 | colKey: 'showAll' 26 | }, 27 | { 28 | title: t('pages.setting.table.header.status'), 29 | align: 'center', 30 | colKey: 'isActive', 31 | }, 32 | { 33 | title: t('pages.setting.table.header.operate'), 34 | align: 'center', 35 | fixed: 'right', 36 | width: 200, 37 | colKey: 'op', 38 | }, 39 | ]; 40 | -------------------------------------------------------------------------------- /src/renderer/src/pages/setting/components/iptv/constants.ts: -------------------------------------------------------------------------------- 1 | import { t } from '@/locales'; 2 | import { PrimaryTableCol, TableRowData } from 'tdesign-vue-next'; 3 | 4 | export const COLUMNS: PrimaryTableCol[] = [ 5 | { 6 | type: 'multiple', 7 | fixed: 'left', 8 | colKey: 'row-select', 9 | }, 10 | { 11 | title: t('pages.setting.table.header.name'), 12 | align: 'left', 13 | colKey: 'name', 14 | ellipsis: true, 15 | }, 16 | { 17 | title: t('pages.setting.table.header.type'), 18 | align: 'center', 19 | colKey: 'type', 20 | }, 21 | { 22 | title: t('pages.setting.table.header.status'), 23 | align: 'center', 24 | colKey: 'isActive', 25 | }, 26 | { 27 | title: t('pages.setting.table.header.operate'), 28 | align: 'center', 29 | fixed: 'right', 30 | width: 200, 31 | colKey: 'op', 32 | }, 33 | ]; 34 | -------------------------------------------------------------------------------- /src/renderer/src/pages/setting/components/site/constants.ts: -------------------------------------------------------------------------------- 1 | import { t } from '@/locales'; 2 | import { PrimaryTableCol, TableRowData } from 'tdesign-vue-next'; 3 | 4 | export const COLUMNS: PrimaryTableCol[] = [ 5 | { 6 | type: 'multiple', 7 | fixed: 'left', 8 | colKey: 'row-select', 9 | }, 10 | { 11 | title: t('pages.setting.table.header.name'), 12 | align: 'left', 13 | colKey: 'name', 14 | ellipsis: true, 15 | }, 16 | { 17 | title: t('pages.setting.table.header.type'), 18 | align: 'center', 19 | colKey: 'type', 20 | ellipsis: true, 21 | }, 22 | { 23 | title: t('pages.setting.table.header.group'), 24 | align: 'center', 25 | colKey: 'group', 26 | ellipsis: true, 27 | }, 28 | { 29 | title: t('pages.setting.table.header.status'), 30 | align: 'center', 31 | colKey: 'isActive', 32 | }, 33 | { 34 | title: t('pages.setting.table.header.operate'), 35 | align: 'center', 36 | fixed: 'right', 37 | width: 230, 38 | colKey: 'op', 39 | }, 40 | ]; 41 | -------------------------------------------------------------------------------- /src/renderer/src/pages/test/assets/demo.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hiram-Wong/ZyPlayer/d61392132094d06a103c9f9c979e8fe88b0810df/src/renderer/src/pages/test/assets/demo.mp4 -------------------------------------------------------------------------------- /src/renderer/src/pages/test/demo/aliplayer.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | //(必须)H5模式播放器,需引用此css文件。 4 | //(必须)引入H5模式的js文件。 5 | 6 | 7 |
8 | 9 | 10 | 11 | 20 | -------------------------------------------------------------------------------- /src/renderer/src/pages/test/demo/griffith.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 36 | -------------------------------------------------------------------------------- /src/renderer/src/pages/test/demo/veplayer.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 火山引擎VePlayer 5 | 9 | 10 | 11 | 12 | 13 |
14 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /src/renderer/src/pages/test/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/renderer/src/router/index.ts: -------------------------------------------------------------------------------- 1 | import { createRouter, createWebHashHistory, RouteRecordRaw, useRoute } from 'vue-router'; 2 | 3 | // 导入homepage相关固定路由 4 | const homepageModules = import.meta.glob('./modules/**/homepage.ts', { eager: true }); 5 | 6 | // 其他固定路由 7 | const defaultRouterList: Array = [ 8 | { 9 | path: '/', 10 | redirect: '/film/index', 11 | }, 12 | ]; 13 | 14 | // 存放固定路由 15 | export const homepageRouterList: Array = mapModuleRouterList(homepageModules); 16 | 17 | export const allRoutes = [...homepageRouterList, ...defaultRouterList]; 18 | 19 | // 固定路由模块转换为路由 20 | // 关于单层路由,meta 中设置 { single: true } 即可为单层路由,{ hidden: true } 即可在侧边栏隐藏该路由 21 | export function mapModuleRouterList(modules: Record): Array { 22 | const routerList: Array = []; 23 | Object.keys(modules).forEach((key) => { 24 | // @ts-ignore 25 | const mod = modules[key].default || {}; 26 | const modList = Array.isArray(mod) ? [...mod] : [mod]; 27 | routerList.push(...modList); 28 | }); 29 | return routerList; 30 | } 31 | 32 | export const getActive = (maxLevel = 3): string => { 33 | const route = useRoute(); 34 | if (!route.path) { 35 | return ''; 36 | } 37 | return route.path 38 | .split('/') 39 | .filter((_item: string, index: number) => index <= maxLevel && index > 0) 40 | .map((item: string) => `/${item}`) 41 | .join(''); 42 | }; 43 | 44 | const router = createRouter({ 45 | history: createWebHashHistory(), 46 | routes: allRoutes, 47 | scrollBehavior() { 48 | return { 49 | el: '#app', 50 | top: 0, 51 | behavior: 'smooth', 52 | }; 53 | }, 54 | }); 55 | 56 | export default router; 57 | -------------------------------------------------------------------------------- /src/renderer/src/store/index.ts: -------------------------------------------------------------------------------- 1 | import { createPinia } from 'pinia'; 2 | import { createPersistedState } from 'pinia-plugin-persistedstate'; 3 | 4 | import { createSyncPlugin } from './plugins/pinia-plugin-sync'; 5 | 6 | const store = createPinia(); 7 | store.use(createPersistedState()); 8 | store.use(createSyncPlugin()); 9 | 10 | export { store }; 11 | 12 | export * from './modules/play'; 13 | export * from './modules/setting'; 14 | 15 | export default store; 16 | -------------------------------------------------------------------------------- /src/renderer/src/store/modules/play.ts: -------------------------------------------------------------------------------- 1 | import { defineStore } from 'pinia'; 2 | 3 | import PLAY_CONFIG from '@/config/play'; 4 | import { store } from '@/store'; 5 | 6 | const state = { 7 | ...PLAY_CONFIG, 8 | }; 9 | 10 | export type PlayState = typeof state; 11 | 12 | export const usePlayStore = defineStore('play', { 13 | state: () => state, 14 | getters: { 15 | getType: (state) => { 16 | return state.type; 17 | }, 18 | getStatus: (state) => { 19 | return state.status; 20 | }, 21 | getSetting: (state) => { 22 | return state.setting; 23 | }, 24 | getData: (state) => { 25 | return state.data; 26 | }, 27 | }, 28 | actions: { 29 | updateConfig(payload: Partial) { 30 | for (const key in payload) { 31 | if (key === 'type') { 32 | this.type = payload.type!; 33 | } 34 | if (key === 'status') { 35 | this.status = payload.status!; 36 | } 37 | if (key === 'data') { 38 | this.data = payload.data!; 39 | } 40 | if (key === 'setting') { 41 | this.setting = { 42 | ...this.setting, // 保留原有的 setting 属性 43 | ...payload.setting, // 更新传入的 setting 属性 44 | }; 45 | } 46 | } 47 | }, 48 | }, 49 | persist: true, // 数据持久化 50 | }); 51 | 52 | export function getPlayStore() { 53 | return usePlayStore(store); 54 | } 55 | -------------------------------------------------------------------------------- /src/renderer/src/store/modules/setting.ts: -------------------------------------------------------------------------------- 1 | import { defineStore } from 'pinia'; 2 | 3 | import SYSTEM_CONFIG from '@/config/system'; 4 | import { store } from '@/store'; 5 | 6 | import { ModeType } from '@/types/interface'; 7 | 8 | const state: Record = { 9 | ...SYSTEM_CONFIG, 10 | }; 11 | 12 | export type TState = typeof state; 13 | export type TStateKey = keyof typeof state; 14 | 15 | export const useSettingStore = defineStore('setting', { 16 | state: () => state, 17 | getters: { 18 | getStateMode: (state): ModeType | 'auto' => { 19 | return state.mode as ModeType | 'auto'; 20 | }, 21 | displayMode: (state): ModeType => { 22 | if (state.mode === 'auto') { 23 | const media = window.matchMedia('(prefers-color-scheme:dark)'); 24 | if (media.matches) { 25 | return 'dark'; 26 | } 27 | return 'light'; 28 | } 29 | return state.mode as ModeType; 30 | }, 31 | displayTheme: (state): ModeType => { 32 | return state.theme as ModeType; 33 | }, 34 | }, 35 | actions: { 36 | changeMode(mode: ModeType | 'auto') { 37 | let theme = mode; 38 | 39 | if (mode === 'auto') { 40 | theme = this.getMediaColor(); 41 | } 42 | const isDarkMode = theme === 'dark'; 43 | 44 | document.documentElement.setAttribute('theme-mode', isDarkMode ? 'dark' : 'light'); 45 | }, 46 | getMediaColor() { 47 | const media = window.matchMedia('(prefers-color-scheme:dark)'); 48 | 49 | if (media.matches) { 50 | return 'dark'; 51 | } 52 | return 'light'; 53 | }, 54 | updateConfig(payload: Partial) { 55 | for (const key in payload) { 56 | if (payload[key as TStateKey] !== undefined) { 57 | this[key as TStateKey] = payload[key as TStateKey]; 58 | } 59 | if (key === 'mode') { 60 | this.changeMode(payload[key]); 61 | this.theme = payload[key] === 'auto' ? this.getMediaColor() : payload[key]; 62 | } 63 | } 64 | }, 65 | }, 66 | persist: true, // 数据持久化 67 | }); 68 | 69 | export function getSettingStore() { 70 | return useSettingStore(store); 71 | } 72 | -------------------------------------------------------------------------------- /src/renderer/src/store/plugins/pinia-plugin-sync.ts: -------------------------------------------------------------------------------- 1 | import { useBroadcastChannel } from '@vueuse/core'; 2 | import { watch } from 'vue'; 3 | 4 | // 根据 key 更新 state 的值,特别处理 Map 类型的字段 5 | const changeState = (state, key, value) => { 6 | if (state[key] instanceof Map) { 7 | state[key] = Array.isArray(value) ? new Map(value) : new Map(Object.entries(value)); 8 | } else { 9 | state[key] = value; 10 | } 11 | }; 12 | 13 | // 将包含 Map 的对象转换为普通对象形式 14 | const cloneToObject = (obj) => { 15 | if (obj instanceof Map) { 16 | return Object.fromEntries(obj); 17 | } else if (Array.isArray(obj)) { 18 | return obj.map(cloneToObject); 19 | } else if (obj instanceof Object) { 20 | return Object.fromEntries(Object.entries(obj).map(([key, value]) => [key, cloneToObject(value)])); 21 | } 22 | return obj; 23 | }; 24 | 25 | // 自定义序列化方法,特别处理 Map 类型的字段 26 | const stringify = (obj) => JSON.stringify(cloneToObject(obj)); 27 | 28 | export function createSyncPlugin() { 29 | return ({ store }) => { 30 | const { data, post } = useBroadcastChannel({ name: `pinia-sync-${store.$id}` }); 31 | 32 | // 监听来自其他窗口的消息并更新 store 33 | watch(data, (newValue) => { 34 | if (newValue) { 35 | const parsedData = JSON.parse(newValue as string); 36 | Object.entries(parsedData).forEach(([key, value]) => { 37 | changeState(store.$state, key, value); 38 | }); 39 | // store.$patch(parsedData); 40 | } 41 | }); 42 | 43 | // 监听本窗口内的 store 变化并广播消息 44 | store.$subscribe((_mutation, state) => { 45 | const deserializeData = stringify(state); 46 | post(deserializeData); 47 | }); 48 | }; 49 | } 50 | -------------------------------------------------------------------------------- /src/renderer/src/style/dialog.less: -------------------------------------------------------------------------------- 1 | 2 | .dialog-search-view { 3 | .t-dialog--default { 4 | padding: 0; 5 | } 6 | } 7 | 8 | /* 公共 dialog */ 9 | .common-dialog { 10 | .t-dialog { 11 | border: none; 12 | overflow: hidden; 13 | 14 | .t-dialog__body { 15 | padding: 0; 16 | } 17 | 18 | .t-dialog__footer { 19 | padding: var(--td-comp-paddingTB-m) 0 0; 20 | 21 | div { 22 | display: flex; 23 | justify-content: space-around; 24 | padding: 0; 25 | 26 | .t-button { 27 | font-weight: 700; 28 | font-size: 15px; 29 | line-height: 45px; 30 | height: 45px; 31 | border-radius: var(--td-radius-round); 32 | width: 50%; 33 | } 34 | 35 | &:only-child { 36 | .t-button { 37 | width: 100%; 38 | } 39 | } 40 | } 41 | } 42 | } 43 | } -------------------------------------------------------------------------------- /src/renderer/src/style/font-family.less: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'MiSans'; 3 | src: url('@/assets/font/MiSans-Medium.woff2') format('woff2'); 4 | font-weight: normal; 5 | font-style: normal; 6 | } 7 | 8 | @font-face { 9 | font-family: 'JetBrainsMono'; 10 | src: url('@/assets/font/JetBrainsMono-Regular.woff2') format('woff2'); 11 | font-weight: normal; 12 | font-style: normal; 13 | } 14 | -------------------------------------------------------------------------------- /src/renderer/src/style/index.less: -------------------------------------------------------------------------------- 1 | @import './font-family.less'; 2 | @import './reset.less'; 3 | @import './normalize.less'; 4 | @import './theme/index.less'; 5 | 6 | @import './dialog.less'; 7 | -------------------------------------------------------------------------------- /src/renderer/src/style/player/index.less: -------------------------------------------------------------------------------- 1 | @import 'xgplayer/es/plugins/danmu/index.css'; 2 | 3 | @import './artplayer.css'; 4 | @import './dplayer.css'; 5 | @import './nplayer.css'; 6 | @import './veplayer.css'; 7 | -------------------------------------------------------------------------------- /src/renderer/src/style/reset.less: -------------------------------------------------------------------------------- 1 | // 对部分样式进行重置 2 | html { 3 | line-height: 1.15; 4 | -webkit-text-size-adjust: 100%; 5 | } 6 | 7 | body { 8 | color: var(--td-text-color-secondary); 9 | font: var(--td-font-body-medium); 10 | font-family: -apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,var(--td-font-family); 11 | -webkit-font-smoothing: antialiased; 12 | } 13 | 14 | pre { 15 | font-family: var(--td-font-family); 16 | } 17 | 18 | ul, 19 | dl, 20 | li, 21 | dd, 22 | dt { 23 | // margin: 0; 24 | // padding: 0; 25 | // list-style: none; 26 | } 27 | 28 | figure, 29 | h1, 30 | h2, 31 | h3, 32 | h4, 33 | h5, 34 | h6, 35 | p { 36 | margin: 0; 37 | } 38 | 39 | * { 40 | box-sizing: border-box; 41 | margin: 0; 42 | padding: 0; 43 | } 44 | -------------------------------------------------------------------------------- /src/renderer/src/style/theme/index.less: -------------------------------------------------------------------------------- 1 | @import './theme.less'; -------------------------------------------------------------------------------- /src/renderer/src/style/theme/theme.less: -------------------------------------------------------------------------------- 1 | :root,:root[theme-mode="light"] { 2 | --td-font-family: MiSans; 3 | --td-font-family-medium: MiSans; 4 | --td-brand-color-light: var(--td-brand-color-1); 5 | --td-brand-color-focus: var(--td-brand-color-2); 6 | --td-brand-color-disabled: var(--td-brand-color-3); 7 | --td-brand-color-hover: var(--td-brand-color-3); 8 | --td-brand-color: var(--td-brand-color-4); 9 | --td-brand-color-active: var(--td-brand-color-5); 10 | --td-brand-color-1: #e4f9e9; 11 | --td-brand-color-2: #c8f2d7; 12 | --td-brand-color-3: #94dab2; 13 | --td-brand-color-4: #45c58b; 14 | --td-brand-color-5: #33a371; 15 | --td-brand-color-6: #008857; 16 | --td-brand-color-7: #006c44; 17 | --td-brand-color-8: #005333; 18 | --td-brand-color-9: #003b23; 19 | --td-brand-color-10: #002515; 20 | --td-radius-default: var(--td-radius-medium); 21 | } 22 | 23 | :root[theme-mode='dark'] { 24 | --td-brand-color-light: var(--td-brand-color-1); 25 | --td-brand-color-focus: var(--td-brand-color-2); 26 | --td-brand-color-disabled: var(--td-brand-color-3); 27 | --td-brand-color-hover: var(--td-brand-color-5); 28 | --td-brand-color: var(--td-brand-color-6); 29 | --td-brand-color-active: var(--td-brand-color-7); 30 | --td-brand-color-1: #33a37120; 31 | --td-brand-color-2: #003b23; 32 | --td-brand-color-3: #005333; 33 | --td-brand-color-4: #006c44; 34 | --td-brand-color-5: #008857; 35 | --td-brand-color-6: #33a371; 36 | --td-brand-color-7: #45c58b; 37 | --td-brand-color-8: #94dab2; 38 | --td-brand-color-9: #c8f2d7; 39 | --td-brand-color-10: #e4f9e9; 40 | } 41 | -------------------------------------------------------------------------------- /src/renderer/src/style/variables.less: -------------------------------------------------------------------------------- 1 | /** 公共前缀 */ 2 | @starter-prefix: zy; 3 | 4 | // 颜色、尺寸、阴影、圆角、字体 variables 请参考 https://tdesign.tencent.com/starter/docs/vue/design-token 5 | // 响应式断点 6 | @screen-sm: 768px; 7 | @screen-md: 992px; 8 | @screen-lg: 1200px; 9 | @screen-xl: 1400px; 10 | 11 | @screen-sm-min: @screen-sm; 12 | @screen-md-min: @screen-md; 13 | @screen-lg-min: @screen-lg; 14 | @screen-xl-min: @screen-xl; 15 | 16 | @screen-sm-max: calc(@screen-md-min - 1px); 17 | @screen-md-max: calc(@screen-lg-min - 1px); 18 | @screen-lg-max: calc(@screen-xl-min - 1px); 19 | 20 | // 动画 21 | @anim-time-fn-easing: cubic-bezier(0.38, 0, 0.24, 1); 22 | @anim-time-fn-ease-out: cubic-bezier(0, 0, 0.15, 1); 23 | @anim-time-fn-ease-in: cubic-bezier(0.82, 0, 1, 0.9); 24 | @anim-duration-base: 0.2s; 25 | @anim-duration-moderate: 0.24s; 26 | @anim-duration-slow: 0.28s; -------------------------------------------------------------------------------- /src/renderer/src/types/axios.d.ts: -------------------------------------------------------------------------------- 1 | import type { AxiosRequestConfig } from 'axios'; 2 | 3 | /** 4 | * Axios请求配置 5 | */ 6 | export interface RequestOptions { 7 | /** 8 | * 接口地址 9 | * 10 | * 例: http://www.baidu.com/api 11 | */ 12 | apiUrl?: string; 13 | /** 14 | * 是否自动添加接口前缀 15 | * 16 | * 例: http://www.baidu.com/api 17 | * urlPrefix: 'api' 18 | */ 19 | isJoinPrefix?: boolean; 20 | /** 21 | * 接口前缀 22 | */ 23 | urlPrefix?: string; 24 | /** 25 | * POST请求的时候添加参数到Url中 26 | */ 27 | joinParamsToUrl?: boolean; 28 | /** 29 | * 格式化提交参数时间 30 | */ 31 | formatDate?: boolean; 32 | /** 33 | * 是否需要对响应数据进行处理 34 | */ 35 | isTransformResponse?: boolean; 36 | /** 37 | * 是否返回原生响应头 38 | * 39 | * 例: 需要获取响应头时使用该属性 40 | */ 41 | isReturnNativeResponse?: boolean; 42 | /** 43 | * 是否忽略请求取消令牌 44 | * 45 | * 如果启用,则重复请求时不进行处理 46 | * 47 | * 如果禁用,则重复请求时会取消当前请求 48 | */ 49 | ignoreCancelToken?: boolean; 50 | /** 51 | * 自动对请求添加时间戳参数 52 | */ 53 | joinTime?: boolean; 54 | /** 55 | * 是否携带Token 56 | */ 57 | withToken?: boolean; 58 | /** 59 | * 重试配置 60 | */ 61 | retry?: { 62 | /** 63 | * 重试次数 64 | */ 65 | count: number; 66 | /** 67 | * 隔多久重试 68 | * 69 | * 单位: 毫秒 70 | */ 71 | delay: number; 72 | }; 73 | /** 74 | * 接口级节流 75 | * 76 | * 单位: 毫秒 77 | */ 78 | throttle?: { 79 | delay: number; 80 | }; 81 | /** 82 | * 接口级防抖 83 | * 84 | * 单位: 毫秒 85 | */ 86 | debounce?: { 87 | delay: number; 88 | }; 89 | } 90 | 91 | export interface Result { 92 | code: number; 93 | data: T; 94 | } 95 | 96 | export interface AxiosRequestConfigRetry extends AxiosRequestConfig { 97 | retryCount?: number; 98 | } -------------------------------------------------------------------------------- /src/renderer/src/types/globals.d.ts: -------------------------------------------------------------------------------- 1 | // 通用声明 2 | import { HTMLTitleBarElementAttributes } from '@electron-uikit/titlebar/renderer'; 3 | 4 | export {}; 5 | 6 | // Vue 7 | declare module '*.vue' { 8 | import { DefineComponent } from 'vue'; 9 | 10 | const component: DefineComponent<{}, {}, any>; 11 | export default component; 12 | } 13 | 14 | declare type ClassName = { [className: string]: any } | ClassName[] | string; 15 | 16 | declare module '*.svg' { 17 | const CONTENT: string; 18 | export default CONTENT; 19 | } 20 | 21 | declare type Recordable = Record; -------------------------------------------------------------------------------- /src/renderer/src/types/interface.d.ts: -------------------------------------------------------------------------------- 1 | import type { TabValue } from 'tdesign-vue-next'; 2 | import { LocationQueryRaw, RouteRecordName } from 'vue-router'; 3 | 4 | export interface RouteMeta { 5 | title?: string | Record; 6 | icon?: string; 7 | expanded?: boolean; 8 | orderNo?: number; 9 | hidden?: boolean; 10 | hiddenBreadcrumb?: boolean; 11 | single?: boolean; 12 | keepAlive?: boolean; 13 | frameSrc?: string; 14 | frameBlank?: boolean; 15 | } 16 | 17 | export interface MenuRoute { 18 | // TODO: menuitem 组件实际支持 string 类型但是类型错误,暂时使用 any 类型避免打包错误待组件类型修复 19 | path: any; 20 | title?: string | Record; 21 | name?: string; 22 | icon?: 23 | | string 24 | | { 25 | render: () => void; 26 | }; 27 | redirect?: string; 28 | children: MenuRoute[]; 29 | meta: RouteMeta; 30 | } 31 | 32 | export type ModeType = 'dark' | 'light'; 33 | 34 | export interface UserInfo { 35 | name: string; 36 | roles: string[]; 37 | } 38 | 39 | export interface NotificationItem { 40 | id: string; 41 | content: string; 42 | type: string; 43 | status: boolean; 44 | collected: boolean; 45 | date: string; 46 | quality: string; 47 | } 48 | 49 | export interface TRouterInfo { 50 | path: string; 51 | query?: LocationQueryRaw; 52 | routeIdx?: number; 53 | title?: string; 54 | name?: RouteRecordName; 55 | isAlive?: boolean; 56 | isHome?: boolean; 57 | meta?: any; 58 | } 59 | 60 | export interface TTabRouterType { 61 | isRefreshing: boolean; 62 | tabRouterList: Array; 63 | } 64 | 65 | export interface TTabRemoveOptions { 66 | value: TabValue; 67 | index: number; 68 | e: MouseEvent; 69 | } -------------------------------------------------------------------------------- /src/renderer/src/utils/channel.ts: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request'; 2 | 3 | let controller = new AbortController(); 4 | 5 | const checkChannel = async (url: string) => { 6 | try { 7 | const startTime: Date = new Date(); // 记录开始请求的时间 8 | await request({ 9 | url, 10 | method: 'HEAD', 11 | timeout: 3000, 12 | }); 13 | const endTime: Date = new Date(); // 记录接收到响应的时间 14 | const delay: number = endTime.getTime() - startTime.getTime(); // 计算延迟 15 | return delay; 16 | } catch (err) { 17 | // console.error(err); 18 | return 9999; 19 | } 20 | }; 21 | 22 | const stopCheckChannel = () => { 23 | controller.abort(); 24 | 25 | controller = new AbortController(); 26 | }; 27 | 28 | export { checkChannel, stopCheckChannel }; 29 | -------------------------------------------------------------------------------- /src/renderer/src/utils/crypto/index.ts: -------------------------------------------------------------------------------- 1 | import { aes, des, tripleDES, rabbit, rabbitLegacy, rc4, rsa, sm4 } from './algorithm'; 2 | import { base64, hash, hmac, html, unicode, url, hex, gzip } from './encoding'; 3 | 4 | export { 5 | aes, 6 | base64, 7 | des, 8 | tripleDES, 9 | gzip, 10 | hash, 11 | hmac, 12 | html, 13 | rabbit, 14 | rabbitLegacy, 15 | rc4, 16 | rsa, 17 | sm4, 18 | unicode, 19 | url, 20 | hex, 21 | }; 22 | -------------------------------------------------------------------------------- /src/renderer/src/utils/emitter.ts: -------------------------------------------------------------------------------- 1 | import mitt from "mitt"; 2 | 3 | const emitter = mitt(); 4 | export default emitter; 5 | -------------------------------------------------------------------------------- /stylelint.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | defaultSeverity: 'error', 3 | extends: ['stylelint-config-standard'], 4 | rules: { 5 | 'no-descending-specificity': null, 6 | 'import-notation': 'string', 7 | 'no-empty-source': null, 8 | 'custom-property-pattern': null, 9 | 'selector-class-pattern': null, 10 | 'selector-pseudo-class-no-unknown': [ 11 | true, 12 | { 13 | ignorePseudoClasses: ['deep'], 14 | }, 15 | ], 16 | 'media-query-no-invalid': null, // 官方表示此规则应当仅对于原生CSS启用,对于预处理器(Less)不应启用 17 | }, 18 | overrides: [ 19 | { 20 | files: ['**/*.html', '**/*.vue'], 21 | customSyntax: 'postcss-html', 22 | }, 23 | { 24 | files: ['**/*.less'], 25 | customSyntax: 'postcss-less', 26 | }, 27 | ], 28 | }; -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": [], 3 | "references": [{ "path": "./tsconfig.node.json" }, { "path": "./tsconfig.web.json" }] 4 | } 5 | -------------------------------------------------------------------------------- /tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@electron-toolkit/tsconfig/tsconfig.node.json", 3 | "include": ["electron.vite.config.*", "src/main/**/*", "src/preload/**/*"], 4 | "compilerOptions": { 5 | "composite": true, 6 | "types": ["electron-vite/node"], 7 | "baseUrl": ".", 8 | "paths": { 9 | "@main/*" :[ 10 | "src/main/*" 11 | ], 12 | "@preload/*": [ 13 | "src/preload/*" 14 | ], 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /tsconfig.web.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@electron-toolkit/tsconfig/tsconfig.web.json", 3 | "include": [ 4 | "src/renderer/src/env.d.ts", 5 | "src/renderer/src/**/*", 6 | "src/renderer/src/**/*.ts", 7 | "src/renderer/src/**/*.tsx", 8 | "src/renderer/src/**/*.vue", 9 | "src/preload/*.d.ts" 10 | ], 11 | "compilerOptions": { 12 | "composite": true, 13 | "baseUrl": ".", 14 | "paths": { 15 | "@/*" :[ 16 | "src/renderer/src/*" 17 | ], 18 | "@renderer/*": [ 19 | "src/renderer/*" 20 | ] 21 | } 22 | } 23 | } 24 | --------------------------------------------------------------------------------