├── types ├── .gitkeep ├── http.d.ts └── vite-env.d.ts ├── packages ├── main │ ├── types │ │ ├── index.ts │ │ └── global.d.ts │ ├── public │ │ ├── esm-got.cjs │ │ ├── worker │ │ │ ├── fetchCookie.js │ │ │ └── fetch.js │ │ └── vm │ │ │ └── script.js │ ├── src │ │ ├── server │ │ │ ├── components │ │ │ │ ├── parse │ │ │ │ │ ├── index.ts │ │ │ │ │ └── routes │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── preHandle.ts │ │ │ │ │ │ └── getCustomRuleResult.ts │ │ │ │ ├── playerposhis │ │ │ │ │ ├── index.ts │ │ │ │ │ └── routes │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── getPos.ts │ │ │ │ │ │ └── record.ts │ │ │ │ ├── viewhistory │ │ │ │ │ ├── index.ts │ │ │ │ │ └── routes │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── all.ts │ │ │ │ │ │ ├── getViewHistory.ts │ │ │ │ │ │ └── recordClick.ts │ │ │ │ ├── articlelistrule │ │ │ │ │ ├── index.ts │ │ │ │ │ └── routes │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── articlelistrule.ts │ │ │ │ │ │ └── import.ts │ │ │ │ └── route.ts │ │ │ ├── hooks.ts │ │ │ ├── plugins.ts │ │ │ └── index.ts │ │ ├── index.ts │ │ ├── apis │ │ │ ├── core │ │ │ │ ├── air │ │ │ │ │ ├── utils │ │ │ │ │ │ ├── eventBus.ts │ │ │ │ │ │ ├── enum.ts │ │ │ │ │ │ ├── airVm │ │ │ │ │ │ │ ├── encoding.ts │ │ │ │ │ │ │ └── privateFile.ts │ │ │ │ │ │ ├── common.ts │ │ │ │ │ │ ├── require.ts │ │ │ │ │ │ └── airVmWorker.ts │ │ │ │ │ └── dav.ts │ │ │ │ ├── database │ │ │ │ │ ├── sqlite │ │ │ │ │ │ ├── models │ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ │ ├── sequelize.ts │ │ │ │ │ │ │ ├── playerposhis.ts │ │ │ │ │ │ │ └── viewhistory.ts │ │ │ │ │ │ └── sync.ts │ │ │ │ │ └── lowdb │ │ │ │ │ │ └── index.ts │ │ │ │ └── utils │ │ │ │ │ ├── password.ts │ │ │ │ │ ├── localLogger.ts │ │ │ │ │ ├── recordViewHistory.ts │ │ │ │ │ └── cloudShearPlate.ts │ │ │ └── app │ │ │ │ └── window │ │ │ │ └── constants.ts │ │ ├── config │ │ │ └── index.ts │ │ ├── utils │ │ │ ├── index.ts │ │ │ └── custom-cheerio.ts │ │ └── events │ │ │ ├── ipcList.ts │ │ │ └── airCoreIPC.ts │ └── tsconfig.json ├── renderer │ ├── src │ │ ├── hooks │ │ │ ├── home │ │ │ │ └── detail.ts │ │ │ ├── setting │ │ │ │ ├── useDesignSetting.ts │ │ │ │ └── index.ts │ │ │ └── socket │ │ │ │ └── index.ts │ │ ├── layout │ │ │ └── parentLayout.vue │ │ ├── components │ │ │ ├── AirColComponent │ │ │ │ ├── Line.vue │ │ │ │ ├── LineBlank.vue │ │ │ │ ├── BlankBlock.vue │ │ │ │ ├── BigBlankBlock.vue │ │ │ │ ├── util.ts │ │ │ │ ├── interface.ts │ │ │ │ ├── LongText.vue │ │ │ │ ├── RichText.vue │ │ │ │ ├── ScrollButton.vue │ │ │ │ ├── Text3.vue │ │ │ │ ├── Text4.vue │ │ │ │ ├── Text5.vue │ │ │ │ ├── IconSmall3.vue │ │ │ │ ├── Text2.vue │ │ │ │ ├── Text1.vue │ │ │ │ ├── X5WebviewSingle.vue │ │ │ │ ├── Avatar.vue │ │ │ │ ├── Icon2Round.vue │ │ │ │ ├── IconRoundSmall4.vue │ │ │ │ ├── TextCenter1.vue │ │ │ │ ├── Pic2.vue │ │ │ │ ├── Pic1.vue │ │ │ │ ├── Movie3.vue │ │ │ │ ├── Movie1.vue │ │ │ │ ├── Movie1VerticalPic.vue │ │ │ │ ├── Movie1LeftPic.vue │ │ │ │ └── Movie2.vue │ │ │ ├── AppProvider │ │ │ │ ├── index.ts │ │ │ │ └── index.vue │ │ │ ├── AirProvider │ │ │ │ ├── index.ts │ │ │ │ └── src │ │ │ │ │ ├── use-air.ts │ │ │ │ │ └── context.ts │ │ │ ├── VirtualGrid │ │ │ │ └── VirtualGrid.vue │ │ │ ├── MessageContent │ │ │ │ └── index.vue │ │ │ ├── Artplayer │ │ │ │ └── Artplayer.vue │ │ │ ├── ViewHistory │ │ │ │ └── ViewHistoryItem.vue │ │ │ └── DetailPanelList │ │ │ │ └── DetailPanelList.vue │ │ ├── assets │ │ │ ├── images │ │ │ │ ├── loading.png │ │ │ │ ├── ploading.gif │ │ │ │ └── player │ │ │ │ │ ├── iina.png │ │ │ │ │ ├── vlc.png │ │ │ │ │ ├── aria2.png │ │ │ │ │ ├── nplayer.png │ │ │ │ │ ├── preview.png │ │ │ │ │ ├── stellar.png │ │ │ │ │ ├── thunder.png │ │ │ │ │ ├── mxplayer.png │ │ │ │ │ └── potplayer.png │ │ │ └── svg │ │ │ │ └── indicator.svg │ │ ├── views │ │ │ ├── home │ │ │ │ ├── components │ │ │ │ │ ├── RuleSearch │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ └── interface.ts │ │ │ │ │ └── Logo.vue │ │ │ │ ├── options.ts │ │ │ │ ├── interface.ts │ │ │ │ ├── RuleTabs.vue │ │ │ │ └── RuleDrawer.vue │ │ │ ├── apiDocument │ │ │ │ └── index.vue │ │ │ └── setting │ │ │ │ └── components │ │ │ │ └── ImportBackupModal.vue │ │ ├── enums │ │ │ ├── roleEnum.ts │ │ │ ├── pageEnum.ts │ │ │ ├── cacheEnum.ts │ │ │ ├── breakpointEnum.ts │ │ │ └── httpEnum.ts │ │ ├── utils │ │ │ ├── storage.ts │ │ │ ├── log.ts │ │ │ ├── extract-public-props.ts │ │ │ ├── urlUtils.ts │ │ │ ├── http │ │ │ │ └── axios │ │ │ │ │ ├── types.ts │ │ │ │ │ ├── axiosTransform.ts │ │ │ │ │ ├── checkStatus.ts │ │ │ │ │ ├── helper.ts │ │ │ │ │ └── axiosCancel.ts │ │ │ ├── colSpan.ts │ │ │ ├── text.ts │ │ │ ├── events-impl.ts │ │ │ ├── rule.ts │ │ │ ├── playUtils.ts │ │ │ └── env.ts │ │ ├── store │ │ │ ├── index.ts │ │ │ └── modules │ │ │ │ ├── index.ts │ │ │ │ ├── app.ts │ │ │ │ ├── search.ts │ │ │ │ └── designSetting.ts │ │ ├── api │ │ │ ├── playerposhis.ts │ │ │ ├── articlelistrule.ts │ │ │ ├── viewhistory.ts │ │ │ └── parse.ts │ │ ├── plugins │ │ │ └── artplayer-plugin-danmuku │ │ │ │ ├── src │ │ │ │ ├── index.js │ │ │ │ ├── i18n.js │ │ │ │ ├── utils.js │ │ │ │ └── bilibili.js │ │ │ │ └── README.md │ │ ├── settings │ │ │ └── designSetting.ts │ │ ├── styles │ │ │ └── global.css │ │ ├── router │ │ │ └── index.ts │ │ └── index.ts │ ├── assets │ │ ├── font │ │ │ └── TwemojiMozilla.ttf │ │ └── logo.svg │ ├── build │ │ ├── constant.ts │ │ ├── getConfigFileName.ts │ │ ├── vite │ │ │ ├── plugin │ │ │ │ ├── styleImport.ts │ │ │ │ ├── compress.ts │ │ │ │ ├── index.ts │ │ │ │ └── html.ts │ │ │ └── proxy.ts │ │ ├── script │ │ │ ├── postBuild.ts │ │ │ └── buildConf.ts │ │ └── utils.ts │ ├── types │ │ ├── shims-vue.d.ts │ │ ├── config.d.ts │ │ └── importMeta.d.ts │ ├── postcss.config.js │ ├── index.html │ ├── tailwind.config.js │ ├── .eslintrc.json │ ├── tsconfig.json │ └── vite.config.js ├── shared │ ├── utils │ │ ├── index.ts │ │ └── url.ts │ ├── params │ │ ├── articlelistrule │ │ │ ├── index.ts │ │ │ └── Import.ts │ │ ├── playerposhis │ │ │ ├── index.ts │ │ │ ├── GetPos.ts │ │ │ └── Record.ts │ │ ├── viewhistory │ │ │ ├── index.ts │ │ │ ├── All.ts │ │ │ ├── GetLastclick.ts │ │ │ ├── RecordClick.ts │ │ │ └── Record.ts │ │ ├── index.ts │ │ └── parse │ │ │ ├── PreHandle.ts │ │ │ ├── index.ts │ │ │ ├── GetSearchRuleResult.ts │ │ │ ├── GetRuleDetailResult.ts │ │ │ ├── GetCustomRuleResult.ts │ │ │ ├── GetRuleResult.ts │ │ │ ├── GetLazyRuleResult.ts │ │ │ └── GetChildPageRuleResult.ts │ ├── types │ │ ├── enum.ts │ │ ├── global.d.ts │ │ ├── electron.d.ts │ │ └── types.d.ts │ ├── enums.ts │ ├── models │ │ ├── index.ts │ │ ├── ViewHistorys.ts │ │ ├── Articlelistrules.ts │ │ ├── ViewHistory.ts │ │ └── Articlelistrule.ts │ ├── events │ │ ├── socket-constants.ts │ │ └── constants.ts │ ├── parse │ │ └── constants.ts │ └── config │ │ └── index.ts └── preload │ ├── tsconfig.json │ ├── exposedInMainWorld.d.ts │ └── vite.config.js ├── .browserslistrc ├── README.md ├── electron-vendors.config.json ├── .husky ├── pre-commit └── commit-msg ├── .prettierignore ├── .vscode ├── setting.json └── settings.json ├── .gitattributes ├── .idea ├── codeStyles │ ├── codeStyleConfig.xml │ └── Project.xml ├── vcs.xml ├── prettier.xml ├── jsLibraryMappings.xml ├── jsLinters │ └── eslint.xml ├── inspectionProfiles │ └── Project_Default.xml ├── modules.xml ├── webResources.xml ├── deployment.xml └── vite-electron-builder.iml ├── .env ├── .eslintignore ├── .editorconfig ├── prettier.config.js ├── tsconfig.json ├── .env.development ├── .env.production ├── vetur.config.js ├── appveyor.yml ├── LICENSE ├── scripts ├── build.js ├── update-electron-vendors.js └── bytenode.js ├── .gitignore ├── electron-builder.config.js └── .eslintrc.js /types/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/main/types/index.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.browserslistrc: -------------------------------------------------------------------------------- 1 | Chrome 98 2 | -------------------------------------------------------------------------------- /packages/renderer/src/hooks/home/detail.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/shared/utils/index.ts: -------------------------------------------------------------------------------- 1 | export * from './url'; 2 | -------------------------------------------------------------------------------- /packages/main/public/esm-got.cjs: -------------------------------------------------------------------------------- 1 | module.exports = import('got'); 2 | -------------------------------------------------------------------------------- /packages/shared/params/articlelistrule/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Import'; 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 👾 Air 2 | 3 | ----- 4 |

海阔视界电脑版

5 | -------------------------------------------------------------------------------- /electron-vendors.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "chrome": "98", 3 | "node": "16" 4 | } 5 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx lint-staged 5 | -------------------------------------------------------------------------------- /packages/main/src/server/components/parse/index.ts: -------------------------------------------------------------------------------- 1 | export * as routes from './routes/index'; 2 | -------------------------------------------------------------------------------- /packages/renderer/src/layout/parentLayout.vue: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | packages/preload/exposedInMainWorld.d.ts 2 | packages/renderer/src/components.d.ts 3 | -------------------------------------------------------------------------------- /.vscode/setting.json: -------------------------------------------------------------------------------- 1 | { 2 | "[vue]": { "editor.defaultFormatter": "johnsoncodehk.volar" } 3 | } 4 | -------------------------------------------------------------------------------- /packages/main/src/server/components/playerposhis/index.ts: -------------------------------------------------------------------------------- 1 | export * as routes from './routes/index'; 2 | -------------------------------------------------------------------------------- /packages/main/src/server/components/viewhistory/index.ts: -------------------------------------------------------------------------------- 1 | export * as routes from './routes/index'; 2 | -------------------------------------------------------------------------------- /packages/shared/types/enum.ts: -------------------------------------------------------------------------------- 1 | export enum IWindowList { 2 | MAIN_WINDOW = 'MAIN_WINDOW', 3 | } 4 | -------------------------------------------------------------------------------- /packages/main/src/index.ts: -------------------------------------------------------------------------------- 1 | import { bootstrap } from '/@/lifeCycle'; 2 | 3 | bootstrap.launchApp(); 4 | -------------------------------------------------------------------------------- /packages/main/src/server/components/articlelistrule/index.ts: -------------------------------------------------------------------------------- 1 | export * as routes from './routes/index'; 2 | -------------------------------------------------------------------------------- /packages/renderer/src/components/AirColComponent/Line.vue: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /packages/shared/params/playerposhis/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Record'; 2 | export * from './GetPos'; 3 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx --no-install commitlint --edit $1 5 | -------------------------------------------------------------------------------- /packages/renderer/src/components/AppProvider/index.ts: -------------------------------------------------------------------------------- 1 | import AppProvider from './index.vue'; 2 | 3 | export { AppProvider }; 4 | -------------------------------------------------------------------------------- /packages/main/types/global.d.ts: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line no-var 2 | declare var airServer: import('/@/server').Server | undefined; 3 | -------------------------------------------------------------------------------- /packages/renderer/assets/font/TwemojiMozilla.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lingyan000/air/HEAD/packages/renderer/assets/font/TwemojiMozilla.ttf -------------------------------------------------------------------------------- /packages/renderer/src/assets/images/loading.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lingyan000/air/HEAD/packages/renderer/src/assets/images/loading.png -------------------------------------------------------------------------------- /packages/renderer/src/assets/images/ploading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lingyan000/air/HEAD/packages/renderer/src/assets/images/ploading.gif -------------------------------------------------------------------------------- /packages/renderer/src/views/home/components/RuleSearch/index.ts: -------------------------------------------------------------------------------- 1 | import RuleSearch from './RuleSearch.vue'; 2 | 3 | export default RuleSearch; 4 | -------------------------------------------------------------------------------- /packages/renderer/src/views/home/options.ts: -------------------------------------------------------------------------------- 1 | export const displayColType: ColType[] = ['rich_text', 'long_text', 'blank_block', 'line_blank']; 2 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | .github/actions/**/*.js linguist-detectable=false 2 | scripts/*.js linguist-detectable=false 3 | *.config.js linguist-detectable=false 4 | -------------------------------------------------------------------------------- /packages/renderer/src/assets/images/player/iina.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lingyan000/air/HEAD/packages/renderer/src/assets/images/player/iina.png -------------------------------------------------------------------------------- /packages/renderer/src/assets/images/player/vlc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lingyan000/air/HEAD/packages/renderer/src/assets/images/player/vlc.png -------------------------------------------------------------------------------- /packages/renderer/src/enums/roleEnum.ts: -------------------------------------------------------------------------------- 1 | export enum RoleEnum { 2 | // 管理员 3 | ADMIN = 'admin', 4 | 5 | // 普通用户 6 | NORMAL = 'normal', 7 | } 8 | -------------------------------------------------------------------------------- /packages/renderer/src/utils/storage.ts: -------------------------------------------------------------------------------- 1 | import store from 'store2'; 2 | 3 | const storage = store.namespace('air'); 4 | 5 | export default storage; 6 | -------------------------------------------------------------------------------- /packages/renderer/src/assets/images/player/aria2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lingyan000/air/HEAD/packages/renderer/src/assets/images/player/aria2.png -------------------------------------------------------------------------------- /packages/renderer/src/assets/images/player/nplayer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lingyan000/air/HEAD/packages/renderer/src/assets/images/player/nplayer.png -------------------------------------------------------------------------------- /packages/renderer/src/assets/images/player/preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lingyan000/air/HEAD/packages/renderer/src/assets/images/player/preview.png -------------------------------------------------------------------------------- /packages/renderer/src/assets/images/player/stellar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lingyan000/air/HEAD/packages/renderer/src/assets/images/player/stellar.png -------------------------------------------------------------------------------- /packages/renderer/src/assets/images/player/thunder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lingyan000/air/HEAD/packages/renderer/src/assets/images/player/thunder.png -------------------------------------------------------------------------------- /packages/main/src/apis/core/air/utils/eventBus.ts: -------------------------------------------------------------------------------- 1 | import { EventEmitter } from 'events'; 2 | 3 | const eventBus = new EventEmitter(); 4 | export { eventBus }; 5 | -------------------------------------------------------------------------------- /packages/renderer/src/assets/images/player/mxplayer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lingyan000/air/HEAD/packages/renderer/src/assets/images/player/mxplayer.png -------------------------------------------------------------------------------- /packages/renderer/src/assets/images/player/potplayer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lingyan000/air/HEAD/packages/renderer/src/assets/images/player/potplayer.png -------------------------------------------------------------------------------- /types/http.d.ts: -------------------------------------------------------------------------------- 1 | interface Result { 2 | code: number; 3 | type?: 'success' | 'error' | 'warning'; 4 | message: string; 5 | result?: T; 6 | } 7 | -------------------------------------------------------------------------------- /packages/main/src/server/components/playerposhis/routes/index.ts: -------------------------------------------------------------------------------- 1 | export { default as record } from './record'; 2 | export { default as getPos } from './getPos'; 3 | -------------------------------------------------------------------------------- /packages/renderer/src/components/AirProvider/index.ts: -------------------------------------------------------------------------------- 1 | export { default as AirProvider } from './src/AirProvider.vue'; 2 | export { useAir } from './src/use-air'; 3 | -------------------------------------------------------------------------------- /packages/shared/enums.ts: -------------------------------------------------------------------------------- 1 | export enum From { 2 | 'home', 3 | 'search', 4 | } 5 | 6 | export enum ViewHistoryType { 7 | '网页浏览', 8 | '二级列表', 9 | } 10 | -------------------------------------------------------------------------------- /packages/shared/types/global.d.ts: -------------------------------------------------------------------------------- 1 | declare var notificationList: IAppNotification[]; 2 | declare var vmCpNumber: number; 3 | declare var HK_PRIVATE_KEY: string[]; 4 | -------------------------------------------------------------------------------- /packages/shared/params/viewhistory/index.ts: -------------------------------------------------------------------------------- 1 | export * from './All'; 2 | export * from './GetLastclick'; 3 | export * from './Record'; 4 | export * from './RecordClick'; 5 | -------------------------------------------------------------------------------- /packages/shared/models/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Articlelistrules'; 2 | export * from './Articlelistrule'; 3 | export * from './ViewHistory'; 4 | export * from './ViewHistorys'; 5 | -------------------------------------------------------------------------------- /packages/shared/events/socket-constants.ts: -------------------------------------------------------------------------------- 1 | export const REFRESH_PAGE = 'REFRESH_PAGE'; 2 | export const SHOW_LOADING = 'SHOW_LOADING'; 3 | export const HIDE_LOADING = 'HIDE_LOADING'; 4 | -------------------------------------------------------------------------------- /packages/main/src/server/components/articlelistrule/routes/index.ts: -------------------------------------------------------------------------------- 1 | export { default as allArticlelistrule } from './articlelistrule'; 2 | export { default as importRule } from './import'; 3 | -------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /packages/main/src/apis/core/database/sqlite/models/index.ts: -------------------------------------------------------------------------------- 1 | import Articlelistrule from './articlelistrule'; 2 | import { DB_VERSION } from '/@/config'; 3 | 4 | export { Articlelistrule, DB_VERSION }; 5 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/prettier.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | -------------------------------------------------------------------------------- /.idea/jsLibraryMappings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /packages/shared/params/index.ts: -------------------------------------------------------------------------------- 1 | export * as Parse from './parse'; 2 | export * as Articlelistrule from './articlelistrule'; 3 | export * as Playerposhis from './playerposhis'; 4 | export * as ViewHistory from './viewhistory'; 5 | -------------------------------------------------------------------------------- /packages/renderer/build/constant.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * The name of the configuration file entered in the production environment 3 | */ 4 | export const GLOB_CONFIG_FILE_NAME = 'app.config.js'; 5 | 6 | export const OUTPUT_DIR = './dist'; 7 | -------------------------------------------------------------------------------- /packages/renderer/src/components/AirColComponent/LineBlank.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 10 | -------------------------------------------------------------------------------- /packages/renderer/src/components/VirtualGrid/VirtualGrid.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 10 | -------------------------------------------------------------------------------- /packages/shared/params/parse/PreHandle.ts: -------------------------------------------------------------------------------- 1 | import { Static, Type } from '@sinclair/typebox'; 2 | 3 | export const PreHandle = Type.Object({ 4 | id: Type.Number(), 5 | }); 6 | 7 | export type PreHandle = Static; 8 | -------------------------------------------------------------------------------- /packages/shared/params/playerposhis/GetPos.ts: -------------------------------------------------------------------------------- 1 | import { Static, Type } from '@sinclair/typebox'; 2 | 3 | export const GetPos = Type.Object({ 4 | playurl: Type.String(), 5 | }); 6 | 7 | export type GetPos = Static; 8 | -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | # port 2 | VITE_PORT=3000 3 | 4 | # spa-title 5 | VITE_GLOB_APP_TITLE=Air 6 | 7 | # spa shortname 8 | VITE_GLOB_APP_SHORT_NAME=Air 9 | 10 | GLOBAL_AGENT_HTTP_PROXY=http://127.0.0.1:8888 11 | 12 | # VITE_HK_PRIVATE_KEY= 13 | -------------------------------------------------------------------------------- /packages/shared/params/articlelistrule/Import.ts: -------------------------------------------------------------------------------- 1 | import { Static, Type } from '@sinclair/typebox'; 2 | 3 | export const ImportRule = Type.Object({ 4 | password: Type.String(), 5 | }); 6 | 7 | export type ImportRule = Static; 8 | -------------------------------------------------------------------------------- /packages/shared/params/viewhistory/All.ts: -------------------------------------------------------------------------------- 1 | import { Static, Type } from '@sinclair/typebox'; 2 | 3 | export const All = Type.Object({ 4 | currentPage: Type.Number(), 5 | pageSize: Type.Number(), 6 | }); 7 | 8 | export type All = Static; 9 | -------------------------------------------------------------------------------- /packages/shared/params/playerposhis/Record.ts: -------------------------------------------------------------------------------- 1 | import { Static, Type } from '@sinclair/typebox'; 2 | 3 | export const Record = Type.Object({ 4 | playurl: Type.String(), 5 | pos: Type.Number(), 6 | }); 7 | 8 | export type Record = Static; 9 | -------------------------------------------------------------------------------- /packages/renderer/src/components/AirColComponent/BlankBlock.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 11 | -------------------------------------------------------------------------------- /packages/renderer/src/components/AirColComponent/BigBlankBlock.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 11 | -------------------------------------------------------------------------------- /packages/renderer/src/store/index.ts: -------------------------------------------------------------------------------- 1 | import type { App } from 'vue'; 2 | import { createPinia } from 'pinia'; 3 | 4 | const store = createPinia(); 5 | 6 | export function setupStore(app: App) { 7 | app.use(store); 8 | } 9 | 10 | export { store }; 11 | -------------------------------------------------------------------------------- /packages/main/src/server/components/viewhistory/routes/index.ts: -------------------------------------------------------------------------------- 1 | export { default as all } from './all'; 2 | export { default as getViewHistory } from './getViewHistory'; 3 | export { default as Record } from './Record'; 4 | export { default as RecordClick } from './RecordClick'; 5 | -------------------------------------------------------------------------------- /packages/shared/params/viewhistory/GetLastclick.ts: -------------------------------------------------------------------------------- 1 | import { Static, Type } from '@sinclair/typebox'; 2 | 3 | export const GetLastclick = Type.Object({ 4 | title: Type.String(), 5 | url: Type.String(), 6 | }); 7 | 8 | export type GetLastclick = Static; 9 | -------------------------------------------------------------------------------- /packages/preload/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "baseUrl": ".", 5 | "paths": { 6 | "/@/*": ["./src/*"] 7 | } 8 | }, 9 | "files": ["src/index.ts"], 10 | "include": ["../../types/**/*.d.ts"] 11 | } 12 | -------------------------------------------------------------------------------- /.idea/jsLinters/eslint.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cSpell.words": [ 3 | "Articlelistrule", 4 | "fyarea", 5 | "fyclass", 6 | "fypage", 7 | "fysort", 8 | "fyyear" 9 | ], 10 | "emmet.includeLanguages": { 11 | "postcss": "css" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /packages/renderer/types/shims-vue.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.vue' { 2 | import type { DefineComponent } from 'vue'; 3 | // eslint-disable-next-line @typescript-eslint/ban-types, @typescript-eslint/no-explicit-any 4 | const component: DefineComponent<{}, {}, any>; 5 | export default component; 6 | } 7 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /packages/main/src/apis/app/window/constants.ts: -------------------------------------------------------------------------------- 1 | const isDevelopment = import.meta.env.MODE === 'development'; 2 | 3 | export const MAIN_WINDOW_URL = 4 | isDevelopment && import.meta.env.VITE_DEV_SERVER_URL !== undefined 5 | ? import.meta.env.VITE_DEV_SERVER_URL 6 | : 'airr://./renderer/dist/index.html'; 7 | -------------------------------------------------------------------------------- /packages/shared/params/parse/index.ts: -------------------------------------------------------------------------------- 1 | export * from './GetRuleResult'; 2 | export * from './GetSearchRuleResult'; 3 | export * from './GetRuleDetailResult'; 4 | export * from './GetLazyRuleResult'; 5 | export * from './GetChildPageRuleResult'; 6 | export * from './PreHandle'; 7 | export * from './GetCustomRuleResult'; 8 | -------------------------------------------------------------------------------- /packages/main/src/apis/core/air/utils/enum.ts: -------------------------------------------------------------------------------- 1 | export enum ILogType { 2 | success = 'success', 3 | info = 'info', 4 | warn = 'warn', 5 | error = 'error', 6 | } 7 | 8 | /** 9 | * these events will be catched only by picgo 10 | */ 11 | export enum IBusEvent { 12 | CONFIG_CHANGE = 'CONFIG_CHANGE', 13 | } 14 | -------------------------------------------------------------------------------- /packages/main/public/worker/fetchCookie.js: -------------------------------------------------------------------------------- 1 | const { runAsWorker } = require('sync-threads'); 2 | 3 | runAsWorker(async ({ url, config = {} }) => { 4 | const got = (await require('../esm-got.cjs')).default; 5 | return got(url, config).then((res) => { 6 | return JSON.stringify(res.headers['set-cookie']); 7 | }); 8 | }); 9 | -------------------------------------------------------------------------------- /packages/renderer/src/utils/log.ts: -------------------------------------------------------------------------------- 1 | const projectName = import.meta.env.VITE_GLOB_APP_TITLE; 2 | 3 | export function warn(message: string) { 4 | console.warn(`[${projectName} warn]:${message}`); 5 | } 6 | 7 | export function error(message: string) { 8 | throw new Error(`[${projectName} error]:${message}`); 9 | } 10 | -------------------------------------------------------------------------------- /packages/renderer/src/components/AirColComponent/util.ts: -------------------------------------------------------------------------------- 1 | export function renderText(text: string) { 2 | return ( 3 | '' + 4 | text 5 | .replace(/““(.*)””/, '$1') 6 | .replace(/‘‘(.*)’’/, '$1') + 7 | '' 8 | ); 9 | } 10 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /packages/renderer/build/getConfigFileName.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Get the configuration file variable name 3 | * @param env 4 | */ 5 | export const getConfigFileName = (env: Record) => { 6 | return `__PRODUCTION__${env.VITE_GLOB_APP_SHORT_NAME || '__APP'}__CONF__` 7 | .toUpperCase() 8 | .replace(/\s/g, ''); 9 | }; 10 | -------------------------------------------------------------------------------- /packages/renderer/src/views/home/interface.ts: -------------------------------------------------------------------------------- 1 | import { InjectionKey, Ref } from 'vue'; 2 | 3 | export interface HomeInjection { 4 | ruleListRef: Ref; 5 | showRuleDrawerRef: Ref; 6 | activeNameRef: Ref; 7 | } 8 | 9 | export const homeInjectionKey: InjectionKey = Symbol('home'); 10 | -------------------------------------------------------------------------------- /packages/shared/params/parse/GetSearchRuleResult.ts: -------------------------------------------------------------------------------- 1 | import { Static, Type } from '@sinclair/typebox'; 2 | 3 | export const GetSearchRuleResult = Type.Object({ 4 | id: Type.Number(), 5 | fypage: Type.Optional(Type.String()), 6 | search: Type.String(), 7 | }); 8 | 9 | export type GetSearchRuleResult = Static; 10 | -------------------------------------------------------------------------------- /packages/renderer/src/components/AirProvider/src/use-air.ts: -------------------------------------------------------------------------------- 1 | import { inject } from 'vue'; 2 | import { airApiInjectionKey } from './context'; 3 | 4 | export function useAir() { 5 | const api = inject(airApiInjectionKey, null); 6 | if (api === null) { 7 | throw new Error('[airApiInjectionKey] api is null'); 8 | } 9 | return api; 10 | } 11 | -------------------------------------------------------------------------------- /packages/renderer/src/utils/extract-public-props.ts: -------------------------------------------------------------------------------- 1 | import { ExtractPropTypes } from 'vue'; 2 | 3 | export type ExtractPublicPropTypes = Omit< 4 | Partial>, 5 | Exclude | Extract 6 | >; 7 | 8 | export type ExtractInternalPropTypes = Partial>; 9 | -------------------------------------------------------------------------------- /packages/shared/params/viewhistory/RecordClick.ts: -------------------------------------------------------------------------------- 1 | import { Static, Type } from '@sinclair/typebox'; 2 | 3 | export const RecordClick = Type.Object({ 4 | title: Type.String(), 5 | url: Type.String(), 6 | lastclickTitle: Type.String(), 7 | lastclickIndex: Type.Number(), 8 | }); 9 | 10 | export type RecordClick = Static; 11 | -------------------------------------------------------------------------------- /packages/renderer/postcss.config.js: -------------------------------------------------------------------------------- 1 | const tailwindcss = require('tailwindcss'); 2 | const autoprefixer = require('autoprefixer'); 3 | const { join } = require('path'); 4 | 5 | module.exports = { 6 | plugins: [ 7 | tailwindcss({ 8 | config: join(__dirname, './tailwind.config.js'), 9 | }), 10 | autoprefixer({}), 11 | ], 12 | }; 13 | -------------------------------------------------------------------------------- /packages/renderer/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 空气 7 | 8 | 9 |
10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /packages/shared/parse/constants.ts: -------------------------------------------------------------------------------- 1 | export const PC_UA = 2 | 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.87 Safari/537.36'; 3 | 4 | export const MOBILE_UA = 5 | 'Mozilla/5.0 (Linux; Android 11; 2112123AC Build/RKQ1.200826.002;) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/94.0.4606.85 Mobile Safari/537.36'; 6 | -------------------------------------------------------------------------------- /packages/renderer/src/enums/pageEnum.ts: -------------------------------------------------------------------------------- 1 | export enum PageEnum { 2 | // 登录 3 | BASE_LOGIN = '/login', 4 | BASE_LOGIN_NAME = 'Login', 5 | //重定向 6 | REDIRECT = '/redirect', 7 | REDIRECT_NAME = 'Redirect', 8 | // 首页 9 | BASE_HOME = '/dashboard', 10 | //首页跳转默认路由 11 | BASE_HOME_REDIRECT = '/dashboard/console', 12 | // 错误 13 | ERROR_PAGE_NAME = 'ErrorPage', 14 | } 15 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | *.sh 2 | packages/preload/exposedInMainWorld.d.ts 3 | node_modules/** 4 | *.md 5 | *.woff 6 | *.ttf 7 | .vscode 8 | .idea 9 | **/dist/** 10 | /public 11 | /docs 12 | .husky 13 | .local 14 | /bin 15 | Dockerfile 16 | packages/renderer/src/components.d.ts 17 | packages/main/types/global.d.ts 18 | packages/shared/types/global.d.ts 19 | tests/** 20 | packages/main/public/* 21 | -------------------------------------------------------------------------------- /packages/renderer/src/components/AirColComponent/interface.ts: -------------------------------------------------------------------------------- 1 | import { InjectionKey, Ref } from 'vue'; 2 | 3 | export interface AirHomeComponentInjection { 4 | picUrlRef: Ref; 5 | titleRef: Ref; 6 | descRef: Ref; 7 | urlRef: Ref; 8 | } 9 | 10 | export const airHomeComponentInjectionKey: InjectionKey = Symbol('home'); 11 | -------------------------------------------------------------------------------- /packages/renderer/src/views/home/components/Logo.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 8 | 9 | 16 | -------------------------------------------------------------------------------- /packages/shared/models/ViewHistorys.ts: -------------------------------------------------------------------------------- 1 | import type { Static } from '@sinclair/typebox'; 2 | import { Type } from '@sinclair/typebox'; 3 | import { ViewHistory } from './ViewHistory'; 4 | 5 | export const ViewHistorys = Type.Array(ViewHistory, { 6 | $id: 'ViewHistorys', 7 | title: 'ViewHistorys', 8 | description: 'ViewHistorys', 9 | }); 10 | 11 | export type ViewHistorys = Static; 12 | -------------------------------------------------------------------------------- /packages/shared/params/parse/GetRuleDetailResult.ts: -------------------------------------------------------------------------------- 1 | import { Static, Type } from '@sinclair/typebox'; 2 | import { From } from '../../enums'; 3 | 4 | const from = Type.Enum(From); 5 | 6 | export const GetRuleDetailResult = Type.Object({ 7 | id: Type.Number(), 8 | from: Type.Optional(from), 9 | url: Type.String(), 10 | }); 11 | 12 | export type GetRuleDetailResult = Static; 13 | -------------------------------------------------------------------------------- /packages/main/src/config/index.ts: -------------------------------------------------------------------------------- 1 | import { join } from 'path'; 2 | import air from '/@/apis/core/air'; 3 | import fs from 'fs-extra'; 4 | 5 | const DB_VERSION = 51; 6 | 7 | const dbDir = join(air.baseDir, 'db'); 8 | const exist = fs.existsSync(dbDir); 9 | if (!exist) { 10 | fs.ensureDirSync(dbDir); 11 | } 12 | 13 | const dbUrl = join(dbDir, `./hiker_${DB_VERSION}.db`); 14 | 15 | export { DB_VERSION, dbDir, dbUrl }; 16 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: http://EditorConfig.org 2 | 3 | # https://github.com/jokeyrhyme/standard-editorconfig 4 | 5 | # top-most EditorConfig file 6 | root = true 7 | 8 | # defaults 9 | [*] 10 | charset = utf-8 11 | end_of_line = lf 12 | insert_final_newline = true 13 | trim_trailing_whitespace = true 14 | indent_size = 2 15 | indent_style = space 16 | 17 | [*.md] 18 | trim_trailing_whitespace = false 19 | -------------------------------------------------------------------------------- /packages/renderer/tailwind.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | module.exports = { 3 | purge: [ 4 | path.join(__dirname, './index.html'), 5 | path.join(__dirname, './src/**/*.{vue,js,ts,jsx,tsx}'), 6 | ], 7 | prefix: 'tw-', 8 | theme: { 9 | spacing: { 10 | sm: '8px', 11 | md: '16px', 12 | lg: '24px', 13 | xl: '48px', 14 | }, 15 | extend: {}, 16 | }, 17 | plugins: [], 18 | }; 19 | -------------------------------------------------------------------------------- /packages/renderer/src/store/modules/index.ts: -------------------------------------------------------------------------------- 1 | // export default modules 2 | import app from './app'; 3 | 4 | const allModules = import.meta.globEager('./*/index.ts'); 5 | const modules = {} as any; 6 | Object.keys(allModules).forEach((path) => { 7 | const fileName = path.split('/')[1]; 8 | modules[fileName] = allModules[path][fileName] || allModules[path].default || allModules[path]; 9 | }); 10 | 11 | export default { 12 | app, 13 | }; 14 | -------------------------------------------------------------------------------- /packages/shared/models/Articlelistrules.ts: -------------------------------------------------------------------------------- 1 | import type { Static } from '@sinclair/typebox'; 2 | import { Type } from '@sinclair/typebox'; 3 | import { Articlelistrule } from './Articlelistrule'; 4 | 5 | export const Articlelistrules = Type.Array(Articlelistrule, { 6 | $id: 'Articlelistrules', 7 | title: 'Articlelistrules', 8 | description: 'Articlelistrules', 9 | }); 10 | 11 | export type Articlelistrules = Static; 12 | -------------------------------------------------------------------------------- /packages/shared/params/parse/GetCustomRuleResult.ts: -------------------------------------------------------------------------------- 1 | import { Static, Type } from '@sinclair/typebox'; 2 | 3 | export const GetCustomRuleResult = Type.Object({ 4 | title: Type.String(), 5 | url: Type.String(), 6 | col_type: Type.String(), 7 | find_rule: Type.String(), 8 | group: Type.String(), 9 | ua: Type.String(), 10 | preRule: Type.String(), 11 | }); 12 | 13 | export type GetCustomRuleResult = Static; 14 | -------------------------------------------------------------------------------- /.idea/webResources.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /packages/shared/params/parse/GetRuleResult.ts: -------------------------------------------------------------------------------- 1 | import { Static, Type } from '@sinclair/typebox'; 2 | import { From } from '../../enums'; 3 | 4 | const from = Type.Enum(From); 5 | 6 | export const GetRuleResult = Type.Object({ 7 | id: Type.Optional(Type.Number()), 8 | from: Type.Optional(from), 9 | url: Type.String(), 10 | rule: Type.String(), 11 | originRule: Type.Optional(Type.Any()), 12 | }); 13 | 14 | export type GetRuleResult = Static; 15 | -------------------------------------------------------------------------------- /packages/renderer/src/components/AirColComponent/LongText.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 11 | 12 | 17 | -------------------------------------------------------------------------------- /packages/shared/params/parse/GetLazyRuleResult.ts: -------------------------------------------------------------------------------- 1 | import { Static, Type } from '@sinclair/typebox'; 2 | import { From } from '../../enums'; 3 | 4 | const from = Type.Enum(From); 5 | 6 | export const GetLazyRuleResult = Type.Object({ 7 | id: Type.Optional(Type.Number()), 8 | from: Type.Optional(from), 9 | url: Type.String(), 10 | lazyRule: Type.String(), 11 | originRule: Type.Optional(Type.Any()), 12 | }); 13 | 14 | export type GetLazyRuleResult = Static; 15 | -------------------------------------------------------------------------------- /prettier.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | printWidth: 100, 3 | tabWidth: 2, 4 | useTabs: false, 5 | semi: true, 6 | vueIndentScriptAndStyle: true, 7 | singleQuote: true, 8 | quoteProps: 'as-needed', 9 | bracketSpacing: true, 10 | trailingComma: 'es5', 11 | jsxSingleQuote: false, 12 | arrowParens: 'always', 13 | insertPragma: false, 14 | requirePragma: false, 15 | proseWrap: 'never', 16 | htmlWhitespaceSensitivity: 'strict', 17 | endOfLine: 'auto', 18 | rangeStart: 0, 19 | }; 20 | -------------------------------------------------------------------------------- /packages/main/src/utils/index.ts: -------------------------------------------------------------------------------- 1 | import { session } from 'electron'; 2 | 3 | export function setDefaultHeaders() { 4 | const filter = { 5 | urls: ['*://*/*'], 6 | }; 7 | session.defaultSession.webRequest.onBeforeSendHeaders(filter, (details, callback) => { 8 | // @ts-ignore 9 | details.requestHeaders['Origin'] = undefined; 10 | // @ts-ignore 11 | details.requestHeaders['Referer'] = undefined; 12 | callback({ cancel: false, requestHeaders: details.requestHeaders }); 13 | }); 14 | } 15 | -------------------------------------------------------------------------------- /packages/renderer/src/views/home/components/RuleSearch/interface.ts: -------------------------------------------------------------------------------- 1 | import { InjectionKey, Ref } from 'vue'; 2 | import * as Models from '#/models'; 3 | 4 | export interface HomeRuleSearchInjection { 5 | activeIdRef: Ref; 6 | popMaxHeight: Ref; 7 | searchRuleList: Ref; 8 | lastSearchRuleList: Ref; 9 | } 10 | 11 | export const homeRuleSearchInjectionKey: InjectionKey = 12 | Symbol('homeRuleSearch'); 13 | -------------------------------------------------------------------------------- /packages/renderer/src/components/MessageContent/index.vue: -------------------------------------------------------------------------------- 1 | 17 | -------------------------------------------------------------------------------- /packages/shared/params/parse/GetChildPageRuleResult.ts: -------------------------------------------------------------------------------- 1 | import { Static, Type } from '@sinclair/typebox'; 2 | import { From } from '../../enums'; 3 | 4 | const from = Type.Enum(From); 5 | 6 | export const GetChildPageRuleResult = Type.Object({ 7 | id: Type.Optional(Type.Number()), 8 | from: Type.Optional(from), 9 | url: Type.String(), 10 | fypage: Type.Optional(Type.String()), 11 | originRule: Type.Optional(Type.Any()), 12 | }); 13 | 14 | export type GetChildPageRuleResult = Static; 15 | -------------------------------------------------------------------------------- /packages/renderer/src/components/AirColComponent/RichText.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 12 | 13 | 17 | -------------------------------------------------------------------------------- /packages/main/src/server/components/parse/routes/index.ts: -------------------------------------------------------------------------------- 1 | export { default as getSearchRuleResult } from './getSearchRuleResult'; 2 | export { default as getRuleDetailResult } from './getRuleDetailResult'; 3 | export { default as getLazyRuleResult } from './getLazyRuleResult'; 4 | export { default as getChildPageRuleResult } from './getChildPageRuleResult'; 5 | export { default as preHandle } from './preHandle'; 6 | export { default as getRuleResult } from './getRuleResult'; 7 | export { default as getCustomRuleResult } from './getCustomRuleResult'; 8 | -------------------------------------------------------------------------------- /packages/renderer/src/api/playerposhis.ts: -------------------------------------------------------------------------------- 1 | import http from '/@/utils/http/axios'; 2 | import { Playerposhis as PlayerposhisQuery } from '#/params'; 3 | 4 | export function record(params: PlayerposhisQuery.Record) { 5 | return http.request({ 6 | url: '/playerposhis/record', 7 | method: 'put', 8 | data: params, 9 | }); 10 | } 11 | 12 | export function getPos(params: PlayerposhisQuery.GetPos) { 13 | return http.request({ 14 | url: '/playerposhis/getPos', 15 | method: 'get', 16 | params, 17 | }); 18 | } 19 | -------------------------------------------------------------------------------- /packages/renderer/src/components/AirColComponent/ScrollButton.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 12 | 13 | 18 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "esnext", 4 | "target": "esnext", 5 | "sourceMap": true, 6 | "moduleResolution": "Node", 7 | "skipLibCheck": true, 8 | "strict": true, 9 | "isolatedModules": true, 10 | "allowSyntheticDefaultImports": true, 11 | "resolveJsonModule": true, 12 | "noImplicitAny": false, 13 | "types": [ 14 | "node" 15 | ], 16 | "typeRoots": [ 17 | "node_modules/@types", 18 | ], 19 | "lib": [ 20 | "ESNext" 21 | ] 22 | }, 23 | } 24 | -------------------------------------------------------------------------------- /types/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | /** 4 | * Describes all existing environment variables and their types. 5 | * Assists in autocomplete and typechecking 6 | * 7 | * @see https://github.com/vitejs/vite/blob/eef51cb37db98a1ad9a541bdd3cd74736ff8488d/packages/vite/types/importMeta.d.ts#L62-L69 Base Interface 8 | */ 9 | interface ImportMetaEnv { 10 | /** 11 | * The value of the variable is set in scripts/watch.js and depend on packages/main/vite.config.js 12 | */ 13 | VITE_DEV_SERVER_URL: undefined | string; 14 | } 15 | -------------------------------------------------------------------------------- /packages/renderer/src/views/apiDocument/index.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 14 | 15 | 25 | -------------------------------------------------------------------------------- /packages/renderer/src/store/modules/app.ts: -------------------------------------------------------------------------------- 1 | import { defineStore } from 'pinia'; 2 | 3 | type Theme = 'auto' | 'dark' | 'light'; 4 | 5 | interface AppStore { 6 | theme: Theme; 7 | } 8 | 9 | const useAppStore = defineStore({ 10 | id: 'air-main', 11 | state: (): AppStore => ({ 12 | theme: 'auto', 13 | }), 14 | getters: { 15 | getTheme(): Theme { 16 | return this.theme; 17 | }, 18 | }, 19 | actions: { 20 | setTheme(theme: Theme): void { 21 | this.theme = theme; 22 | }, 23 | }, 24 | }); 25 | 26 | export default useAppStore; 27 | -------------------------------------------------------------------------------- /.env.development: -------------------------------------------------------------------------------- 1 | # 只在开发模式中被载入 2 | VITE_PORT = 3000 3 | 4 | # 网站根目录 5 | VITE_PUBLIC_PATH = / 6 | 7 | # 网站前缀 8 | VITE_BASE_URL = / 9 | 10 | # 是否删除console 11 | VITE_DROP_CONSOLE = true 12 | 13 | # 跨域代理,可以配置多个,请注意不要换行 14 | #VITE_PROXY = [["/appApi","http://localhost:8001"],["/upload","http://localhost:8001/upload"]] 15 | # VITE_PROXY=[["/api","https://naive-ui-admin"]] 16 | 17 | # API 接口地址 18 | VITE_GLOB_API_URL = http://localhost:52020 19 | 20 | # 图片上传地址 21 | VITE_GLOB_UPLOAD_URL= 22 | 23 | # 图片前缀地址 24 | VITE_GLOB_IMG_URL= 25 | 26 | # 接口前缀 27 | VITE_GLOB_API_URL_PREFIX = 28 | 29 | -------------------------------------------------------------------------------- /packages/renderer/src/hooks/setting/useDesignSetting.ts: -------------------------------------------------------------------------------- 1 | import { computed } from 'vue'; 2 | import { useDesignSettingStore } from '/@/store/modules/designSetting'; 3 | 4 | export function useDesignSetting() { 5 | const designStore = useDesignSettingStore(); 6 | 7 | const getThemeMode = computed(() => designStore.themeMode); 8 | 9 | const getAppTheme = computed(() => designStore.appTheme); 10 | 11 | const getAppThemeList = computed(() => designStore.appThemeList); 12 | 13 | return { 14 | getThemeMode, 15 | getAppTheme, 16 | getAppThemeList, 17 | }; 18 | } 19 | -------------------------------------------------------------------------------- /packages/renderer/src/plugins/artplayer-plugin-danmuku/src/index.js: -------------------------------------------------------------------------------- 1 | import Danmuku from './danmuku'; 2 | 3 | export default function artplayerPluginDanmuku(option) { 4 | return (art) => { 5 | const danmuku = new Danmuku(art, option); 6 | return { 7 | name: 'artplayerPluginDanmuku', 8 | emit: danmuku.emit.bind(danmuku), 9 | config: danmuku.config.bind(danmuku), 10 | hide: danmuku.hide.bind(danmuku), 11 | show: danmuku.show.bind(danmuku), 12 | get isHide() { 13 | return danmuku.isHide; 14 | }, 15 | }; 16 | }; 17 | } 18 | -------------------------------------------------------------------------------- /packages/renderer/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "node": false 5 | }, 6 | "extends": [ 7 | /** @see https://eslint.vuejs.org/rules/ */ 8 | "plugin:vue/vue3-recommended", 9 | "prettier", 10 | "plugin:prettier/recommended" 11 | ], 12 | "rules": { 13 | "vue/no-setup-props-destructure": "off", 14 | "vue/no-v-html": "off", 15 | "vue/multi-word-component-names": "off" 16 | }, 17 | "parserOptions": { 18 | "parser": "@typescript-eslint/parser", 19 | "ecmaVersion": 12, 20 | "sourceType": "module" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /.env.production: -------------------------------------------------------------------------------- 1 | # 是否开启mock 2 | VITE_USE_MOCK = true 3 | 4 | # 网站根目录 5 | VITE_PUBLIC_PATH = ./ 6 | 7 | # 网站前缀 8 | VITE_BASE_URL = / 9 | 10 | # 是否删除console 11 | VITE_DROP_CONSOLE = true 12 | 13 | # API 14 | VITE_GLOB_API_URL = http://localhost:52020 15 | 16 | # 图片上传地址 17 | VITE_GLOB_UPLOAD_URL= 18 | 19 | # 图片前缀地址 20 | VITE_GLOB_IMG_URL= 21 | 22 | # 接口前缀 23 | VITE_GLOB_API_URL_PREFIX = 24 | 25 | # 是否启用gzip压缩或brotli压缩 26 | # 可选: gzip | brotli | none 27 | # 如果你需要多种形式,你可以用','来分隔 28 | VITE_BUILD_COMPRESS = 'none' 29 | 30 | # 使用压缩时是否删除原始文件,默认为false 31 | VITE_BUILD_COMPRESS_DELETE_ORIGIN_FILE = false 32 | -------------------------------------------------------------------------------- /packages/renderer/src/enums/cacheEnum.ts: -------------------------------------------------------------------------------- 1 | // token key 2 | export const TOKEN_KEY = 'TOKEN'; 3 | 4 | // user info key 5 | export const USER_INFO_KEY = 'USER__INFO__'; 6 | 7 | // role info key 8 | export const ROLES_KEY = 'ROLES__KEY__'; 9 | 10 | // project config key 11 | export const PROJ_CFG_KEY = 'PROJ__CFG__KEY__'; 12 | 13 | // lock info 14 | export const LOCK_INFO_KEY = 'LOCK__INFO__KEY__'; 15 | 16 | // base global local key 17 | export const BASE_LOCAL_CACHE_KEY = 'LOCAL__CACHE__KEY__'; 18 | 19 | // base global session key 20 | export const BASE_SESSION_CACHE_KEY = 'SESSION__CACHE__KEY__'; 21 | -------------------------------------------------------------------------------- /packages/main/src/server/components/route.ts: -------------------------------------------------------------------------------- 1 | import { routes as articlelistrule } from './articlelistrule/index'; 2 | import { routes as parse } from './parse/index'; 3 | import { routes as playerposhis } from './playerposhis/index'; 4 | import { routes as viewhistory } from './viewhistory/index'; 5 | 6 | // Manually doing this is required to avoid the compiler giving up and making these values any 7 | const routes = [ 8 | ...Object.values(articlelistrule), 9 | ...Object.values(parse), 10 | ...Object.values(playerposhis), 11 | ...Object.values(viewhistory), 12 | ] as const; 13 | 14 | export default routes; 15 | -------------------------------------------------------------------------------- /packages/renderer/src/settings/designSetting.ts: -------------------------------------------------------------------------------- 1 | // app theme preset color 2 | export const appThemeList: string[] = [ 3 | '#2d8cf0', 4 | '#0960bd', 5 | '#0084f4', 6 | '#009688', 7 | '#536dfe', 8 | '#ff5c93', 9 | '#ee4f12', 10 | '#0096c7', 11 | '#9c27b0', 12 | '#ff9800', 13 | '#FF3D68', 14 | '#00C1D4', 15 | '#71EFA3', 16 | '#171010', 17 | '#78DEC7', 18 | '#1768AC', 19 | '#FB9300', 20 | '#FC5404', 21 | ]; 22 | 23 | const setting = { 24 | //主题模式 25 | themeMode: 'light', 26 | //系统主题色 27 | appTheme: '#2d8cf0', 28 | //系统内置主题色列表 29 | appThemeList, 30 | }; 31 | 32 | export default setting; 33 | -------------------------------------------------------------------------------- /packages/main/src/apis/core/database/sqlite/sync.ts: -------------------------------------------------------------------------------- 1 | import air from '/@/apis/core/air'; 2 | import ArticleListRule from '/@/apis/core/database/sqlite/models/articlelistrule'; 3 | import Viewhistory from '/@/apis/core/database/sqlite/models/viewhistory'; 4 | import Playerposhis from '/@/apis/core/database/sqlite/models/playerposhis'; 5 | 6 | export default async () => { 7 | try { 8 | await Promise.all([ 9 | ArticleListRule.sync({ alter: true }), 10 | Viewhistory.sync({ alter: true }), 11 | Playerposhis.sync({ alter: true }), 12 | ]); 13 | } catch (error: any) { 14 | air.log.error(error); 15 | } 16 | }; 17 | -------------------------------------------------------------------------------- /packages/renderer/src/api/articlelistrule.ts: -------------------------------------------------------------------------------- 1 | import http from '/@/utils/http/axios'; 2 | import * as Models from '#/models'; 3 | import { Articlelistrule as ArticlelistruleParams } from '#/params'; 4 | 5 | // 获取规则列表 6 | export function getArticlelistruleList() { 7 | return http.request({ 8 | url: '/articlelistrule', 9 | method: 'get', 10 | }); 11 | } 12 | 13 | // 导入 14 | export function importRule(params: ArticlelistruleParams.ImportRule) { 15 | return http.request<{ type: PasswordSignType; data: any }>({ 16 | url: '/articlelistrule/import', 17 | method: 'post', 18 | params, 19 | }); 20 | } 21 | -------------------------------------------------------------------------------- /packages/renderer/src/components/AirColComponent/Text3.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 13 | 14 | 23 | -------------------------------------------------------------------------------- /packages/renderer/src/components/AirColComponent/Text4.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 13 | 14 | 23 | -------------------------------------------------------------------------------- /packages/main/src/apis/core/database/sqlite/models/sequelize.ts: -------------------------------------------------------------------------------- 1 | import { dbUrl, dbDir } from '/@/config'; 2 | import { Sequelize } from 'sequelize'; 3 | import fs from 'fs-extra'; 4 | import { join } from 'path'; 5 | 6 | const sequelize = new Sequelize({ 7 | dialect: 'sqlite', 8 | storage: dbUrl, 9 | }); 10 | 11 | // 升级数据库文件 12 | export function upgradeDatabase() { 13 | fs.readdirSync(dbDir).forEach((file) => { 14 | if (/hiker_\d+/g.test(file)) { 15 | fs.moveSync(join(dbDir, file), dbUrl); 16 | } 17 | }); 18 | sequelize.sync(); 19 | } 20 | 21 | !fs.existsSync(dbUrl) && upgradeDatabase(); 22 | 23 | export default sequelize; 24 | -------------------------------------------------------------------------------- /packages/main/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "dist", 5 | "declaration": true, 6 | "removeComments": true, 7 | "emitDecoratorMetadata": true, 8 | "experimentalDecorators": true, 9 | "baseUrl": ".", 10 | "paths": { 11 | "/@/*": [ 12 | "./src/*" 13 | ], 14 | "#/*": [ 15 | "../shared/*" 16 | ] 17 | } 18 | }, 19 | "files": [ 20 | "src/index.ts" 21 | ], 22 | "include": [ 23 | "../../types/**/*.d.ts", 24 | "types/**/*.d.ts", 25 | "../shared/types/**/*.d.ts", 26 | "src/**/*.ts" 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /packages/renderer/src/components/AppProvider/index.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 20 | -------------------------------------------------------------------------------- /packages/renderer/src/styles/global.css: -------------------------------------------------------------------------------- 1 | /*@tailwind base;*/ 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | @layer utilities { 6 | .tw-child-w-full > * { 7 | width: 100%; 8 | } 9 | } 10 | 11 | @font-face { 12 | font-family: TwemojiMozilla; 13 | src: url(/assets/font/TwemojiMozilla.ttf) format('truetype'); 14 | } 15 | 16 | * { 17 | margin: 0; 18 | padding: 0; 19 | user-select: none; 20 | } 21 | 22 | body { 23 | --header-height: 31px; 24 | font-family: v-sans, system-ui, -apple-system, BlinkMacSystemFont, sans-serif, 'Apple Color Emoji', 25 | 'Segoe UI Emoji', 'Segoe UI', 'Segoe UI Symbol', 'TwemojiMozilla'; 26 | } 27 | -------------------------------------------------------------------------------- /packages/main/src/apis/core/air/utils/airVm/encoding.ts: -------------------------------------------------------------------------------- 1 | import CryptoJS from 'crypto-js'; 2 | 3 | export default () => { 4 | return { 5 | base64Encode(src: string) { 6 | return CryptoJS.enc.Utf8.parse(src).toString(CryptoJS.enc.Base64); 7 | }, 8 | base64Decode(src: string) { 9 | return CryptoJS.enc.Base64.parse(src).toString(CryptoJS.enc.Utf8); 10 | }, 11 | aesEncode(key: string, input: string) { 12 | return CryptoJS.AES.encrypt(input, key).toString(); 13 | }, 14 | aesDecode(key: string, input: string) { 15 | return CryptoJS.AES.decrypt(input, key).toString(CryptoJS.enc.Utf8); 16 | }, 17 | }; 18 | }; 19 | -------------------------------------------------------------------------------- /packages/renderer/types/config.d.ts: -------------------------------------------------------------------------------- 1 | interface GlobConfig { 2 | title: string; 3 | apiUrl: string; 4 | shortName: string; 5 | urlPrefix?: string; 6 | uploadUrl?: string; 7 | prodMock: boolean; 8 | imgUrl?: string; 9 | } 10 | 11 | interface GlobEnvConfig { 12 | // 标题 13 | VITE_GLOB_APP_TITLE: string; 14 | // 接口地址 15 | VITE_GLOB_API_URL: string; 16 | // 接口前缀 17 | VITE_GLOB_API_URL_PREFIX?: string; 18 | // Project abbreviation 19 | VITE_GLOB_APP_SHORT_NAME: string; 20 | // 图片上传地址 21 | VITE_GLOB_UPLOAD_URL?: string; 22 | //图片前缀地址 23 | VITE_GLOB_IMG_URL?: string; 24 | //生产环境开启mock 25 | VITE_GLOB_PROD_MOCK: boolean; 26 | } 27 | -------------------------------------------------------------------------------- /vetur.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('vls').VeturConfig} */ 2 | module.exports = { 3 | settings: { 4 | 'vetur.useWorkspaceDependencies': true, 5 | 'vetur.experimental.templateInterpolationService': true, 6 | }, 7 | projects: [ 8 | { 9 | root: './packages/renderer', 10 | tsconfig: './tsconfig.json', 11 | snippetFolder: './.vscode/vetur/snippets', 12 | globalComponents: ['./src/components/**/*.vue'], 13 | }, 14 | { 15 | root: './packages/main', 16 | tsconfig: './tsconfig.json', 17 | }, 18 | { 19 | root: './packages/preload', 20 | tsconfig: './tsconfig.json', 21 | }, 22 | ], 23 | }; 24 | -------------------------------------------------------------------------------- /packages/renderer/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "jsx": "preserve", 5 | "baseUrl": ".", 6 | "paths": { 7 | "/@/*": ["./src/*"], 8 | "#/*": ["../shared/*"], 9 | "~assets/*": ["./assets/*"], 10 | "#main/*": ["../main/src/*"] 11 | }, 12 | "typeRoots": ["./types"], 13 | "lib": ["ESNext", "dom", "dom.iterable"] 14 | }, 15 | "include": [ 16 | "src/**/*.vue", 17 | "src/**/*.ts", 18 | "src/**/*.tsx", 19 | "types/**/*.d.ts", 20 | "../../types/**/*.d.ts", 21 | "../preload/exposedInMainWorld.d.ts", 22 | "../shared/types/**/*.d.ts" 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /packages/renderer/build/vite/plugin/styleImport.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Introduces component library styles on demand. 3 | * https://github.com/anncwb/vite-plugin-style-import 4 | */ 5 | 6 | import styleImport from 'vite-plugin-style-import'; 7 | 8 | export function configStyleImportPlugin(isBuild: boolean) { 9 | if (!isBuild) return []; 10 | const styleImportPlugin = styleImport({ 11 | libs: [ 12 | { 13 | libraryName: 'ant-design-vue', 14 | esModule: true, 15 | resolveStyle: (name) => { 16 | return `ant-design-vue/es/${name}/style/index`; 17 | }, 18 | }, 19 | ], 20 | }); 21 | return styleImportPlugin; 22 | } 23 | -------------------------------------------------------------------------------- /packages/main/public/vm/script.js: -------------------------------------------------------------------------------- 1 | // function putVar(key, value) { 2 | // AIR_VARS[key] = value; 3 | // } 4 | // 5 | // function putVar2(key, value) { 6 | // AIR_VARS[key] = value; 7 | // } 8 | // 9 | // function getVar(key, defaultValue) { 10 | // return AIR_VARS[key] || defaultValue; 11 | // } 12 | 13 | function getResCode() { 14 | return AIR_RESCODE; 15 | } 16 | 17 | function setHomeResult(value) { 18 | AIR_RESULT.data = (value && value.data) || value; 19 | } 20 | 21 | function setSearchResult(value) { 22 | AIR_RESULT.data = (value && value.data) || value; 23 | } 24 | 25 | function setResult(value) { 26 | AIR_RESULT.data = (value && value.data) || value; 27 | } 28 | -------------------------------------------------------------------------------- /packages/renderer/build/script/postBuild.ts: -------------------------------------------------------------------------------- 1 | // #!/usr/bin/env node 2 | 3 | import { runBuildConfig } from './buildConf'; 4 | import chalk from 'chalk'; 5 | 6 | import pkg from '../../../../package.json'; 7 | 8 | export const runBuild = async () => { 9 | try { 10 | const argvList = process.argv.splice(2); 11 | 12 | // Generate configuration file 13 | if (!argvList.includes('disabled-config')) { 14 | await runBuildConfig(); 15 | } 16 | 17 | console.log(`✨ ${chalk.cyan(`[${pkg.name}]`)}` + ' - build successfully!'); 18 | } catch (error) { 19 | console.log(chalk.red('vite build error:\n' + error)); 20 | process.exit(1); 21 | } 22 | }; 23 | runBuild(); 24 | -------------------------------------------------------------------------------- /packages/renderer/src/components/AirColComponent/Text5.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 14 | 15 | 24 | -------------------------------------------------------------------------------- /packages/renderer/src/plugins/artplayer-plugin-danmuku/src/i18n.js: -------------------------------------------------------------------------------- 1 | export default { 2 | 'zh-cn': { 3 | 'Danmu opacity': '弹幕透明度', 4 | 'Danmu speed': '弹幕速度', 5 | 'Danmu size': '弹幕大小', 6 | 'Danmu text cannot be empty': '弹幕文本不能为空', 7 | 'The length of the danmu does not exceed': '弹幕文本字数不能超过', 8 | 'Danmu speed synchronous playback multiple': '弹幕速度同步播放倍数', 9 | }, 10 | 'zh-tw': { 11 | 'Danmu opacity': '彈幕透明度', 12 | 'Danmu speed': '彈幕速度', 13 | 'Danmu size': '弹幕大小', 14 | 'Danmu text cannot be empty': '彈幕文本不能為空', 15 | 'The length of the danmu does not exceed': '彈幕文本字數不能超過', 16 | 'Danmu speed synchronous playback multiple': '彈幕速度同步播放倍數', 17 | }, 18 | }; 19 | -------------------------------------------------------------------------------- /packages/renderer/src/enums/breakpointEnum.ts: -------------------------------------------------------------------------------- 1 | export enum sizeEnum { 2 | XS = 'XS', 3 | SM = 'SM', 4 | MD = 'MD', 5 | LG = 'LG', 6 | XL = 'XL', 7 | XXL = 'XXL', 8 | } 9 | 10 | export enum screenEnum { 11 | XS = 480, 12 | SM = 576, 13 | MD = 768, 14 | LG = 992, 15 | XL = 1200, 16 | XXL = 1600, 17 | } 18 | 19 | const screenMap = new Map(); 20 | 21 | screenMap.set(sizeEnum.XS, screenEnum.XS); 22 | screenMap.set(sizeEnum.SM, screenEnum.SM); 23 | screenMap.set(sizeEnum.MD, screenEnum.MD); 24 | screenMap.set(sizeEnum.LG, screenEnum.LG); 25 | screenMap.set(sizeEnum.XL, screenEnum.XL); 26 | screenMap.set(sizeEnum.XXL, screenEnum.XXL); 27 | 28 | export { screenMap }; 29 | -------------------------------------------------------------------------------- /packages/shared/models/ViewHistory.ts: -------------------------------------------------------------------------------- 1 | import type { Static } from '@sinclair/typebox'; 2 | import { Type } from '@sinclair/typebox'; 3 | 4 | export const ViewHistory = Type.Object( 5 | { 6 | id: Type.Number(), 7 | lastclick: Type.String(), 8 | params: Type.String(), 9 | rulebaseurl: Type.String(), 10 | time: Type.Number(), 11 | title: Type.String(), 12 | type: Type.String(), 13 | url: Type.String(), 14 | videourl: Type.String(), 15 | group_lpcolumn: Type.String(), 16 | picurl: Type.String(), 17 | extradata: Type.String(), 18 | }, 19 | { $id: 'ViewHistory', title: 'ViewHistory', description: 'ViewHistory' } 20 | ); 21 | 22 | export type ViewHistory = Static; 23 | -------------------------------------------------------------------------------- /packages/renderer/src/components/AirProvider/src/context.ts: -------------------------------------------------------------------------------- 1 | import { InjectionKey } from 'vue'; 2 | import { DetailPanelOption } from '/@/components/DetailPanel/DetailPanel.vue'; 3 | import { Object } from 'ts-toolbelt'; 4 | import * as Models from '#/models'; 5 | 6 | export interface AirApiInjection { 7 | showViewHistory: () => void; 8 | showDetailPanel: (option: DetailPanelOption) => void; 9 | showPlayerPanel: (title: string, url: string, item: HikerResultOption, otherOptions: any) => void; 10 | showSearchPanel: ( 11 | articlelistrule: Object.Optional, 12 | value: string 13 | ) => void; 14 | } 15 | 16 | export const airApiInjectionKey: InjectionKey = Symbol('airApiInjection'); 17 | -------------------------------------------------------------------------------- /packages/renderer/src/assets/svg/indicator.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /packages/renderer/src/components/AirColComponent/IconSmall3.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 20 | 21 | 26 | -------------------------------------------------------------------------------- /packages/renderer/src/utils/urlUtils.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 将对象添加当作参数拼接到URL上面 3 | * @param baseUrl 需要拼接的url 4 | * @param obj 参数对象 5 | * @returns {string} 拼接后的对象 6 | * 例子: 7 | * let obj = {a: '3', b: '4'} 8 | * setObjToUrlParams('www.baidu.com', obj) 9 | * ==>www.baidu.com?a=3&b=4 10 | */ 11 | export function setObjToUrlParams(baseUrl: string, obj: object): string { 12 | let parameters = ''; 13 | let url = ''; 14 | for (const key in obj) { 15 | parameters += key + '=' + encodeURIComponent(obj[key]) + '&'; 16 | } 17 | parameters = parameters.replace(/&$/, ''); 18 | if (/\?$/.test(baseUrl)) { 19 | url = baseUrl + parameters; 20 | } else { 21 | url = baseUrl.replace(/\/?$/, '?') + parameters; 22 | } 23 | return url; 24 | } 25 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | # Commented sections below can be used to run tests on the CI server 2 | # https://simulatedgreg.gitbooks.io/electron-vue/content/en/testing.html#on-the-subject-of-ci-testing 3 | version: 0.1.{build} 4 | 5 | branches: 6 | only: 7 | - main 8 | 9 | image: Visual Studio 2017 10 | platform: 11 | - x64 12 | 13 | cache: 14 | - node_modules 15 | - '%APPDATA%\npm-cache' 16 | - '%USERPROFILE%\.electron' 17 | - '%USERPROFILE%\AppData\Local\Yarn\cache' 18 | 19 | init: 20 | - git config --global core.autocrlf input 21 | 22 | install: 23 | - ps: Install-Product node 16 x64 24 | - git reset --hard HEAD 25 | - yarn 26 | - node --version 27 | 28 | build_script: 29 | #- yarn test 30 | - npm run compile:release 31 | 32 | test: off 33 | -------------------------------------------------------------------------------- /packages/main/src/apis/core/utils/password.ts: -------------------------------------------------------------------------------- 1 | import { CLOUD_SHEAR_PLATE_MAP, PASSWORD_SIGN } from '#/config'; 2 | 3 | export function getPasswordRuleType(password: string): PasswordSignType { 4 | for (const type in PASSWORD_SIGN) { 5 | const reg = new RegExp(`海阔视界.*${PASSWORD_SIGN[type].sign}.+`, 'g'); 6 | if (reg.test(password)) { 7 | return type as PasswordSignType; 8 | } 9 | } 10 | throw new Error('口令无法识别,请确认规则是否正确!'); 11 | } 12 | 13 | export function isCloudShearPlate(password: string): boolean { 14 | for (const type in CLOUD_SHEAR_PLATE_MAP) { 15 | const reg = new RegExp(CLOUD_SHEAR_PLATE_MAP[type].validator, 'g'); 16 | if (reg.test(password.split('\n')[0])) { 17 | return true; 18 | } 19 | } 20 | return false; 21 | } 22 | -------------------------------------------------------------------------------- /packages/renderer/src/hooks/socket/index.ts: -------------------------------------------------------------------------------- 1 | import { io, Socket } from 'socket.io-client'; 2 | import { useGlobSetting } from '/@/hooks/setting'; 3 | import { nanoid } from 'nanoid'; 4 | 5 | export function useSocket(): { 6 | socket: Socket; 7 | id: string; 8 | } { 9 | if (!(window as any).$socket || !(window as any).$socketGroupId) { 10 | const globSetting = useGlobSetting(); 11 | 12 | (window as any).$socketGroupId = nanoid(); 13 | 14 | (window as any).$socket = io(globSetting.apiUrl, { 15 | transports: ['websocket'], 16 | }); 17 | 18 | (window as any).$socket.emit('addGroup', (window as any).$socketGroupId); 19 | } 20 | 21 | return { 22 | socket: (window as any).$socket, 23 | id: (window as any).$socketGroupId, 24 | }; 25 | } 26 | -------------------------------------------------------------------------------- /packages/shared/events/constants.ts: -------------------------------------------------------------------------------- 1 | export const AIR_GET_CONFIG = 'AIR_GET_CONFIG'; 2 | export const AIR_SAVE_CONFIG = 'AIR_SAVE_CONFIG'; 3 | export const MINIMIZE_WINDOW = 'MINIMIZE_WINDOW'; 4 | export const MAX_RESTORE_WINDOW = 'MAX_RESTORE_WINDOW'; 5 | export const IS_MAXIMIZE_WINDOW = 'IS_MAXIMIZE_WINDOW'; 6 | export const CLOSE_WINDOW = 'CLOSE_WINDOW'; 7 | export const UPDATE_REQUEST_HEADERS = 'UPDATE_REQUEST_HEADERS'; 8 | export const GET_FILE_CONTENT = 'GET_FILE_CONTENT'; 9 | export const WEBDAV_SYNC = 'WEBDAV_SYNC'; 10 | export const AIR_OPEN_FILE = 'AIR_OPEN_FILE'; 11 | export const IMPORT_BACKUP = 'IMPORT_BACKUP'; 12 | export const SET_DEFAULT_HEADERS = 'SET_DEFAULT_HEADERS'; 13 | 14 | /** 15 | * webview 使用 16 | */ 17 | export const WEBVIEW_PLAY_VIDEO = 'WEBVIEW_PLAY_VIDEO'; 18 | -------------------------------------------------------------------------------- /packages/renderer/src/enums/httpEnum.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description: 请求结果集 3 | */ 4 | export enum ResultEnum { 5 | SUCCESS = 200, 6 | ERROR = -1, 7 | TIMEOUT = 10042, 8 | TYPE = 'success', 9 | } 10 | 11 | /** 12 | * @description: 请求方法 13 | */ 14 | export enum RequestEnum { 15 | GET = 'GET', 16 | POST = 'POST', 17 | PATCH = 'PATCH', 18 | PUT = 'PUT', 19 | DELETE = 'DELETE', 20 | } 21 | 22 | /** 23 | * @description: 常用的contentTyp类型 24 | */ 25 | export enum ContentTypeEnum { 26 | // json 27 | JSON = 'application/json;charset=UTF-8', 28 | // json 29 | TEXT = 'text/plain;charset=UTF-8', 30 | // form-data 一般配合qs 31 | FORM_URLENCODED = 'application/x-www-form-urlencoded;charset=UTF-8', 32 | // form-data 上传 33 | FORM_DATA = 'multipart/form-data;charset=UTF-8', 34 | } 35 | -------------------------------------------------------------------------------- /packages/renderer/src/components/AirColComponent/Text2.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 15 | 16 | 25 | -------------------------------------------------------------------------------- /packages/main/src/apis/core/air/utils/common.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * the config black item list which won't be setted 3 | * only can be got 4 | */ 5 | export const configBlackList = []; 6 | 7 | /** 8 | * check some config key is in blackList 9 | * @param key 10 | */ 11 | export const isConfigKeyInBlackList = (key: string): boolean => { 12 | return configBlackList.some((blackItem) => key.startsWith(blackItem)); 13 | }; 14 | 15 | /** 16 | * check the input config is valid 17 | * config must be object such as { xxx: 'xxx' } 18 | * && can't be array 19 | * @param config 20 | * @returns 21 | */ 22 | export const isInputConfigValid = (config: any): boolean => { 23 | if (typeof config === 'object' && !Array.isArray(config) && Object.keys(config).length > 0) { 24 | return true; 25 | } 26 | return false; 27 | }; 28 | -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | 15 | 16 | -------------------------------------------------------------------------------- /packages/renderer/src/router/index.ts: -------------------------------------------------------------------------------- 1 | import { createRouter, createWebHashHistory } from 'vue-router'; 2 | import Home from '/@/views/home/index.vue'; 3 | import Setting from '/@/views/setting/index.vue'; 4 | import Layout from '/@/layout/index.vue'; 5 | import ApiDocument from '/@/views/apiDocument/index.vue'; 6 | 7 | const routes = [ 8 | { 9 | path: '/', 10 | component: Layout, 11 | redirect: '/home', 12 | children: [ 13 | { path: 'home', name: 'home', component: Home }, 14 | { path: 'setting', name: 'setting', component: Setting }, 15 | { path: 'apiDocument', name: 'apiDocument', component: ApiDocument }, 16 | ], 17 | meta: { 18 | index: 0, 19 | }, 20 | }, 21 | ]; 22 | 23 | export default createRouter({ 24 | routes, 25 | history: createWebHashHistory(), 26 | }); 27 | -------------------------------------------------------------------------------- /packages/shared/types/electron.d.ts: -------------------------------------------------------------------------------- 1 | // https://stackoverflow.com/questions/45420448/how-to-import-external-type-into-global-d-ts-file 2 | declare type BrowserWindow = import('electron').BrowserWindow; 3 | declare type IWindowList = import('./enum').IWindowList; 4 | 5 | declare interface IWindowListItem { 6 | isValid: boolean; 7 | multiple: boolean; 8 | options: () => Electron.BrowserWindowConstructorOptions; 9 | callback: (window: BrowserWindow, windowManager: IWindowManager) => void; 10 | } 11 | 12 | declare interface IWindowManager { 13 | create: (name: IWindowList) => BrowserWindow | null; 14 | get: (name: IWindowList) => BrowserWindow | null; 15 | has: (name: IWindowList) => boolean; 16 | // delete: (name: IWindowList) => void 17 | deleteById: (id: number) => void; 18 | getAvailableWindow: () => BrowserWindow; 19 | } 20 | -------------------------------------------------------------------------------- /packages/renderer/src/components/AirColComponent/Text1.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 15 | 16 | 27 | -------------------------------------------------------------------------------- /packages/main/src/apis/core/database/sqlite/models/playerposhis.ts: -------------------------------------------------------------------------------- 1 | import { DataTypes, InferAttributes, Model } from 'sequelize'; 2 | import sequelize from '/@/apis/core/database/sqlite/models/sequelize'; 3 | 4 | class Playerposhis extends Model> { 5 | declare id?: number; 6 | declare playurl: string; 7 | declare pos: number; 8 | } 9 | 10 | Playerposhis.init( 11 | { 12 | id: { 13 | type: DataTypes.INTEGER, 14 | allowNull: true, 15 | primaryKey: true, 16 | autoIncrement: true, 17 | }, 18 | playurl: { 19 | type: DataTypes.TEXT, 20 | allowNull: true, 21 | }, 22 | pos: { 23 | type: DataTypes.INTEGER, 24 | allowNull: true, 25 | }, 26 | }, 27 | { 28 | sequelize, 29 | tableName: 'playerposhis', 30 | timestamps: false, 31 | } 32 | ); 33 | 34 | export default Playerposhis; 35 | -------------------------------------------------------------------------------- /packages/renderer/src/components/AirColComponent/X5WebviewSingle.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 30 | -------------------------------------------------------------------------------- /.idea/deployment.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /packages/shared/types/types.d.ts: -------------------------------------------------------------------------------- 1 | // global 2 | interface IObj { 3 | [propName: string]: any; 4 | } 5 | 6 | // Main process 7 | interface IBrowserWindowOptions { 8 | height: number; 9 | width: number; 10 | show: boolean; 11 | fullscreenable: boolean; 12 | resizable: boolean; 13 | webPreferences: { 14 | webviewTag: boolean; 15 | nodeIntegrationInWorker: boolean; 16 | contextIsolation: boolean; 17 | backgroundThrottling: boolean; 18 | webSecurity?: boolean; 19 | }; 20 | vibrancy?: string | any; 21 | frame?: boolean; 22 | center?: boolean; 23 | title?: string; 24 | titleBarStyle?: string | any; 25 | backgroundColor?: string; 26 | autoHideMenuBar?: boolean; 27 | transparent?: boolean; 28 | icon?: string; 29 | skipTaskbar?: boolean; 30 | alwaysOnTop?: boolean; 31 | } 32 | 33 | type ILogArgvType = string | number; 34 | 35 | type ILogArgvTypeWithError = ILogArgvType | Error; 36 | -------------------------------------------------------------------------------- /packages/renderer/src/components/AirColComponent/Avatar.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 17 | 18 | 32 | -------------------------------------------------------------------------------- /packages/renderer/src/components/AirColComponent/Icon2Round.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 18 | 19 | 29 | -------------------------------------------------------------------------------- /packages/renderer/src/components/AirColComponent/IconRoundSmall4.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 21 | 22 | 35 | -------------------------------------------------------------------------------- /packages/renderer/build/vite/proxy.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Used to parse the .env.development proxy configuration 3 | */ 4 | import type { ProxyOptions } from 'vite'; 5 | 6 | type ProxyItem = [string, string]; 7 | 8 | type ProxyList = ProxyItem[]; 9 | 10 | type ProxyTargetList = Record string }>; 11 | 12 | const httpsRE = /^https:\/\//; 13 | 14 | /** 15 | * Generate proxy 16 | * @param list 17 | */ 18 | export function createProxy(list: ProxyList = []) { 19 | const ret: ProxyTargetList = {}; 20 | for (const [prefix, target] of list) { 21 | const isHttps = httpsRE.test(target); 22 | 23 | // https://github.com/http-party/node-http-proxy#options 24 | ret[prefix] = { 25 | target: target, 26 | changeOrigin: true, 27 | ws: true, 28 | rewrite: (path) => path.replace(new RegExp(`^${prefix}`), ''), 29 | // https is require secure=false 30 | ...(isHttps ? { secure: false } : {}), 31 | }; 32 | } 33 | return ret; 34 | } 35 | -------------------------------------------------------------------------------- /packages/renderer/src/components/Artplayer/Artplayer.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 45 | -------------------------------------------------------------------------------- /packages/renderer/src/store/modules/search.ts: -------------------------------------------------------------------------------- 1 | import { defineStore } from 'pinia'; 2 | import storage from '/@/utils/storage'; 3 | import { union } from 'lodash'; 4 | 5 | export const searchHistoryKey = 'searchHistory'; 6 | 7 | interface SearchStore { 8 | historyList: string[]; 9 | } 10 | 11 | const useSearchStore = defineStore({ 12 | id: 'air-search', 13 | state: (): SearchStore => ({ 14 | historyList: storage.get(searchHistoryKey, []), 15 | }), 16 | getters: { 17 | getHistoryList: (state) => state.historyList, 18 | }, 19 | actions: { 20 | addHistory(keyWord): void { 21 | if (this.historyList.length === 100) { 22 | this.historyList.pop(); 23 | } 24 | this.historyList = union([keyWord], this.historyList); 25 | storage.set(searchHistoryKey, this.historyList); 26 | }, 27 | deleteHistoryList(): void { 28 | this.historyList = []; 29 | storage.set(searchHistoryKey, this.historyList); 30 | }, 31 | }, 32 | }); 33 | 34 | export default useSearchStore; 35 | -------------------------------------------------------------------------------- /packages/shared/models/Articlelistrule.ts: -------------------------------------------------------------------------------- 1 | import type { Static } from '@sinclair/typebox'; 2 | import { Type } from '@sinclair/typebox'; 3 | 4 | export const Articlelistrule = Type.Object( 5 | { 6 | id: Type.Number(), 7 | title: Type.String(), 8 | icon: Type.String(), 9 | col_type: Type.String(), 10 | detail_col_type: Type.String(), 11 | detail_find_rule: Type.String(), 12 | find_rule: Type.String(), 13 | firstheader: Type.String(), 14 | group_lpcolumn: Type.String(), 15 | prerule: Type.String(), 16 | sdetail_col_type: Type.String(), 17 | sdetail_find_rule: Type.String(), 18 | searchfind: Type.String(), 19 | search_url: Type.String(), 20 | sort_name: Type.String(), 21 | sort_url: Type.String(), 22 | titlecolor: Type.String(), 23 | ua: Type.String(), 24 | url: Type.String(), 25 | }, 26 | { $id: 'Articlelistrule', title: 'Articlelistrule', description: 'Articlelistrule' } 27 | ); 28 | 29 | export type Articlelistrule = Static; 30 | -------------------------------------------------------------------------------- /packages/main/src/apis/core/air/utils/require.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs-extra'; 2 | import dayjs from 'dayjs'; 3 | 4 | export function getRequireData(path: string): { 5 | url: string; 6 | file?: string; 7 | pcFile: string; 8 | accessTime: number; 9 | }[] { 10 | if (!fs.existsSync(path)) { 11 | fs.ensureFileSync(path); 12 | } 13 | let data = []; 14 | try { 15 | data = fs.readJSONSync(path, 'utf8'); 16 | } catch (e) {} 17 | return data; 18 | } 19 | 20 | export function addOrUpdateRequireData(path: string, url: string, pcFile: string) { 21 | const data: { 22 | url: string; 23 | file?: string; 24 | pcFile: string; 25 | accessTime: number; 26 | }[] = getRequireData(path); 27 | const index = data.findIndex((item) => item.url === url); 28 | if (index > -1) { 29 | data[index].accessTime = dayjs().valueOf(); 30 | data[index].pcFile = pcFile; 31 | } else { 32 | data.push({ url, pcFile, accessTime: dayjs().valueOf() }); 33 | } 34 | fs.writeJSONSync(path, data, { spaces: 2 }); 35 | } 36 | -------------------------------------------------------------------------------- /packages/renderer/build/vite/plugin/compress.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Used to package and output gzip. Note that this does not work properly in Vite, the specific reason is still being investigated 3 | * https://github.com/anncwb/vite-plugin-compression 4 | */ 5 | import type { Plugin } from 'vite'; 6 | 7 | import compressPlugin from 'vite-plugin-compression'; 8 | 9 | export function configCompressPlugin( 10 | compress: 'gzip' | 'brotli' | 'none', 11 | deleteOriginFile = false 12 | ): Plugin | Plugin[] { 13 | const compressList = compress.split(','); 14 | 15 | const plugins: Plugin[] = []; 16 | 17 | if (compressList.includes('gzip')) { 18 | plugins.push( 19 | compressPlugin({ 20 | ext: '.gz', 21 | deleteOriginFile, 22 | }) 23 | ); 24 | } 25 | if (compressList.includes('brotli')) { 26 | plugins.push( 27 | compressPlugin({ 28 | ext: '.br', 29 | algorithm: 'brotliCompress', 30 | deleteOriginFile, 31 | }) 32 | ); 33 | } 34 | return plugins; 35 | } 36 | -------------------------------------------------------------------------------- /packages/main/src/server/hooks.ts: -------------------------------------------------------------------------------- 1 | import { FastifyInstance } from 'fastify'; 2 | import air from '/@/apis/core/air'; 3 | 4 | export default function addHooks(fastify: FastifyInstance): void { 5 | fastify.ready(async (error) => { 6 | if (error) { 7 | throw error; 8 | } 9 | 10 | try { 11 | fastify.swagger(); 12 | } catch (error: unknown) { 13 | air.log.error('Fastify error', error as string); 14 | } 15 | }); 16 | 17 | fastify.addHook('onRequest', async (request) => { 18 | if (request.is404) { 19 | return; 20 | } 21 | 22 | if (request.headers['socket-group-id']) { 23 | (request as any).session.socketGroupId = request.headers['socket-group-id']; 24 | } 25 | 26 | const requestName = `${request.routerMethod} ${request.routerPath}`; 27 | 28 | const requestContext = { 29 | body: request.body, 30 | params: request.params, 31 | query: request.query, 32 | }; 33 | 34 | air.log.info(request.id, requestName, requestContext as any); 35 | }); 36 | } 37 | -------------------------------------------------------------------------------- /packages/renderer/src/api/viewhistory.ts: -------------------------------------------------------------------------------- 1 | import http from '/@/utils/http/axios'; 2 | import { ViewHistory as ViewHistoryQuery } from '#/params'; 3 | import * as Model from '#/models'; 4 | 5 | export function all(params: ViewHistoryQuery.All) { 6 | return http.request<{ rows: Model.ViewHistory[]; count: number }>({ 7 | url: '/viewhistory/all', 8 | method: 'get', 9 | params, 10 | }); 11 | } 12 | 13 | export function getViewHistory(params: ViewHistoryQuery.GetLastclick) { 14 | return http.request({ 15 | url: '/viewhistory/getViewHistory', 16 | method: 'get', 17 | params, 18 | }); 19 | } 20 | 21 | export function record(params: ViewHistoryQuery.Record) { 22 | return http.request({ 23 | url: '/viewhistory/record', 24 | method: 'put', 25 | data: params, 26 | }); 27 | } 28 | 29 | export function recordClick(params: ViewHistoryQuery.RecordClick) { 30 | return http.request({ 31 | url: '/viewhistory/recordClick', 32 | method: 'put', 33 | data: params, 34 | }); 35 | } 36 | -------------------------------------------------------------------------------- /.idea/vite-electron-builder.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /packages/main/src/server/components/articlelistrule/routes/articlelistrule.ts: -------------------------------------------------------------------------------- 1 | import type { RouteOptions } from 'fastify'; 2 | import ArticleListRule from '/@/apis/core/database/sqlite/models/articlelistrule'; 3 | import { RawReplyDefaultExpression, RawRequestDefaultExpression, RawServerDefault } from 'fastify'; 4 | import { Http } from '@jonahsnider/util'; 5 | import * as Models from '#/models'; 6 | 7 | export default function getRoute() { 8 | const route: RouteOptions< 9 | RawServerDefault, 10 | RawRequestDefaultExpression, 11 | RawReplyDefaultExpression, 12 | { 13 | Reply: Models.Articlelistrules; 14 | } 15 | > = { 16 | url: '/articlelistrule', 17 | method: 'GET', 18 | schema: { 19 | response: { 20 | [Http.Status.Ok]: Models.Articlelistrules, 21 | }, 22 | }, 23 | handler: async () => { 24 | return await ArticleListRule.findAll(); 25 | }, 26 | errorHandler: (error) => { 27 | console.error(error); 28 | return error; 29 | }, 30 | }; 31 | 32 | return route; 33 | } 34 | -------------------------------------------------------------------------------- /packages/renderer/src/components/AirColComponent/TextCenter1.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 19 | 20 | 32 | -------------------------------------------------------------------------------- /packages/main/src/apis/core/utils/localLogger.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs-extra'; 2 | import dayjs from 'dayjs'; 3 | import util from 'util'; 4 | 5 | /** 6 | * for local log before picgo inited 7 | */ 8 | const getLogger = (logPath: string) => { 9 | if (!fs.existsSync(logPath)) { 10 | fs.ensureFileSync(logPath); 11 | } 12 | return (type: string, ...msg: any[]) => { 13 | let log = `${dayjs().format('YYYY-MM-DD HH:mm:ss')} [PicGo ${type.toUpperCase()}] `; 14 | msg.forEach((item: ILogArgvTypeWithError) => { 15 | if (typeof item === 'object' && type === 'error') { 16 | log += `\n------Error Stack Begin------\n${util.format( 17 | item.stack 18 | )}\n-------Error Stack End------- `; 19 | } else { 20 | if (typeof item === 'object') { 21 | item = JSON.stringify(item); 22 | } 23 | log += `${item} `; 24 | } 25 | }); 26 | log += '\n'; 27 | // A synchronized approach to avoid log msg sequence errors 28 | fs.appendFileSync(logPath, log); 29 | }; 30 | }; 31 | 32 | export { getLogger }; 33 | -------------------------------------------------------------------------------- /packages/main/src/apis/core/utils/recordViewHistory.ts: -------------------------------------------------------------------------------- 1 | import Viewhistory from '/@/apis/core/database/sqlite/models/viewhistory'; 2 | import dayjs from 'dayjs'; 3 | import { Attributes } from 'sequelize/types/model'; 4 | 5 | export interface RecordViewHistoryOption { 6 | type: '二级列表' | '网页浏览'; 7 | title: string; 8 | url: string; 9 | rulebaseurl?: string; 10 | } 11 | 12 | export async function recordViewHistory({ 13 | type = '二级列表', 14 | title, 15 | url, 16 | rulebaseurl = '', 17 | }: RecordViewHistoryOption) { 18 | const insertData: Attributes = { 19 | type, 20 | title, 21 | url, 22 | rulebaseurl, 23 | params: '', 24 | videourl: '', 25 | group_lpcolumn: '', 26 | picurl: '', 27 | extradata: '', 28 | lastclick: '', 29 | time: dayjs().valueOf(), 30 | }; 31 | 32 | const [viewHistory, create] = await Viewhistory.findOrCreate({ 33 | where: { 34 | title, 35 | url, 36 | }, 37 | defaults: insertData, 38 | }); 39 | 40 | if (!create) { 41 | viewHistory.update(insertData); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /packages/renderer/src/utils/http/axios/types.ts: -------------------------------------------------------------------------------- 1 | import { AxiosRequestConfig } from 'axios'; 2 | import { AxiosTransform } from './axiosTransform'; 3 | 4 | export interface CreateAxiosOptions extends AxiosRequestConfig { 5 | prefixUrl?: string; 6 | transform?: AxiosTransform; 7 | requestOptions?: RequestOptions; 8 | } 9 | 10 | export interface RequestOptions { 11 | // 请求参数拼接到url 12 | joinParamsToUrl?: boolean; 13 | // 格式化请求参数时间 14 | formatDate?: boolean; 15 | // 是否显示提示信息 16 | isShowMessage?: boolean; 17 | // 是否解析成JSON 18 | isParseToJson?: boolean; 19 | // 成功的文本信息 20 | successMessageText?: string; 21 | // 是否显示成功信息 22 | isShowSuccessMessage?: boolean; 23 | // 是否显示失败信息 24 | isShowErrorMessage?: boolean; 25 | // 错误的文本信息 26 | errorMessageText?: string; 27 | // 是否加入url 28 | joinPrefix?: boolean; 29 | // 接口地址, 不填则使用默认apiUrl 30 | apiUrl?: string; 31 | // 错误消息提示类型 32 | errorMessageMode?: 'none' | 'modal'; 33 | // 是否添加时间戳 34 | joinTime?: boolean; 35 | // 不进行任何处理,直接返回 36 | isTransformResponse?: boolean; 37 | // 是否返回原生响应头 38 | isReturnNativeResponse?: boolean; 39 | } 40 | -------------------------------------------------------------------------------- /packages/renderer/src/hooks/setting/index.ts: -------------------------------------------------------------------------------- 1 | import { warn } from '/@/utils/log'; 2 | import { getAppEnvConfig } from '/@/utils/env'; 3 | 4 | export const useGlobSetting = (): Readonly => { 5 | const { 6 | VITE_GLOB_APP_TITLE, 7 | VITE_GLOB_API_URL, 8 | VITE_GLOB_APP_SHORT_NAME, 9 | VITE_GLOB_API_URL_PREFIX, 10 | VITE_GLOB_UPLOAD_URL, 11 | VITE_GLOB_PROD_MOCK, 12 | VITE_GLOB_IMG_URL, 13 | } = getAppEnvConfig(); 14 | 15 | if (!/[a-zA-Z\_]*/.test(VITE_GLOB_APP_SHORT_NAME)) { 16 | warn( 17 | `VITE_GLOB_APP_SHORT_NAME Variables can only be characters/underscores, please modify in the environment variables and re-running.` 18 | ); 19 | } 20 | 21 | // Take global configuration 22 | const glob: Readonly = { 23 | title: VITE_GLOB_APP_TITLE, 24 | apiUrl: VITE_GLOB_API_URL, 25 | shortName: VITE_GLOB_APP_SHORT_NAME, 26 | urlPrefix: VITE_GLOB_API_URL_PREFIX, 27 | uploadUrl: VITE_GLOB_UPLOAD_URL, 28 | prodMock: VITE_GLOB_PROD_MOCK, 29 | imgUrl: VITE_GLOB_IMG_URL, 30 | }; 31 | return glob as Readonly; 32 | }; 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 LingYan 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 | -------------------------------------------------------------------------------- /packages/preload/exposedInMainWorld.d.ts: -------------------------------------------------------------------------------- 1 | interface Window { 2 | /** 3 | * Expose Environment versions. 4 | * @example 5 | * console.log( window.versions ) 6 | */ 7 | readonly versions: NodeJS.ProcessVersions; 8 | /** 9 | * Safe expose node.js API 10 | * @example 11 | * window.nodeCrypto('data') 12 | */ 13 | readonly nodeCrypto: { sha256sum(data: import("crypto").BinaryLike): string; }; 14 | /** 15 | * Safe expose node.js API 16 | * axios.defaults.adapter = http 17 | */ 18 | readonly http: any; 19 | readonly preloadPath: string; 20 | /** 打开浏览器 */ 21 | readonly openBrowser: (url: string) => void; 22 | readonly api: { send: (channel: any, ...args: any[]) => void; sendSync: (channel: any, ...args: any[]) => void; receive: (channel: any, func: any) => void; invoke: (channel: any, ...args: any[]) => Promise; on: (channel: any, func: any) => void; removeListener: (channel: string, listener: (...args: any[]) => void) => void; }; 23 | readonly log: (...args: any[]) => void; 24 | readonly sanitizeHtml: { sanitizeHtml: any; defaults: any; }; 25 | } 26 | -------------------------------------------------------------------------------- /packages/shared/params/viewhistory/Record.ts: -------------------------------------------------------------------------------- 1 | import { Static, Type } from '@sinclair/typebox'; 2 | import { ViewHistoryType } from '../../enums'; 3 | 4 | export const Record = Type.Object({ 5 | ruleId: Type.Optional(Type.Number()), 6 | params: Type.Optional( 7 | Type.Object({ 8 | last_chapter_rule: Type.Optional(Type.String()), 9 | title: Type.Optional(Type.String()), 10 | version: Type.Optional(Type.Number()), 11 | url: Type.Optional(Type.String()), 12 | col_type: Type.Optional(Type.String()), 13 | find_rule: Type.Optional(Type.String()), 14 | group: Type.Optional(Type.String()), 15 | ua: Type.Optional(Type.String()), 16 | preRule: Type.Optional(Type.String()), 17 | pages: Type.Optional(Type.String()), 18 | }) 19 | ), 20 | rulebaseurl: Type.Optional(Type.String()), 21 | title: Type.String(), 22 | type: Type.Enum(ViewHistoryType), 23 | url: Type.String(), 24 | videourl: Type.Optional(Type.String()), 25 | group_lpcolumn: Type.Optional(Type.String()), 26 | picurl: Type.Optional(Type.String()), 27 | extradata: Type.Optional(Type.String()), 28 | }); 29 | 30 | export type Record = Static; 31 | -------------------------------------------------------------------------------- /packages/main/src/server/components/playerposhis/routes/getPos.ts: -------------------------------------------------------------------------------- 1 | import { 2 | RawReplyDefaultExpression, 3 | RawRequestDefaultExpression, 4 | RawServerDefault, 5 | RouteOptions, 6 | } from 'fastify'; 7 | import { Playerposhis as PlayerposhisQuery } from '#/params'; 8 | import Playerposhis from '/@/apis/core/database/sqlite/models/playerposhis'; 9 | 10 | export default function record() { 11 | const route: RouteOptions< 12 | RawServerDefault, 13 | RawRequestDefaultExpression, 14 | RawReplyDefaultExpression, 15 | { 16 | Reply: number; 17 | Querystring: PlayerposhisQuery.GetPos; 18 | } 19 | > = { 20 | url: '/playerposhis/getPos', 21 | method: 'GET', 22 | schema: { 23 | querystring: PlayerposhisQuery.GetPos, 24 | }, 25 | handler: async (request) => { 26 | const playurl = request.query.playurl.replace(/\u0000/g, ''); 27 | const res = await Playerposhis.findOne({ 28 | where: { 29 | playurl, 30 | }, 31 | }); 32 | return res?.getDataValue('pos') || 0; 33 | }, 34 | errorHandler: (error) => { 35 | console.error(error); 36 | return error; 37 | }, 38 | }; 39 | 40 | return route; 41 | } 42 | -------------------------------------------------------------------------------- /packages/preload/vite.config.js: -------------------------------------------------------------------------------- 1 | import { chrome } from '../../electron-vendors.config.json'; 2 | import { join } from 'path'; 3 | import { builtinModules } from 'module'; 4 | import path from 'path'; 5 | 6 | const PACKAGE_ROOT = __dirname; 7 | 8 | /** 9 | * @type {import('vite').UserConfig} 10 | * @see https://vitejs.dev/config/ 11 | */ 12 | const config = { 13 | mode: process.env.MODE, 14 | root: PACKAGE_ROOT, 15 | envDir: process.cwd(), 16 | resolve: { 17 | alias: { 18 | '/@/': join(PACKAGE_ROOT, 'src') + '/', 19 | }, 20 | }, 21 | build: { 22 | sourcemap: 'inline', 23 | target: `chrome${chrome}`, 24 | outDir: 'dist', 25 | assetsDir: '.', 26 | minify: process.env.MODE !== 'development', 27 | lib: { 28 | entry: 'src/index.ts', 29 | formats: ['cjs'], 30 | }, 31 | rollupOptions: { 32 | external: [ 33 | 'electron', 34 | 'sanitize-html', 35 | path.resolve(__dirname, '../main/dist/index.cjs'), 36 | ...builtinModules, 37 | ], 38 | output: { 39 | entryFileNames: '[name].cjs', 40 | }, 41 | }, 42 | emptyOutDir: true, 43 | brotliSize: false, 44 | }, 45 | }; 46 | 47 | export default config; 48 | -------------------------------------------------------------------------------- /packages/renderer/src/plugins/artplayer-plugin-danmuku/src/utils.js: -------------------------------------------------------------------------------- 1 | export function filter(queue, state, callback) { 2 | return queue.filter((danmu) => danmu.$state === state).map(callback); 3 | } 4 | 5 | export function getRect(ref, key) { 6 | const result = ref.getBoundingClientRect(); 7 | return key ? result[key] : result; 8 | } 9 | 10 | export function getDanmuRef(queue) { 11 | const result = queue.find((danmu) => { 12 | return danmu.$ref && danmu.$state === 'wait'; 13 | }); 14 | 15 | if (result) { 16 | const { $ref } = result; 17 | result.$ref = null; 18 | return $ref; 19 | } 20 | 21 | const $ref = document.createElement('div'); 22 | $ref.style.cssText = ` 23 | user-select: none; 24 | position: absolute; 25 | white-space: pre; 26 | pointer-events: none; 27 | perspective: 500px; 28 | display: inline-block; 29 | will-change: transform; 30 | font-family: SimHei, "Microsoft JhengHei", Arial, Helvetica, sans-serif; 31 | font-weight: normal; 32 | line-height: 1.125; 33 | text-shadow: rgb(0, 0, 0) 1px 0px 1px, rgb(0, 0, 0) 0px 1px 1px, rgb(0, 0, 0) 0px -1px 1px, rgb(0, 0, 0) -1px 0px 1px; 34 | `; 35 | 36 | return $ref; 37 | } 38 | -------------------------------------------------------------------------------- /packages/renderer/src/utils/http/axios/axiosTransform.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 数据处理类,可以根据项目自行配置 3 | */ 4 | import type { AxiosRequestConfig, AxiosResponse } from 'axios'; 5 | import type { RequestOptions } from './types'; 6 | 7 | export abstract class AxiosTransform { 8 | /** 9 | * @description: 请求之前处理配置 10 | * @description: Process configuration before request 11 | */ 12 | beforeRequestHook?: (config: AxiosRequestConfig, options: RequestOptions) => AxiosRequestConfig; 13 | 14 | /** 15 | * @description: 请求成功处理 16 | */ 17 | transformRequestData?: (res: AxiosResponse, options: RequestOptions) => any; 18 | 19 | /** 20 | * @description: 请求失败处理 21 | */ 22 | requestCatch?: (e: Error) => Promise; 23 | 24 | /** 25 | * @description: 请求之前的拦截器 26 | */ 27 | requestInterceptors?: (config: AxiosRequestConfig) => AxiosRequestConfig; 28 | 29 | /** 30 | * @description: 请求之后的拦截器 31 | */ 32 | responseInterceptors?: (res: AxiosResponse) => AxiosResponse; 33 | 34 | /** 35 | * @description: 请求之前的拦截器错误处理 36 | */ 37 | requestInterceptorsCatch?: (error: Error) => void; 38 | 39 | /** 40 | * @description: 请求之后的拦截器错误处理 41 | */ 42 | responseInterceptorsCatch?: (error: Error) => void; 43 | } 44 | -------------------------------------------------------------------------------- /scripts/build.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | const { build } = require('vite'); 3 | const { dirname } = require('path'); 4 | 5 | /** @type 'production' | 'development' */ 6 | const mode = (process.env.MODE = process.env.MODE || 'production'); 7 | 8 | const packagesConfigs = [ 9 | 'packages/main/vite.config.js', 10 | 'packages/preload/vite.config.js', 11 | 'packages/renderer/vite.config.js', 12 | ]; 13 | 14 | /** 15 | * Run `vite build` for config file 16 | */ 17 | const buildByConfig = (configFile) => build({ configFile, mode }); 18 | (async () => { 19 | try { 20 | const totalTimeLabel = 'Total bundling time'; 21 | console.time(totalTimeLabel); 22 | 23 | for (const packageConfigPath of packagesConfigs) { 24 | const consoleGroupName = `${dirname(packageConfigPath)}/`; 25 | console.group(consoleGroupName); 26 | 27 | const timeLabel = 'Bundling time'; 28 | console.time(timeLabel); 29 | 30 | await buildByConfig(packageConfigPath); 31 | 32 | console.timeEnd(timeLabel); 33 | console.groupEnd(); 34 | console.log('\n'); // Just for pretty print 35 | } 36 | console.timeEnd(totalTimeLabel); 37 | } catch (e) { 38 | console.error(e); 39 | process.exit(1); 40 | } 41 | })(); 42 | -------------------------------------------------------------------------------- /packages/renderer/src/utils/http/axios/checkStatus.ts: -------------------------------------------------------------------------------- 1 | export function checkStatus(status: number, msg: string, message: any): void { 2 | switch (status) { 3 | case 400: 4 | message.error(`${msg}`); 5 | break; 6 | // 401: 未登录 7 | // 未登录则跳转登录页面,并携带当前页面的路径 8 | // 在登录成功后返回当前页面,这一步需要在登录页操作。 9 | case 401: 10 | message.error('用户没有权限(令牌、用户名、密码错误)!'); 11 | break; 12 | case 403: 13 | message.error('用户得到授权,但是访问是被禁止的。!'); 14 | break; 15 | // 404请求不存在 16 | case 404: 17 | message.error('网络请求错误,未找到该资源!'); 18 | break; 19 | case 405: 20 | message.error('网络请求错误,请求方法未允许!'); 21 | break; 22 | case 408: 23 | message.error('网络请求超时!'); 24 | break; 25 | case 500: 26 | message.error(msg || '服务器错误,请联系开发者!'); 27 | break; 28 | case 501: 29 | message.error('网络未实现!'); 30 | break; 31 | case 502: 32 | message.error('网络错误!'); 33 | break; 34 | case 503: 35 | message.error('服务不可用,服务器暂时过载或维护!'); 36 | break; 37 | case 504: 38 | message.error('网络超时!'); 39 | break; 40 | case 505: 41 | message.error('http版本不支持该请求!'); 42 | break; 43 | default: 44 | message.error(msg); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /packages/renderer/src/views/home/RuleTabs.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /packages/renderer/build/vite/plugin/index.ts: -------------------------------------------------------------------------------- 1 | import type { Plugin } from 'vite'; 2 | import Components from 'unplugin-vue-components/vite'; 3 | import { NaiveUiResolver } from 'unplugin-vue-components/resolvers'; 4 | 5 | import vue from '@vitejs/plugin-vue'; 6 | import vueJsx from '@vitejs/plugin-vue-jsx'; 7 | 8 | import { configHtmlPlugin } from './html'; 9 | import { configCompressPlugin } from './compress'; 10 | 11 | // @ts-ignore 12 | export function createVitePlugins(viteEnv: ViteEnv, isBuild: boolean) { 13 | const { VITE_BUILD_COMPRESS, VITE_BUILD_COMPRESS_DELETE_ORIGIN_FILE } = viteEnv; 14 | 15 | const vitePlugins: (Plugin | Plugin[])[] = [ 16 | // have to 17 | vue({ 18 | reactivityTransform: true, 19 | }), 20 | // have to 21 | vueJsx(), 22 | 23 | // 按需引入NaiveUi且自动创建组件声明 24 | Components({ 25 | dts: 'src/components.d.ts', 26 | resolvers: [NaiveUiResolver()], 27 | }), 28 | ]; 29 | 30 | // vite-plugin-html 31 | vitePlugins.push(configHtmlPlugin(viteEnv, isBuild)); 32 | 33 | if (isBuild) { 34 | // rollup-plugin-gzip 35 | vitePlugins.push( 36 | configCompressPlugin(VITE_BUILD_COMPRESS, VITE_BUILD_COMPRESS_DELETE_ORIGIN_FILE) 37 | ); 38 | } 39 | 40 | return vitePlugins; 41 | } 42 | -------------------------------------------------------------------------------- /packages/renderer/src/store/modules/designSetting.ts: -------------------------------------------------------------------------------- 1 | import { defineStore } from 'pinia'; 2 | import { store } from '/@/store'; 3 | import designSetting from '/@/settings/designSetting'; 4 | 5 | const { themeMode, appTheme, appThemeList } = designSetting; 6 | 7 | type ThemeModeType = 'light' | 'dark' | 'system'; 8 | 9 | interface DesignSettingState { 10 | themeMode: ThemeModeType; 11 | //系统风格 12 | appTheme: string; 13 | //系统内置风格 14 | appThemeList: string[]; 15 | } 16 | 17 | export const useDesignSettingStore = defineStore({ 18 | id: 'app-design-setting', 19 | state: (): DesignSettingState => ({ 20 | themeMode: themeMode as ThemeModeType, 21 | appTheme, 22 | appThemeList, 23 | }), 24 | getters: { 25 | getThemeMode(): ThemeModeType { 26 | return this.themeMode; 27 | }, 28 | getAppTheme(): string { 29 | return this.appTheme; 30 | }, 31 | getAppThemeList(): string[] { 32 | return this.appThemeList; 33 | }, 34 | }, 35 | actions: { 36 | setThemeMode(themeMode: ThemeModeType): void { 37 | this.themeMode = themeMode; 38 | }, 39 | }, 40 | }); 41 | 42 | // Need to be used outside the setup 43 | export function useDesignSettingWithOut() { 44 | return useDesignSettingStore(store); 45 | } 46 | -------------------------------------------------------------------------------- /packages/renderer/src/components/AirColComponent/Pic2.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 22 | 23 | 51 | -------------------------------------------------------------------------------- /packages/main/public/worker/fetch.js: -------------------------------------------------------------------------------- 1 | // const iconv = require('iconv-lite'); 2 | const { runAsWorker } = require('sync-threads'); 3 | 4 | runAsWorker(async ({ url, config = {} }) => { 5 | const got = (await require('../esm-got.cjs')).default; 6 | const instance = got.extend({ followRedirect: config.redirect !== false }); 7 | return instance(url, { 8 | headers: { 9 | 'Content-Type': 'application/x-www-form-urlencoded', 10 | 'User-Agent': 11 | 'Mozilla/5.0 (iPhone; CPU iPhone OS 10_3_1 like Mac OS X) AppleWebKit/603.1.30 (KHTML, like Gecko) Version/10.0 Mobile/14E304 Safari/602.1 Edg/98.0.4758.80', 12 | ...(config.headers || {}), 13 | }, 14 | method: config.method || 'GET', 15 | body: config.body, 16 | responseType: config.toHex ? 'buffer' : 'text', 17 | }).then((response) => { 18 | // const data = iconv.decode(response, 'utf-8'); 19 | let body = response.body; 20 | if (config.toHex) { 21 | body = Buffer.from(body).toString('hex'); 22 | } 23 | if (config.withStatusCode || config.withHeaders) { 24 | return JSON.stringify({ 25 | body, 26 | headers: response.headers, 27 | statusCode: response.statusCode, 28 | }); 29 | } else { 30 | return body; 31 | } 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /packages/main/src/apis/core/air/utils/airVm/privateFile.ts: -------------------------------------------------------------------------------- 1 | import AirVm from '/@/apis/core/air/utils/airVm/index'; 2 | import { URL } from 'url'; 3 | import { join } from 'path'; 4 | import fs from 'fs-extra'; 5 | 6 | export default (airVm: AirVm) => { 7 | function getFilePath(fileName: string) { 8 | if (fileName.startsWith('hiker://files')) { 9 | const url = new URL(fileName); 10 | const path = decodeURIComponent(url.pathname); 11 | return join(airVm.documentsDir, path); 12 | } else { 13 | return join( 14 | airVm.documentsDir, 15 | `./rules/files/${airVm.context.articlelistrule.title}/${fileName}` 16 | ); 17 | } 18 | } 19 | 20 | return { 21 | saveFile(fileName: string, content: string) { 22 | const filePath = getFilePath(fileName); 23 | fs.ensureDirSync(filePath); 24 | fs.writeFileSync(filePath, content); 25 | }, 26 | 27 | readFile(fileName: string) { 28 | const filePath = getFilePath(fileName); 29 | return fs.readFileSync(filePath, 'utf-8'); 30 | }, 31 | 32 | deleteFile(fileName: string) { 33 | const filePath = getFilePath(fileName); 34 | fs.removeSync(filePath); 35 | }, 36 | 37 | fileExist(fileName: string) { 38 | return fs.pathExists(getFilePath(fileName)); 39 | }, 40 | }; 41 | }; 42 | -------------------------------------------------------------------------------- /packages/renderer/src/utils/colSpan.ts: -------------------------------------------------------------------------------- 1 | export const normalSpan: { [key in ColType]: string } = { 2 | avatar: '24 800:48 1000:64', 3 | blank_block: '24 800:48 1000:64', 4 | card_pic_1: '', 5 | card_pic_2: '', 6 | card_pic_2_2: '', 7 | card_pic_2_2_left: '', 8 | flex_button: '', 9 | icon_1_search: '', 10 | icon_2: '12', 11 | icon_2_round: '12', 12 | icon_4: '', 13 | icon_4_card: '', 14 | icon_small_3: '8', 15 | icon_small_4: '', 16 | icon_round_small_4: '6', 17 | input: '24 800:48 1000:64', 18 | line: '24 800:48 1000:64', 19 | line_blank: '24 800:48 1000:64', 20 | long_text: '24 800:48 1000:64', 21 | movie_1: '24 800:48 1000:64', 22 | movie_1_left_pic: '24 800:48 1000:64', 23 | movie_1_vertical_pic: '24 800:48 1000:64', 24 | movie_1_vertical_pic_blur: '24 800:48 1000:64', 25 | movie_2: '12', 26 | movie_3: '8', 27 | movie_3_marquee: '', 28 | pic_1_full: '', 29 | pic_2: '12', 30 | pic_3: '', 31 | pic_3_square: '', 32 | rich_text: '24 800:48 1000:64', 33 | scroll_button: '24 800:48 1000:64', 34 | text_1: '24 800:48 1000:64', 35 | text_2: '12', 36 | text_4: '6', 37 | text_5: '4', 38 | text_center_1: '24 800:48 1000:64', 39 | x5_webview_single: '24 800:48 1000:64', 40 | pic_1: '24 800:48 1000:64', 41 | text_3: '8', 42 | big_blank_block: '24 800:48 1000:64', 43 | }; 44 | -------------------------------------------------------------------------------- /packages/renderer/src/utils/text.ts: -------------------------------------------------------------------------------- 1 | import XRegExp, { ReplacementDetail } from 'xregexp'; 2 | 3 | const escapeMents: ReplacementDetail[] = [[/\t/g, '  ']]; 4 | 5 | export function wrapText(text) { 6 | return XRegExp.replaceEach(text, [[/\t/g, ' ']]); 7 | } 8 | 9 | export function wrapTextToHtml(text, removeTags = false) { 10 | if (!text) return text; 11 | return window.sanitizeHtml.sanitizeHtml( 12 | XRegExp.replaceEach(text, [ 13 | [/‘‘(.*?)’’/g, '$1'], 14 | [/““(.*?)””/g, '$1'], 15 | ...escapeMents, 16 | ]), 17 | { 18 | allowedTags: removeTags ? [] : window.sanitizeHtml.defaults.allowedTags.concat(['font']), 19 | allowedAttributes: { 20 | font: ['color'], 21 | span: ['style'], 22 | }, 23 | allowedStyles: { 24 | '*': { 25 | // Match HEX and RGB 26 | color: [/^#(0x)?[0-9a-f]+$/i, /^rgb\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*\)$/], 27 | 'text-align': [/^left$/, /^right$/, /^center$/], 28 | // Match any number with px, em, or % 29 | 'font-size': [/^\d+(?:px|em|%)$/], 30 | }, 31 | }, 32 | } 33 | ); 34 | } 35 | 36 | export function isCanWrapText(text) { 37 | return XRegExp.test(text, /‘‘(.*?)’’|““(.*?)””/g); 38 | } 39 | -------------------------------------------------------------------------------- /packages/renderer/build/vite/plugin/html.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Plugin to minimize and use ejs template syntax in index.html. 3 | * https://github.com/anncwb/vite-plugin-html 4 | */ 5 | import type { Plugin } from 'vite'; 6 | 7 | import html from 'vite-plugin-html'; 8 | 9 | import pkg from '../../../../../package.json'; 10 | import { GLOB_CONFIG_FILE_NAME } from '../../constant'; 11 | 12 | // @ts-ignore 13 | export function configHtmlPlugin(env: ViteEnv, isBuild: boolean) { 14 | const { VITE_GLOB_APP_TITLE, VITE_PUBLIC_PATH } = env; 15 | 16 | const path = VITE_PUBLIC_PATH.endsWith('/') ? VITE_PUBLIC_PATH : `${VITE_PUBLIC_PATH}/`; 17 | 18 | const getAppConfigSrc = () => { 19 | return `${path || '/'}${GLOB_CONFIG_FILE_NAME}?v=${pkg.version}-${new Date().getTime()}`; 20 | }; 21 | 22 | const htmlPlugin: Plugin[] = html({ 23 | minify: isBuild, 24 | inject: { 25 | // Inject data into ejs template 26 | injectData: { 27 | title: VITE_GLOB_APP_TITLE, 28 | }, 29 | // Embed the generated app.config.js file 30 | tags: isBuild 31 | ? [ 32 | { 33 | tag: 'script', 34 | attrs: { 35 | src: getAppConfigSrc(), 36 | }, 37 | }, 38 | ] 39 | : [], 40 | }, 41 | }); 42 | return htmlPlugin; 43 | } 44 | -------------------------------------------------------------------------------- /packages/main/src/server/components/viewhistory/routes/all.ts: -------------------------------------------------------------------------------- 1 | import { 2 | RawReplyDefaultExpression, 3 | RawRequestDefaultExpression, 4 | RawServerDefault, 5 | RouteOptions, 6 | } from 'fastify'; 7 | import { ViewHistory as ViewHistoryQuery } from '#/params'; 8 | import ViewHistory from '/@/apis/core/database/sqlite/models/viewhistory'; 9 | import * as Models from '#/models'; 10 | import { Type } from '@sinclair/typebox'; 11 | 12 | export default function all() { 13 | const route: RouteOptions< 14 | RawServerDefault, 15 | RawRequestDefaultExpression, 16 | RawReplyDefaultExpression, 17 | { 18 | Querystring: ViewHistoryQuery.All; 19 | } 20 | > = { 21 | url: '/viewhistory/all', 22 | method: 'GET', 23 | schema: { 24 | querystring: ViewHistoryQuery.All, 25 | response: { 26 | count: Type.Number(), 27 | rows: Models.ViewHistorys, 28 | }, 29 | }, 30 | handler: async (request) => { 31 | return ViewHistory.findAndCountAll({ 32 | order: [['time', 'DESC']], 33 | offset: ((request.query.currentPage || 1) - 1) * request.query.pageSize, 34 | limit: request.query.pageSize || 15, 35 | }); 36 | }, 37 | errorHandler: (error) => { 38 | console.error(error); 39 | return error; 40 | }, 41 | }; 42 | 43 | return route; 44 | } 45 | -------------------------------------------------------------------------------- /packages/renderer/src/utils/http/axios/helper.ts: -------------------------------------------------------------------------------- 1 | import { isObject, isString } from '/@/utils/is'; 2 | 3 | const DATE_TIME_FORMAT = 'YYYY-MM-DD HH:mm'; 4 | 5 | export function joinTimestamp( 6 | join: boolean, 7 | restful: T 8 | ): T extends true ? string : object; 9 | 10 | export function joinTimestamp(join: boolean, restful = false): string | object { 11 | if (!join) { 12 | return restful ? '' : {}; 13 | } 14 | const now = new Date().getTime(); 15 | if (restful) { 16 | return `?_t=${now}`; 17 | } 18 | return { _t: now }; 19 | } 20 | 21 | /** 22 | * @description: Format request parameter time 23 | */ 24 | export function formatRequestDate(params: Recordable) { 25 | if (Object.prototype.toString.call(params) !== '[object Object]') { 26 | return; 27 | } 28 | 29 | for (const key in params) { 30 | if (params[key] && params[key]._isAMomentObject) { 31 | params[key] = params[key].format(DATE_TIME_FORMAT); 32 | } 33 | if (isString(key)) { 34 | const value = params[key]; 35 | if (value) { 36 | try { 37 | params[key] = isString(value) ? value.trim() : value; 38 | } catch (error) { 39 | throw new Error(error as string); 40 | } 41 | } 42 | } 43 | if (isObject(params[key])) { 44 | formatRequestDate(params[key]); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /packages/renderer/src/plugins/artplayer-plugin-danmuku/src/bilibili.js: -------------------------------------------------------------------------------- 1 | export function getMode(key) { 2 | switch (key) { 3 | case 1: 4 | case 2: 5 | case 3: 6 | return 0; 7 | case 4: 8 | case 5: 9 | return 1; 10 | default: 11 | return 0; 12 | } 13 | } 14 | 15 | export function bilibiliDanmuParseFromXml(xmlString) { 16 | if (typeof xmlString !== 'string') return []; 17 | const srtList = xmlString.match(/[\S ]*?)<\/d>/gi); 18 | return srtList && srtList.length 19 | ? srtList.map((item) => { 20 | const [, attrStr, text] = item.match(/(.+)<\/d>/); 21 | const attr = attrStr.split(','); 22 | return attr.length >= 8 && text.trim() 23 | ? { 24 | text, 25 | time: Number(attr[0]), 26 | mode: getMode(Number(attr[1])), 27 | fontSize: Number(attr[2]), 28 | color: `#${Number(attr[3]).toString(16)}`, 29 | timestamp: Number(attr[4]), 30 | pool: Number(attr[5]), 31 | userID: attr[6], 32 | rowID: Number(attr[7]), 33 | } 34 | : null; 35 | }) 36 | : []; 37 | } 38 | 39 | export function bilibiliDanmuParseFromUrl(url) { 40 | return fetch(url) 41 | .then((res) => res.text()) 42 | .then((xmlString) => bilibiliDanmuParseFromXml(xmlString)); 43 | } 44 | -------------------------------------------------------------------------------- /packages/main/src/utils/custom-cheerio.ts: -------------------------------------------------------------------------------- 1 | const { load } = require('@yongteng/cheerio/lib/slim'); 2 | const { select } = require('@yongteng/cheerio'); 3 | import { Cheerio, CheerioAPI, Element } from '@yongteng/cheerio'; 4 | 5 | select.filters.gt2 = (next, rule, { adapter, equals }) => { 6 | return function nthChild(elem) { 7 | const siblings = adapter.getSiblings(elem); 8 | let pos = 0; 9 | 10 | for (let i = 0; i < siblings.length; i++) { 11 | if (equals(elem, siblings[i])) break; 12 | pos++; 13 | } 14 | return pos > Number(rule) + 1 && next(elem); 15 | }; 16 | }; 17 | 18 | export default { 19 | load: function (...args) { 20 | const func = load(...args) as any; 21 | 22 | const find = func.prototype.find; 23 | 24 | func.prototype.find = function (selectorOrHaystack?: string | Cheerio | Element) { 25 | if (typeof selectorOrHaystack === 'string') { 26 | selectorOrHaystack = selectorOrHaystack.replace(/:gt(\(\d?\))/g, ':gt2$1'); 27 | } 28 | 29 | return find.bind(this)(selectorOrHaystack); 30 | }; 31 | 32 | const newFunc = function (...args2) { 33 | if (typeof args2[0] === 'string') { 34 | args2[0] = args2[0].replace(/:gt(\(\d?\))/g, ':gt2$1'); 35 | } 36 | return func(...args2); 37 | }; 38 | 39 | Object.assign(newFunc, func); 40 | 41 | return newFunc; 42 | }, 43 | } as CheerioAPI; 44 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | dist 4 | *.local 5 | thumbs.db 6 | 7 | .eslintcache 8 | 9 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider 10 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 11 | 12 | # User-specific stuff 13 | .idea/**/workspace.xml 14 | .idea/**/tasks.xml 15 | .idea/**/usage.statistics.xml 16 | .idea/**/dictionaries 17 | .idea/**/shelf 18 | 19 | # Generated files 20 | .idea/**/contentModel.xml 21 | 22 | # Sensitive or high-churn files 23 | .idea/**/dataSources/ 24 | .idea/**/dataSources.ids 25 | .idea/**/dataSources.local.xml 26 | .idea/**/sqlDataSources.xml 27 | .idea/**/dynamic.xml 28 | .idea/**/uiDesigner.xml 29 | .idea/**/dbnavigator.xml 30 | 31 | # Gradle 32 | .idea/**/gradle.xml 33 | .idea/**/libraries 34 | 35 | # Gradle and Maven with auto-import 36 | # When using Gradle or Maven with auto-import, you should exclude module files, 37 | # since they will be recreated, and may cause churn. Uncomment if using 38 | # auto-import. 39 | .idea/artifacts 40 | .idea/compiler.xml 41 | .idea/jarRepositories.xml 42 | .idea/modules.xml 43 | .idea/*.iml 44 | .idea/modules 45 | *.iml 46 | *.ipr 47 | 48 | # Mongo Explorer plugin 49 | .idea/**/mongoSettings.xml 50 | 51 | # File-based project format 52 | *.iws 53 | 54 | # Editor-based Rest Client 55 | .idea/httpRequests 56 | /.idea/csv-plugin.xml 57 | -------------------------------------------------------------------------------- /packages/main/src/server/components/playerposhis/routes/record.ts: -------------------------------------------------------------------------------- 1 | import { 2 | RawReplyDefaultExpression, 3 | RawRequestDefaultExpression, 4 | RawServerDefault, 5 | RouteOptions, 6 | } from 'fastify'; 7 | import { Playerposhis as PlayerposhisQuery } from '#/params'; 8 | import Playerposhis from '/@/apis/core/database/sqlite/models/playerposhis'; 9 | 10 | export default function record() { 11 | const route: RouteOptions< 12 | RawServerDefault, 13 | RawRequestDefaultExpression, 14 | RawReplyDefaultExpression, 15 | { 16 | Reply: string; 17 | Body: PlayerposhisQuery.Record; 18 | } 19 | > = { 20 | url: '/playerposhis/record', 21 | method: 'PUT', 22 | schema: { 23 | body: PlayerposhisQuery.Record, 24 | }, 25 | handler: async (request) => { 26 | const playurl = request.body.playurl.replace(/\u0000/g, ''); 27 | const [res, created] = await Playerposhis.findOrCreate({ 28 | defaults: { 29 | playurl, 30 | pos: request.body.pos, 31 | }, 32 | where: { 33 | playurl, 34 | }, 35 | }); 36 | if (!created) { 37 | res.update({ 38 | playurl, 39 | pos: request.body.pos, 40 | }); 41 | } 42 | return '成功'; 43 | }, 44 | errorHandler: (error) => { 45 | console.error(error); 46 | return error; 47 | }, 48 | }; 49 | 50 | return route; 51 | } 52 | -------------------------------------------------------------------------------- /packages/renderer/src/index.ts: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue'; 2 | import App from '/@/App.vue'; 3 | import router from '/@/router'; 4 | import 'vfonts/Lato.css'; 5 | import 'vfonts/FiraCode.css'; 6 | import '/@/styles/global.css'; 7 | import { setupStore } from '/@/store'; 8 | import { AppProvider } from '/@/components/AppProvider/index'; 9 | import { useDesignSettingStore } from '/@/store/modules/designSetting'; 10 | import { getConfig } from '/@/utils/events-impl'; 11 | import 'viewerjs/dist/viewer.css'; 12 | import VueViewer from 'v-viewer'; 13 | import { useSocket } from '/@/hooks/socket'; 14 | import VueLazyload from '@jambonn/vue-lazyload'; 15 | import loadImage from '/@/assets/images/loading.png'; 16 | 17 | async function bootstrap() { 18 | const appProvider = createApp(AppProvider); 19 | 20 | const app = createApp(App); 21 | 22 | // 挂载状态管理 23 | setupStore(app); 24 | 25 | //优先挂载一下 Provider 解决路由守卫,Axios中可使用,Dialog,Message 等之类组件 26 | appProvider.mount('#AppProvider', true); 27 | 28 | const designSetting = useDesignSettingStore(); 29 | 30 | designSetting.setThemeMode(await getConfig('settings.themeMode')); 31 | 32 | app.use(router); 33 | 34 | app.use(VueViewer); 35 | 36 | app.use(VueLazyload, { 37 | preLoad: 1.3, 38 | loading: loadImage, 39 | error: loadImage, 40 | attempt: 1, 41 | }); 42 | 43 | useSocket(); 44 | 45 | app.mount('#app', true); 46 | } 47 | 48 | void bootstrap(); 49 | -------------------------------------------------------------------------------- /packages/main/src/server/plugins.ts: -------------------------------------------------------------------------------- 1 | import type { FastifyInstance } from 'fastify'; 2 | import swaggerPlugin from 'fastify-swagger'; 3 | import fastifyCorsor from 'fastify-cors'; 4 | import fastifyCookie from 'fastify-cookie'; 5 | import fastifyCaching from 'fastify-caching'; 6 | import fastifyServerSession from 'fastify-server-session'; 7 | import fastifySocketio from 'fastify-socket.io'; 8 | 9 | const SESSION_TTL = 86400; // 1 day in seconds 10 | 11 | export default async function registerPlugins(fastify: FastifyInstance): Promise { 12 | await fastify.register(swaggerPlugin, { 13 | routePrefix: '/docs/api', 14 | exposeRoute: true, 15 | }); 16 | 17 | await fastify.register(fastifyCorsor, { 18 | // put your options here 19 | origin: true, 20 | credentials: true, 21 | }); 22 | 23 | await fastify.register(fastifySocketio, { 24 | cors: { 25 | // put your options here 26 | origin: true, 27 | credentials: true, 28 | }, 29 | }); 30 | 31 | await fastify.register(fastifyCookie); 32 | 33 | await fastify.register(fastifyCaching); 34 | 35 | await fastify.register(fastifyServerSession, { 36 | secretKey: 'airhhhh0000000000011111111111222', 37 | sessionMaxAge: 900000, // 15 minutes 38 | cookie: { 39 | maxAge: SESSION_TTL, 40 | secure: true, 41 | httpOnly: true, 42 | domain: null, 43 | path: '/', 44 | sameSite: 'none', 45 | }, 46 | }); 47 | } 48 | -------------------------------------------------------------------------------- /packages/main/src/server/index.ts: -------------------------------------------------------------------------------- 1 | import Fastify, { FastifyInstance } from 'fastify'; 2 | import routes from '/@/server/components/route'; 3 | import registerHooks from './hooks'; 4 | import registerPlugins from '/@/server/plugins'; 5 | import { RouteOptions } from 'fastify/types/route'; 6 | 7 | export class Server { 8 | public readonly fastify: FastifyInstance; 9 | private port = 52020; 10 | 11 | constructor() { 12 | this.fastify = Fastify(); 13 | } 14 | 15 | async start() { 16 | global.vmCpNumber = 1; 17 | 18 | await registerPlugins(this.fastify); 19 | 20 | registerHooks(this.fastify); 21 | 22 | this.fastify.get('/', async () => { 23 | return { hello: 'world' }; 24 | }); 25 | 26 | this.fastify.ready((err) => { 27 | if (err) { 28 | throw err; 29 | } 30 | this.fastify.io.on('connect', (socket) => { 31 | socket.on('addGroup', (id) => { 32 | socket.join(id); 33 | }); 34 | }); 35 | }); 36 | 37 | this.fastify.after(() => { 38 | for (const getRoute of routes) { 39 | this.fastify.route(getRoute() as RouteOptions); 40 | } 41 | }); 42 | 43 | return new Promise((resolve, reject) => { 44 | this.fastify.listen(this.port, (err, address) => { 45 | if (err) { 46 | reject(err); 47 | } 48 | console.log(`Air app listening at http://localhost:${this.port}`); 49 | resolve(address); 50 | }); 51 | }); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /scripts/update-electron-vendors.js: -------------------------------------------------------------------------------- 1 | const { writeFile } = require('fs/promises'); 2 | const { execSync } = require('child_process'); 3 | const electron = require('electron'); 4 | const path = require('path'); 5 | 6 | /** 7 | * Returns versions of electron vendors 8 | * The performance of this feature is very poor and can be improved 9 | * @see https://github.com/electron/electron/issues/28006 10 | * 11 | * @returns {NodeJS.ProcessVersions} 12 | */ 13 | function getVendors() { 14 | const output = execSync(`${electron} -p "JSON.stringify(process.versions)"`, { 15 | env: { ELECTRON_RUN_AS_NODE: '1' }, 16 | encoding: 'utf-8', 17 | }); 18 | 19 | return JSON.parse(output); 20 | } 21 | 22 | function updateVendors() { 23 | const electronRelease = getVendors(); 24 | 25 | const nodeMajorVersion = electronRelease.node.split('.')[0]; 26 | const chromeMajorVersion = electronRelease.v8.split('.')[0] + electronRelease.v8.split('.')[1]; 27 | 28 | const browserslistrcPath = path.resolve(process.cwd(), '.browserslistrc'); 29 | 30 | return Promise.all([ 31 | writeFile( 32 | './.electron-vendors.cache.json', 33 | JSON.stringify( 34 | { 35 | chrome: chromeMajorVersion, 36 | node: nodeMajorVersion, 37 | }, 38 | null, 39 | 2 40 | ) + '\n' 41 | ), 42 | 43 | writeFile(browserslistrcPath, `Chrome ${chromeMajorVersion}\n`, 'utf8'), 44 | ]); 45 | } 46 | 47 | updateVendors().catch((err) => { 48 | console.error(err); 49 | process.exit(1); 50 | }); 51 | -------------------------------------------------------------------------------- /packages/renderer/assets/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /packages/main/src/server/components/viewhistory/routes/getViewHistory.ts: -------------------------------------------------------------------------------- 1 | import { 2 | RawReplyDefaultExpression, 3 | RawRequestDefaultExpression, 4 | RawServerDefault, 5 | RouteOptions, 6 | } from 'fastify'; 7 | import { ViewHistory as ViewHistoryQuery } from '#/params'; 8 | import ViewHistory from '/@/apis/core/database/sqlite/models/viewhistory'; 9 | import { URL } from 'url'; 10 | 11 | export default function getViewHistory() { 12 | const route: RouteOptions< 13 | RawServerDefault, 14 | RawRequestDefaultExpression, 15 | RawReplyDefaultExpression, 16 | { 17 | Querystring: ViewHistoryQuery.GetLastclick; 18 | } 19 | > = { 20 | url: '/viewhistory/getViewHistory', 21 | method: 'GET', 22 | schema: { 23 | querystring: ViewHistoryQuery.GetLastclick, 24 | }, 25 | handler: async (request) => { 26 | let url = request.query.url; 27 | if (url.startsWith('hiker://page')) { 28 | try { 29 | const url2 = new URL(url); 30 | 31 | url = 32 | url2.searchParams 33 | .get('url') 34 | ?.replace(/;;/g, ';') 35 | .replace(/&&/g, '&') 36 | .replace(/??/g, '?') || url; 37 | } catch (e) {} 38 | } 39 | return ViewHistory.findOne({ 40 | where: { 41 | url: url, 42 | title: request.query.title, 43 | }, 44 | raw: true, 45 | }); 46 | }, 47 | errorHandler: (error) => { 48 | console.error(error); 49 | return error; 50 | }, 51 | }; 52 | 53 | return route; 54 | } 55 | -------------------------------------------------------------------------------- /packages/renderer/src/utils/events-impl.ts: -------------------------------------------------------------------------------- 1 | import { 2 | AIR_GET_CONFIG, 3 | AIR_OPEN_FILE, 4 | AIR_SAVE_CONFIG, 5 | GET_FILE_CONTENT, 6 | } from '#/events/constants'; 7 | import { nanoid } from 'nanoid'; 8 | import type { IpcRendererEvent } from 'electron'; 9 | 10 | const { api } = window; 11 | 12 | export function saveConfig(config: IObj | string, value?: any) { 13 | if (typeof config === 'string') { 14 | config = { 15 | [config]: value, 16 | }; 17 | } 18 | api.send(AIR_SAVE_CONFIG, config); 19 | } 20 | 21 | export function getConfig(key?: string): Promise { 22 | return new Promise((resolve) => { 23 | const callbackId = nanoid(); 24 | const callback = (event: IpcRendererEvent, config: T | undefined, returnCallbackId: string) => { 25 | if (returnCallbackId === callbackId) { 26 | resolve(config); 27 | api.removeListener(AIR_GET_CONFIG, callback); 28 | } 29 | }; 30 | api.on(AIR_GET_CONFIG, callback); 31 | api.send(AIR_GET_CONFIG, key, callbackId); 32 | }); 33 | } 34 | 35 | export function getFileContent(fileNmae: string): Promise { 36 | return new Promise((resolve) => { 37 | const callbackId = nanoid(); 38 | const callback = (event: IpcRendererEvent, content: string, returnCallbackId: string) => { 39 | if (returnCallbackId === callbackId) { 40 | resolve(content); 41 | api.removeListener(GET_FILE_CONTENT, callback); 42 | } 43 | }; 44 | api.on(GET_FILE_CONTENT, callback); 45 | api.send(GET_FILE_CONTENT, fileNmae, callbackId); 46 | }); 47 | } 48 | 49 | export function openFile(file: string) { 50 | api.send(AIR_OPEN_FILE, file); 51 | } 52 | -------------------------------------------------------------------------------- /packages/main/src/apis/core/database/lowdb/index.ts: -------------------------------------------------------------------------------- 1 | import lowdb from 'lowdb'; 2 | import lodashId from 'lodash-id'; 3 | import FileSync from 'lowdb/adapters/FileSync'; 4 | import json from 'comment-json'; 5 | import { IConfig, IAir } from '/@/apis/core/air/types'; 6 | 7 | class DB { 8 | private readonly ctx: IAir; 9 | private readonly db: lowdb.LowdbSync; 10 | 11 | constructor(ctx: IAir) { 12 | this.ctx = ctx; 13 | const adapter = new FileSync(this.ctx.configPath, { 14 | serialize(obj: object): string { 15 | return json.stringify(obj, null, 2); 16 | }, 17 | deserialize: json.parse, 18 | }); 19 | this.db = lowdb(adapter); 20 | this.db._.mixin(lodashId); 21 | } 22 | 23 | read(): any { 24 | return this.db.read(); 25 | } 26 | 27 | get(key = ''): any { 28 | return this.read().get(key).value(); 29 | } 30 | 31 | set(key: string, value: any): void { 32 | return this.read().set(key, value).write(); 33 | } 34 | 35 | has(key: string): boolean { 36 | return this.read().has(key).value(); 37 | } 38 | 39 | insert(key: string, value: any): void { 40 | return this.read().get(key).insert(value).write(); 41 | } 42 | 43 | unset(key: string, value: any): boolean { 44 | return this.read().get(key).unset(value).write(); 45 | } 46 | 47 | saveConfig(config: Partial): void { 48 | Object.keys(config).forEach((name: string) => { 49 | this.set(name, config[name]); 50 | }); 51 | } 52 | 53 | removeConfig(config: IConfig): void { 54 | Object.keys(config).forEach((name: string) => { 55 | this.unset(name, config[name]); 56 | }); 57 | } 58 | } 59 | 60 | export default DB; 61 | -------------------------------------------------------------------------------- /electron-builder.config.js: -------------------------------------------------------------------------------- 1 | if (process.env.VITE_APP_VERSION === undefined) { 2 | const now = new Date(); 3 | process.env.VITE_APP_VERSION = `${now.getUTCFullYear() - 2000}.${ 4 | now.getUTCMonth() + 1 5 | }.${now.getUTCDate()}-${now.getUTCHours() * 60 + now.getUTCMinutes()}`; 6 | } 7 | 8 | /** 9 | * @type {import('electron-builder').Configuration} 10 | * @see https://www.electron.build/configuration/configuration 11 | */ 12 | const config = { 13 | npmRebuild: false, 14 | appId: 'com.Lingyan000.air', 15 | productName: '空气', 16 | fileAssociations: { 17 | ext: ['.hiker'], 18 | }, 19 | publish: [ 20 | { 21 | provider: 'github', 22 | owner: 'Lingyan000', 23 | repo: 'air', 24 | releaseType: 'draft', 25 | }, 26 | ], 27 | directories: { 28 | output: 'dist', 29 | buildResources: 'buildResources', 30 | }, 31 | copyright: 'Copyright © 2022', 32 | dmg: { 33 | contents: [ 34 | { 35 | x: 410, 36 | y: 150, 37 | type: 'link', 38 | path: '/Applications', 39 | }, 40 | { 41 | x: 130, 42 | y: 150, 43 | type: 'file', 44 | }, 45 | ], 46 | }, 47 | nsis: { 48 | deleteAppDataOnUninstall: true, 49 | oneClick: false, 50 | allowToChangeInstallationDirectory: true, 51 | shortcutName: '空气', 52 | }, 53 | win: { 54 | target: [ 55 | { 56 | target: 'nsis', 57 | arch: [process.arch], 58 | }, 59 | ], 60 | }, 61 | files: ['packages/**/dist/**'], 62 | extraMetadata: { 63 | version: process.env.VITE_APP_VERSION, 64 | }, 65 | snap: { 66 | publish: ['github'], 67 | }, 68 | }; 69 | 70 | module.exports = config; 71 | -------------------------------------------------------------------------------- /packages/renderer/src/utils/http/axios/axiosCancel.ts: -------------------------------------------------------------------------------- 1 | import axios, { AxiosRequestConfig, Canceler } from 'axios'; 2 | import qs from 'qs'; 3 | 4 | import { isFunction } from '/@/utils/is/index'; 5 | 6 | // 声明一个 Map 用于存储每个请求的标识 和 取消函数 7 | let pendingMap = new Map(); 8 | 9 | export const getPendingUrl = (config: AxiosRequestConfig) => 10 | [config.method, config.url, qs.stringify(config.data), qs.stringify(config.params)].join('&'); 11 | 12 | export class AxiosCanceler { 13 | /** 14 | * 添加请求 15 | * @param {Object} config 16 | */ 17 | addPending(config: AxiosRequestConfig) { 18 | this.removePending(config); 19 | const url = getPendingUrl(config); 20 | config.cancelToken = 21 | config.cancelToken || 22 | new axios.CancelToken((cancel) => { 23 | if (!pendingMap.has(url)) { 24 | // 如果 pending 中不存在当前请求,则添加进去 25 | pendingMap.set(url, cancel); 26 | } 27 | }); 28 | } 29 | 30 | /** 31 | * @description: 清空所有pending 32 | */ 33 | removeAllPending() { 34 | pendingMap.forEach((cancel) => { 35 | cancel && isFunction(cancel) && cancel(); 36 | }); 37 | pendingMap.clear(); 38 | } 39 | 40 | /** 41 | * 移除请求 42 | * @param {Object} config 43 | */ 44 | removePending(config: AxiosRequestConfig) { 45 | const url = getPendingUrl(config); 46 | 47 | if (pendingMap.has(url)) { 48 | // 如果在 pending 中存在当前请求标识,需要取消当前请求,并且移除 49 | const cancel = pendingMap.get(url); 50 | cancel && cancel(url); 51 | pendingMap.delete(url); 52 | } 53 | } 54 | 55 | /** 56 | * @description: 重置 57 | */ 58 | reset(): void { 59 | pendingMap = new Map(); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /packages/renderer/types/importMeta.d.ts: -------------------------------------------------------------------------------- 1 | interface ImportMeta { 2 | url: string; 3 | 4 | readonly hot?: { 5 | readonly data: any; 6 | 7 | accept(): void; 8 | accept(cb: (mod: any) => void): void; 9 | accept(dep: string, cb: (mod: any) => void): void; 10 | accept(deps: readonly string[], cb: (mods: any[]) => void): void; 11 | 12 | /** 13 | * @deprecated 14 | */ 15 | acceptDeps(): never; 16 | 17 | dispose(cb: (data: any) => void): void; 18 | decline(): void; 19 | invalidate(): void; 20 | 21 | on: { 22 | ( 23 | event: 'vite:beforeUpdate', 24 | cb: (payload: import('./hmrPayload').UpdatePayload) => void 25 | ): void; 26 | (event: 'vite:beforePrune', cb: (payload: import('./hmrPayload').PrunePayload) => void): void; 27 | ( 28 | event: 'vite:beforeFullReload', 29 | cb: (payload: import('./hmrPayload').FullReloadPayload) => void 30 | ): void; 31 | (event: 'vite:error', cb: (payload: import('./hmrPayload').ErrorPayload) => void): void; 32 | ( 33 | event: import('./customEvent').CustomEventName, 34 | cb: (data: any) => void 35 | ): void; 36 | }; 37 | }; 38 | 39 | readonly env: ImportMetaEnv; 40 | 41 | glob(pattern: string): Record< 42 | string, 43 | () => Promise<{ 44 | [key: string]: any; 45 | }> 46 | >; 47 | 48 | globEager(pattern: string): Record< 49 | string, 50 | { 51 | [key: string]: any; 52 | } 53 | >; 54 | } 55 | 56 | interface ImportMetaEnv { 57 | [key: string]: string | boolean | undefined; 58 | BASE_URL: string; 59 | MODE: string; 60 | DEV: boolean; 61 | PROD: boolean; 62 | SSR: boolean; 63 | } 64 | -------------------------------------------------------------------------------- /packages/main/src/server/components/parse/routes/preHandle.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | RawReplyDefaultExpression, 3 | RawRequestDefaultExpression, 4 | RawServerDefault, 5 | RouteOptions, 6 | } from 'fastify'; 7 | import articlelistrule from '/@/apis/core/database/sqlite/models/articlelistrule'; 8 | import AirParse from '/@/apis/core/air/parse'; 9 | import { Parse as ParseQuery } from '#/params'; 10 | 11 | export default function getRoute() { 12 | const route: RouteOptions< 13 | RawServerDefault, 14 | RawRequestDefaultExpression, 15 | RawReplyDefaultExpression, 16 | { 17 | Body: ParseQuery.PreHandle; 18 | } 19 | > = { 20 | url: '/parse/preHandle', 21 | method: 'PUT', 22 | schema: { 23 | body: ParseQuery.PreHandle, 24 | }, 25 | handler: async (request) => { 26 | const rule = await articlelistrule.findByPk(Number(request.body.id), { 27 | rejectOnEmpty: true, 28 | raw: true, 29 | }); 30 | 31 | const home = await articlelistrule.findAll(); 32 | 33 | if (!rule) throw new Error('规则不存在'); 34 | const airParse = new AirParse(rule, home, (request as any).session || {}); 35 | 36 | return airParse 37 | .preParse(rule.prerule) 38 | .then(() => { 39 | return { 40 | message: '成功', 41 | }; 42 | }) 43 | .finally(() => { 44 | (request as any).session.vars = airParse.vars; 45 | (request as any).session.allConfig = airParse.allConfig; 46 | (request as any).session.allMyVars = airParse.allMyVars; 47 | }); 48 | }, 49 | errorHandler: (error) => { 50 | console.error(error); 51 | return error; 52 | }, 53 | }; 54 | 55 | return route; 56 | } 57 | -------------------------------------------------------------------------------- /packages/renderer/src/views/home/RuleDrawer.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 50 | -------------------------------------------------------------------------------- /packages/renderer/src/utils/rule.ts: -------------------------------------------------------------------------------- 1 | import { isImageUrl, isJson, isVideoUrl } from '/@/utils/index'; 2 | import { isObject } from '/@/utils/is'; 3 | 4 | export type ItemUrlSplitResultType = 5 | | 'rule' 6 | | 'lazyRule' 7 | | 'link' 8 | | 'image' 9 | | 'video' 10 | | 'page' 11 | | 'toast' 12 | | 'object'; 13 | 14 | export interface ItemUrlSplitResult { 15 | url: string; 16 | type: ItemUrlSplitResultType; 17 | rule?: string; 18 | } 19 | 20 | export function replaceMark(value: string): string { 21 | return value.replace(/??/g, '?').replace(/&&/g, '&'); 22 | } 23 | 24 | export function splitItemUrl(url = ''): ItemUrlSplitResult { 25 | if (url.includes('@lazyRule')) { 26 | const [resUrl, ...rule] = url.split('@lazyRule='); 27 | return { 28 | url: resUrl, 29 | type: 'lazyRule', 30 | rule: rule.join('@lazyRule='), 31 | }; 32 | } else if (url.includes('@rule')) { 33 | const [resUrl, ...rule] = url.split('@rule='); 34 | return { 35 | url: resUrl, 36 | type: 'rule', 37 | rule: rule.join('@rule='), 38 | }; 39 | } else if (url.startsWith('hiker://page')) { 40 | return { 41 | url: url, 42 | type: 'page', 43 | }; 44 | } else if (isJson(url) && isObject(JSON.parse(url))) { 45 | return { 46 | url, 47 | type: 'object', 48 | }; 49 | } else if (isImageUrl(url)) { 50 | return { 51 | url, 52 | type: 'image', 53 | }; 54 | } else if (isVideoUrl(url)) { 55 | return { 56 | url, 57 | type: 'video', 58 | }; 59 | } else if (url.startsWith('toast://')) { 60 | return { 61 | url, 62 | type: 'toast', 63 | }; 64 | } else { 65 | return { 66 | url, 67 | type: 'link', 68 | }; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /packages/main/src/server/components/viewhistory/routes/recordClick.ts: -------------------------------------------------------------------------------- 1 | import { 2 | RawReplyDefaultExpression, 3 | RawRequestDefaultExpression, 4 | RawServerDefault, 5 | RouteOptions, 6 | } from 'fastify'; 7 | import { ViewHistory as ViewHistoryQuery } from '#/params'; 8 | import Viewhistory from '/@/apis/core/database/sqlite/models/viewhistory'; 9 | import { URL } from 'url'; 10 | 11 | export default function recordClick() { 12 | const route: RouteOptions< 13 | RawServerDefault, 14 | RawRequestDefaultExpression, 15 | RawReplyDefaultExpression, 16 | { 17 | Body: ViewHistoryQuery.RecordClick; 18 | } 19 | > = { 20 | url: '/viewhistory/recordClick', 21 | method: 'PUT', 22 | schema: { 23 | body: ViewHistoryQuery.RecordClick, 24 | }, 25 | handler: async (request) => { 26 | const lastclick = request.body.lastclickTitle + '@@' + request.body.lastclickIndex; 27 | 28 | let url = request.body.url; 29 | if (url.startsWith('hiker://page')) { 30 | try { 31 | const url2 = new URL(url); 32 | 33 | url = 34 | url2.searchParams 35 | .get('url') 36 | ?.replace(/;;/g, ';') 37 | .replace(/&&/g, '&') 38 | .replace(/??/g, '?') || url; 39 | } catch (e) {} 40 | } 41 | 42 | const viewHistory = await Viewhistory.findOne({ 43 | where: { 44 | title: request.body.title, 45 | url: url, 46 | }, 47 | }); 48 | 49 | await viewHistory?.update({ 50 | lastclick: lastclick, 51 | }); 52 | return '成功'; 53 | }, 54 | errorHandler: (error) => { 55 | console.error(error); 56 | return error; 57 | }, 58 | }; 59 | 60 | return route; 61 | } 62 | -------------------------------------------------------------------------------- /packages/renderer/src/components/AirColComponent/Pic1.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 36 | 37 | 56 | -------------------------------------------------------------------------------- /packages/main/src/apis/core/utils/cloudShearPlate.ts: -------------------------------------------------------------------------------- 1 | import { CLOUD_SHEAR_PLATE_MAP } from '#/config'; 2 | import axios from 'axios'; 3 | import cheerio from 'cheerio'; 4 | import htmlparser2 from 'htmlparser2'; 5 | 6 | export class CloudShearPlate { 7 | public type: CloudShearPlateType | null = null; 8 | constructor(public readonly url: string) { 9 | for (const type in CLOUD_SHEAR_PLATE_MAP) { 10 | if (new RegExp(CLOUD_SHEAR_PLATE_MAP[type].validator, 'g').test(url)) { 11 | this.type = type as CloudShearPlateType; 12 | } 13 | } 14 | } 15 | 16 | getRes(): Promise { 17 | switch (this.type) { 18 | case 'netcut.cn': 19 | return this.getCloud2(); 20 | case 'cmd.im': 21 | return this.getCloud5(); 22 | case 'pasteme.tyrantg.com': 23 | return this.getCloud6(); 24 | default: 25 | throw new Error('不存在的云剪切板'); 26 | } 27 | } 28 | 29 | private getCloud2(): Promise { 30 | return axios 31 | .get('http://netcut.cn/api/note/data/?note_id=' + this.url.split('/p/')[1]) 32 | .then((res) => { 33 | if (res.data.error) throw new Error(res.data.error); 34 | if (res.data && res.data.data) { 35 | return res.data.data.note_content; 36 | } 37 | }); 38 | } 39 | 40 | private getCloud5(): Promise { 41 | return axios.get(this.url).then((res) => { 42 | const dom = htmlparser2.parseDocument(res.data); 43 | const $ = cheerio.load(dom); 44 | return $('.test_box').text(); 45 | }); 46 | } 47 | 48 | private getCloud6(): Promise { 49 | return axios.get(this.url.replace('xxxxxx', 'api/getContent')).then((res) => { 50 | if (res.data && res.data.result_code === 'SUCCESS') { 51 | return res.data.data; 52 | } 53 | }); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /packages/main/src/server/components/articlelistrule/routes/import.ts: -------------------------------------------------------------------------------- 1 | import { 2 | RawReplyDefaultExpression, 3 | RawRequestDefaultExpression, 4 | RawServerDefault, 5 | RouteOptions, 6 | } from 'fastify'; 7 | import { Articlelistrule as ArticlelistruleQuery } from '#/params'; 8 | import { getPasswordRuleType, isCloudShearPlate } from '/@/apis/core/utils/password'; 9 | import { ImportRule } from '/@/apis/core/utils/importRule'; 10 | import { PASSWORD_SIGN } from '#/config'; 11 | import { CloudShearPlate } from '/@/apis/core/utils/cloudShearPlate'; 12 | 13 | export default function importRule() { 14 | const route: RouteOptions< 15 | RawServerDefault, 16 | RawRequestDefaultExpression, 17 | RawReplyDefaultExpression, 18 | { 19 | Body: ArticlelistruleQuery.ImportRule; 20 | } 21 | > = { 22 | url: '/articlelistrule/import', 23 | method: 'POST', 24 | schema: { 25 | body: ArticlelistruleQuery.ImportRule, 26 | }, 27 | handler: async (request) => { 28 | if (!request.body.password) throw new Error('规则不能为空'); 29 | let password = request.body.password; 30 | const isCloud = isCloudShearPlate(password); 31 | if (isCloud) { 32 | const cloudShearPlate = new CloudShearPlate(password.split('\n')[0]); 33 | password = await cloudShearPlate.getRes(); 34 | } 35 | const type = getPasswordRuleType(password); 36 | const rule = password.match(new RegExp(`.*${PASSWORD_SIGN[type].sign}(.*)`))![1] || ''; 37 | const importRule = new ImportRule(type, rule); 38 | return importRule.import().then((res) => { 39 | return { 40 | type: importRule.type, 41 | data: res, 42 | }; 43 | }); 44 | }, 45 | errorHandler: (error) => { 46 | console.error(error); 47 | return error; 48 | }, 49 | }; 50 | 51 | return route; 52 | } 53 | -------------------------------------------------------------------------------- /packages/renderer/src/components/AirColComponent/Movie3.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 25 | 26 | 69 | -------------------------------------------------------------------------------- /packages/renderer/build/script/buildConf.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Generate additional configuration files when used for packaging. The file can be configured with some global variables, so that it can be changed directly externally without repackaging 3 | */ 4 | import { GLOB_CONFIG_FILE_NAME, OUTPUT_DIR } from '../constant'; 5 | import fs, { writeFileSync } from 'fs-extra'; 6 | import chalk from 'chalk'; 7 | 8 | import { getRootPath, getEnvConfig } from '../utils'; 9 | import { getConfigFileName } from '../getConfigFileName'; 10 | 11 | import pkg from '../../../../package.json'; 12 | 13 | function createConfig( 14 | { 15 | configName, 16 | config, 17 | configFileName = GLOB_CONFIG_FILE_NAME, 18 | }: { configName: string; config: any; configFileName?: string } = { configName: '', config: {} } 19 | ) { 20 | try { 21 | const windowConf = `window.${configName}`; 22 | // Ensure that the variable will not be modified 23 | const configStr = `${windowConf}=${JSON.stringify(config)}; 24 | Object.freeze(${windowConf}); 25 | Object.defineProperty(window, "${configName}", { 26 | configurable: false, 27 | writable: false, 28 | }); 29 | `.replace(/\s/g, ''); 30 | console.log(getRootPath(OUTPUT_DIR)); 31 | fs.mkdirp(getRootPath(OUTPUT_DIR)); 32 | writeFileSync(getRootPath(`${OUTPUT_DIR}/${configFileName}`), configStr); 33 | 34 | console.log(chalk.cyan(`✨ [${pkg.name}]`) + ` - configuration file is build successfully:`); 35 | console.log(chalk.gray(OUTPUT_DIR + '/' + chalk.green(configFileName)) + '\n'); 36 | } catch (error) { 37 | console.log(chalk.red('configuration file configuration file failed to package:\n' + error)); 38 | } 39 | } 40 | 41 | export function runBuildConfig() { 42 | const config = getEnvConfig(); 43 | const configFileName = getConfigFileName(config); 44 | 45 | createConfig({ config, configName: configFileName }); 46 | } 47 | -------------------------------------------------------------------------------- /packages/main/src/apis/core/database/sqlite/models/viewhistory.ts: -------------------------------------------------------------------------------- 1 | import { DataTypes, InferAttributes, Model } from 'sequelize'; 2 | import sequelize from '/@/apis/core/database/sqlite/models/sequelize'; 3 | 4 | class ViewHistory extends Model> { 5 | declare id?: number; 6 | declare lastclick?: string; 7 | declare params?: string; 8 | declare rulebaseurl?: string; 9 | declare time: number; 10 | declare title: string; 11 | declare type: string; 12 | declare url: string; 13 | declare videourl?: string; 14 | declare group_lpcolumn?: string; 15 | declare picurl?: string; 16 | declare extradata?: string; 17 | } 18 | 19 | ViewHistory.init( 20 | { 21 | id: { 22 | type: DataTypes.INTEGER, 23 | allowNull: true, 24 | primaryKey: true, 25 | autoIncrement: true, 26 | }, 27 | lastclick: { 28 | type: DataTypes.TEXT, 29 | allowNull: true, 30 | }, 31 | params: { 32 | type: DataTypes.TEXT, 33 | allowNull: true, 34 | }, 35 | rulebaseurl: { 36 | type: DataTypes.TEXT, 37 | allowNull: true, 38 | }, 39 | time: { 40 | type: DataTypes.INTEGER, 41 | allowNull: true, 42 | }, 43 | title: { 44 | type: DataTypes.TEXT, 45 | allowNull: true, 46 | }, 47 | type: { 48 | type: DataTypes.TEXT, 49 | allowNull: true, 50 | }, 51 | url: { 52 | type: DataTypes.TEXT, 53 | allowNull: true, 54 | }, 55 | videourl: { 56 | type: DataTypes.TEXT, 57 | allowNull: true, 58 | }, 59 | group_lpcolumn: { 60 | type: DataTypes.TEXT, 61 | allowNull: true, 62 | }, 63 | picurl: { 64 | type: DataTypes.TEXT, 65 | allowNull: true, 66 | }, 67 | extradata: { 68 | type: DataTypes.TEXT, 69 | allowNull: true, 70 | }, 71 | }, 72 | { 73 | sequelize, 74 | tableName: 'viewhistory', 75 | timestamps: false, 76 | } 77 | ); 78 | 79 | export default ViewHistory; 80 | -------------------------------------------------------------------------------- /packages/renderer/src/components/AirColComponent/Movie1.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 32 | 33 | 75 | -------------------------------------------------------------------------------- /packages/renderer/src/components/ViewHistory/ViewHistoryItem.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 51 | 52 | 62 | -------------------------------------------------------------------------------- /packages/shared/config/index.ts: -------------------------------------------------------------------------------- 1 | export const PASSWORD_SIGN: { 2 | [key in PasswordSignType]: { 3 | sign: string; 4 | name: string; 5 | }; 6 | } = { 7 | ad_subscribe_url: { 8 | sign: '¥ad_subscribe_url¥', 9 | name: '订阅链接', 10 | }, 11 | ad_url_rule: { 12 | sign: '¥ad_subscribe_url¥', 13 | name: '广告网址拦截', 14 | }, 15 | bookmark: { 16 | sign: '¥bookmark¥', 17 | name: '书签规则', 18 | }, 19 | bookmark_url: { 20 | sign: '¥bookmark_url¥', 21 | name: '书签规则', 22 | }, 23 | fast_play_urls: { 24 | sign: '¥fast_play_urls¥', 25 | name: '快速播放白名单', 26 | }, 27 | file_url: { 28 | sign: '¥file_url¥', 29 | name: '本地文件', 30 | }, 31 | home_sub: { 32 | sign: '¥home_sub¥', 33 | name: '合集规则订阅', 34 | }, 35 | js_url: { 36 | sign: '¥js_url¥', 37 | name: '网页插件', 38 | }, 39 | publish_account: { 40 | sign: '¥publish_account¥', 41 | name: '云仓库账号密码设置', 42 | }, 43 | search_engine_url: { 44 | sign: '¥search_engine_url¥', 45 | name: '搜索引擎合集', 46 | }, 47 | search_engine_v2: { 48 | sign: '¥search_engine_v2¥', 49 | name: '搜索引擎', 50 | }, 51 | xt_dialog_rules: { 52 | sign: '¥xt_dialog_rules¥', 53 | name: '嗅探弹窗黑名单', 54 | }, 55 | home_rule_url: { 56 | sign: '¥home_rule_url¥', 57 | name: '首页频道合集', 58 | }, 59 | home_rule: { 60 | sign: '¥home_rule¥', 61 | name: '首页频道', 62 | }, 63 | home_rule_v2: { 64 | sign: '¥home_rule_v2¥', 65 | name: '小程序', 66 | }, 67 | }; 68 | 69 | export const CLOUD_SHEAR_PLATE_MAP: { 70 | [key in CloudShearPlateType]: { 71 | name: string; 72 | validator: string; 73 | }; 74 | } = { 75 | 'netcut.cn': { 76 | name: '云剪贴板分享2', 77 | validator: 'http(s?):\\/\\/netcut.cn\\/p\\/.*', 78 | }, 79 | 'cmd.im': { 80 | name: '云剪贴板分享5', 81 | validator: 'http(s?):\\/\\/cmd.im\\/.*', 82 | }, 83 | 'pasteme.tyrantg.com': { 84 | name: '云剪贴板分享6', 85 | validator: 'http(s?):\\/\\/pasteme.tyrantg.com\\/xxxxxx/.*', 86 | }, 87 | }; 88 | -------------------------------------------------------------------------------- /packages/renderer/src/components/AirColComponent/Movie1VerticalPic.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 28 | 29 | 71 | -------------------------------------------------------------------------------- /packages/shared/utils/url.ts: -------------------------------------------------------------------------------- 1 | import { Headers, Method } from 'got'; 2 | 3 | export function getMyUrl(protoUrl: string, fyPageParams: FyPageParams) { 4 | const { 5 | fyclass = '', 6 | fyarea = '', 7 | fyyear = '', 8 | fysort = '', 9 | fypage = '', 10 | fyAll = '', 11 | search = '', 12 | } = fyPageParams; 13 | return protoUrl 14 | .replace(/fyclass/g, fyclass) 15 | .replace(/fyarea/g, fyarea) 16 | .replace(/fyyear/g, fyyear) 17 | .replace(/fysort/g, fysort) 18 | .replace(/fypage/g, fypage) 19 | .replace(/fyAll/g, fyAll) 20 | .replace(/\*\*/g, search); 21 | } 22 | 23 | export function splitProtoUrl(protoUrl: string, fyPageParams: FyPageParams): UrlConfig { 24 | let url: string; 25 | let method: string; 26 | let encoding: string; 27 | let headers: any; 28 | // eslint-disable-next-line prefer-const 29 | [url, method = 'GET', encoding = 'utf-8', headers = '{User-Agent@air/1.0.0}'] = 30 | protoUrl.split(';'); 31 | url = getMyUrl(url, fyPageParams); 32 | try { 33 | headers = headers 34 | .match(/\{(.+?)\}$/)[1] 35 | .replace(/;;/g, ';') 36 | .split('&&') 37 | .map((item) => item.split('@')) 38 | .reduce((acc, cur) => { 39 | acc[cur[0]] = cur[1]; 40 | return acc; 41 | }, {}); 42 | } catch (error) { 43 | console.error(error); 44 | headers = { 45 | 'user-agent': 'air/1.0.0', 46 | } as Headers; 47 | } 48 | let params = ''; 49 | let data: any = undefined; 50 | if (url.includes('?')) { 51 | [url, params = ''] = url.split('?'); 52 | } 53 | if (['post', 'put'].includes(method.toLowerCase())) { 54 | [url, data = ''] = url.split('?'); 55 | data = data 56 | .split('&') 57 | .map((item) => item.split('=')) 58 | .reduce((acc, cur) => { 59 | acc[cur[0]] = cur[1]; 60 | return acc; 61 | }, {}); 62 | } 63 | return { 64 | url: url, 65 | params, 66 | data, 67 | method: method as Method, 68 | encoding, 69 | headers: headers, 70 | }; 71 | } 72 | -------------------------------------------------------------------------------- /packages/renderer/src/components/AirColComponent/Movie1LeftPic.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 32 | 33 | 75 | -------------------------------------------------------------------------------- /packages/renderer/build/utils.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import path from 'path'; 3 | import dotenv from 'dotenv'; 4 | 5 | export function isDevFn(mode: string): boolean { 6 | return mode === 'development'; 7 | } 8 | 9 | export function isProdFn(mode: string): boolean { 10 | return mode === 'production'; 11 | } 12 | 13 | /** 14 | * Whether to generate package preview 15 | */ 16 | export function isReportMode(): boolean { 17 | return process.env.REPORT === 'true'; 18 | } 19 | 20 | // Read all environment variable configuration files to process.env 21 | export function wrapperEnv(envConf: Recordable): ViteEnv { 22 | const ret: any = {}; 23 | 24 | for (const envName of Object.keys(envConf)) { 25 | let realName = envConf[envName].replace(/\\n/g, '\n'); 26 | realName = realName === 'true' ? true : realName === 'false' ? false : realName; 27 | 28 | if (envName === 'VITE_PORT') { 29 | realName = Number(realName); 30 | } 31 | if (envName === 'VITE_PROXY') { 32 | try { 33 | realName = JSON.parse(realName); 34 | } catch (error) {} 35 | } 36 | ret[envName] = realName; 37 | process.env[envName] = realName; 38 | } 39 | return ret; 40 | } 41 | 42 | /** 43 | * Get the environment variables starting with the specified prefix 44 | * @param match prefix 45 | * @param confFiles ext 46 | */ 47 | export function getEnvConfig(match = 'VITE_GLOB_', confFiles = ['.env', '.env.production']) { 48 | let envConfig = {}; 49 | confFiles.forEach((item) => { 50 | try { 51 | const env = dotenv.parse(fs.readFileSync(path.resolve(process.cwd(), '../../' + item))); 52 | envConfig = { ...envConfig, ...env }; 53 | } catch (error) {} 54 | }); 55 | 56 | Object.keys(envConfig).forEach((key) => { 57 | const reg = new RegExp(`^(${match})`); 58 | if (!reg.test(key)) { 59 | Reflect.deleteProperty(envConfig, key); 60 | } 61 | }); 62 | return envConfig; 63 | } 64 | 65 | /** 66 | * Get user root directory 67 | * @param dir file path 68 | */ 69 | export function getRootPath(...dir: string[]) { 70 | return path.resolve(process.cwd(), ...dir); 71 | } 72 | -------------------------------------------------------------------------------- /packages/renderer/src/views/setting/components/ImportBackupModal.vue: -------------------------------------------------------------------------------- 1 | 54 | 55 | 77 | -------------------------------------------------------------------------------- /packages/main/src/apis/core/air/dav.ts: -------------------------------------------------------------------------------- 1 | import * as webdav from 'webdav'; 2 | import air from '/@/apis/core/air/index'; 3 | import { FileStat } from 'webdav/dist/node/types'; 4 | import { Notification } from 'electron'; 5 | import { IDav } from '/@/apis/core/air/types'; 6 | import dayjs from 'dayjs'; 7 | import { importBackup } from '/@/apis/core/utils'; 8 | 9 | export default class Dav implements IDav { 10 | client: webdav.WebDAVClient; 11 | 12 | constructor() { 13 | this.client = webdav.createClient(air.getConfig('settings.webdav.remoteURL'), { 14 | username: air.getConfig('settings.webdav.username'), 15 | password: air.getConfig('settings.webdav.password'), 16 | }); 17 | } 18 | 19 | async sync(): Promise<{ 20 | status: 'success' | 'fail'; 21 | message: string; 22 | }> { 23 | let status: 'success' | 'fail' = 'success'; 24 | const messageArr: string[] = []; 25 | 26 | try { 27 | if (!(await this.client.exists('/hiker'))) { 28 | await this.client.createDirectory('/hiker'); 29 | } 30 | const directoryItems = (await this.client.getDirectoryContents('/hiker', { 31 | deep: true, 32 | glob: '/**/*.zip', 33 | })) as Array; 34 | const fileItems = directoryItems 35 | .filter((item) => item.mime === 'application/zip') 36 | .sort((a, b) => dayjs(b.lastmod).unix() - dayjs(a.lastmod).unix()); 37 | if (fileItems.length === 0) { 38 | return { 39 | status, 40 | message: '没有需要同步的文件', 41 | }; 42 | } 43 | const buff: Buffer = (await this.client.getFileContents(fileItems[0].filename)) as Buffer; 44 | messageArr.push(...importBackup(buff)); 45 | } catch (error: any) { 46 | status = 'fail'; 47 | console.error(error); 48 | air.log.error(error); 49 | const notification = new Notification({ 50 | title: '同步失败', 51 | body: error.message, 52 | }); 53 | notification.show(); 54 | messageArr.push(error.message); 55 | } 56 | 57 | return { 58 | status, 59 | message: messageArr.join(','), 60 | }; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /scripts/bytenode.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: Allan 3 | * @Description: 加密源码 4 | */ 5 | const { app } = require('electron'); 6 | const v8 = require('v8'); 7 | const bytenode = require('bytenode'); 8 | const fs = require('fs'); 9 | const path = require('path'); 10 | 11 | v8.setFlagsFromString('--no-lazy'); 12 | 13 | const distPaths = ['../packages/main/dist/']; 14 | let extNew = '.jsc'; 15 | 16 | function startByteCode() { 17 | //开始加密 18 | const totalTimeLabel = '总加密用时'; 19 | console.time(totalTimeLabel); 20 | 21 | for (const disPath of distPaths) { 22 | const rootPath = path.join(__dirname, disPath); 23 | console.group('加密目录:', rootPath); 24 | const timeLabel = 'Bundling time'; 25 | console.time(timeLabel); 26 | 27 | const filenames = fs.readdirSync(rootPath); 28 | filenames.forEach((filename) => { 29 | let ext = path.extname(filename); 30 | let base = path.basename(filename, ext); 31 | if (ext === '.js' || (ext === '.cjs' && base !== 'esm-got')) { 32 | // if (base === 'index') { 33 | let filePath = path.join(rootPath, filename); 34 | let fileNameOut = base + extNew; 35 | let filePathOut = path.join(rootPath, fileNameOut); 36 | console.log('file: ' + filePath); 37 | //字节码转化 38 | bytenode 39 | .compileFile({ 40 | filename: filePath, 41 | output: filePathOut, 42 | }) 43 | .then(() => { 44 | //替换原文件代码,导入引用 45 | fs.writeFileSync( 46 | filePath, 47 | `require('bytenode');module.exports = require('./${fileNameOut}');` 48 | ); 49 | //删除loader.js文件 50 | let fileNameLoader = base + '.loader.js'; 51 | let filePathLoader = path.join(rootPath, fileNameLoader); 52 | if (fs.existsSync(filePathLoader)) { 53 | fs.unlinkSync(filePathLoader); 54 | } 55 | }); 56 | } 57 | }); 58 | console.timeEnd(timeLabel); 59 | console.groupEnd(); 60 | console.log('\n'); 61 | } 62 | console.timeEnd(totalTimeLabel); 63 | } 64 | 65 | startByteCode(); 66 | 67 | app.quit(); 68 | -------------------------------------------------------------------------------- /packages/main/src/events/ipcList.ts: -------------------------------------------------------------------------------- 1 | import airCoreIPC from '/@/events/airCoreIPC'; 2 | import { ipcMain, BrowserWindow, session, Notification } from 'electron'; 3 | import { 4 | CLOSE_WINDOW, 5 | IS_MAXIMIZE_WINDOW, 6 | MAX_RESTORE_WINDOW, 7 | MINIMIZE_WINDOW, 8 | SET_DEFAULT_HEADERS, 9 | UPDATE_REQUEST_HEADERS, 10 | } from '#/events/constants'; 11 | import { setDefaultHeaders } from '/@/utils'; 12 | 13 | export default { 14 | listen() { 15 | airCoreIPC.listen(); 16 | 17 | ipcMain.on(MINIMIZE_WINDOW, () => { 18 | const window = BrowserWindow.getFocusedWindow(); 19 | window?.minimize(); 20 | }); 21 | 22 | ipcMain.handle(IS_MAXIMIZE_WINDOW, () => { 23 | const window = BrowserWindow.getFocusedWindow(); 24 | const result = window?.isMaximized(); 25 | return result; 26 | }); 27 | 28 | ipcMain.on(MAX_RESTORE_WINDOW, () => { 29 | const window = BrowserWindow.getFocusedWindow(); 30 | const isMaximized = window?.isMaximized(); 31 | isMaximized ? window?.unmaximize() : window?.maximize(); 32 | }); 33 | 34 | ipcMain.on(CLOSE_WINDOW, () => { 35 | const window = BrowserWindow.getFocusedWindow(); 36 | if (process.platform === 'linux') { 37 | window?.hide(); 38 | } else { 39 | window?.close(); 40 | } 41 | }); 42 | 43 | ipcMain.on(UPDATE_REQUEST_HEADERS, (event, filter, headers) => { 44 | try { 45 | session.defaultSession.webRequest.onBeforeSendHeaders(filter, (details, callback) => { 46 | Object.keys(headers).forEach((key) => { 47 | details.requestHeaders[key] = headers[key]; 48 | }); 49 | const requestHeaders = { requestHeaders: details.requestHeaders }; 50 | callback(requestHeaders); 51 | }); 52 | } catch (e: any) { 53 | const notification = new Notification({ 54 | title: '错误', 55 | body: '糟糕...发生了一些错误,可能是 headers 有误', 56 | }); 57 | notification.show(); 58 | throw new Error(e); 59 | } 60 | }); 61 | 62 | ipcMain.on(SET_DEFAULT_HEADERS, () => { 63 | setDefaultHeaders(); 64 | }); 65 | }, 66 | dispose() {}, 67 | }; 68 | -------------------------------------------------------------------------------- /packages/renderer/src/api/parse.ts: -------------------------------------------------------------------------------- 1 | import http from '/@/utils/http/axios'; 2 | import { Parse as ParseQuery } from '#/params'; 3 | 4 | export interface IGetRuleDetailResultParams { 5 | type: 'search' | 'home'; 6 | id: number; 7 | url: string; 8 | vars: { [key: string]: any }; 9 | } 10 | 11 | export interface IGetLazyRuleResultParams { 12 | type: 'search' | 'home'; 13 | id: number; 14 | url: string; 15 | lazyRule: string; 16 | vars: { [key: string]: any }; 17 | } 18 | 19 | // 解析搜索规则 20 | export function getSearchRuleResult(params: ParseQuery.GetSearchRuleResult) { 21 | return http.request({ 22 | url: '/parse/getSearchRuleResult', 23 | params, 24 | method: 'get', 25 | }); 26 | } 27 | 28 | // 解析二级页面 29 | export function getRuleDetailResult(params: ParseQuery.GetRuleDetailResult) { 30 | return http.request({ 31 | url: '/parse/getRuleDetailResult', 32 | params, 33 | method: 'get', 34 | }); 35 | } 36 | 37 | // 动态解析 38 | export function getLazyRuleResult(params: ParseQuery.GetLazyRuleResult) { 39 | return http.request({ 40 | url: '/parse/getLazyRuleResult', 41 | params, 42 | method: 'post', 43 | }); 44 | } 45 | 46 | // 解析子页面 47 | export function getChildPageRuleResult(params: ParseQuery.GetChildPageRuleResult) { 48 | return http.request({ 49 | url: '/parse/getChildPageRuleResult', 50 | data: params, 51 | method: 'post', 52 | }); 53 | } 54 | 55 | // 预处理 56 | export function preHandle(params: ParseQuery.PreHandle) { 57 | return http.request({ 58 | url: '/parse/preHandle', 59 | params, 60 | method: 'put', 61 | }); 62 | } 63 | 64 | // 解析规则 65 | export function getRuleResult(params: ParseQuery.GetRuleResult) { 66 | return http.request({ 67 | url: '/parse/getRuleResult', 68 | data: params, 69 | method: 'post', 70 | }); 71 | } 72 | 73 | // 解析自定义规则(比如历史记录) 74 | export function getCustomRuleResult(params: ParseQuery.GetCustomRuleResult) { 75 | return http.request({ 76 | url: '/parse/getCustomRuleResult', 77 | data: params, 78 | method: 'post', 79 | }); 80 | } 81 | -------------------------------------------------------------------------------- /packages/renderer/src/components/DetailPanelList/DetailPanelList.vue: -------------------------------------------------------------------------------- 1 | 68 | 69 | 79 | -------------------------------------------------------------------------------- /packages/renderer/vite.config.js: -------------------------------------------------------------------------------- 1 | /* eslint-env node */ 2 | import { loadEnv } from 'vite'; 3 | import { chrome } from '../../electron-vendors.config.json'; 4 | import { join } from 'path'; 5 | import { builtinModules } from 'module'; 6 | import { wrapperEnv } from './build/utils'; 7 | import { createVitePlugins } from './build/vite/plugin'; 8 | import { OUTPUT_DIR } from './build/constant'; 9 | import pkg from '../../package.json'; 10 | import { format } from 'date-fns'; 11 | 12 | const { dependencies, devDependencies, name, version } = pkg; 13 | 14 | const __APP_INFO__ = { 15 | pkg: { dependencies, devDependencies, name, version }, 16 | lastBuildTime: format(new Date(), 'yyyy-MM-dd HH:mm:ss'), 17 | }; 18 | 19 | const PACKAGE_ROOT = __dirname; 20 | 21 | /** 22 | * @type {({ command, mode }: import('vite').ConfigEnv) => import('vite').UserConfig} 23 | * @see https://vitejs.dev/config/ 24 | */ 25 | export default ({ command, mode }) => { 26 | const root = process.cwd(); 27 | const env = loadEnv(mode, root); 28 | const viteEnv = wrapperEnv(env); 29 | const { VITE_PUBLIC_PATH, VITE_DROP_CONSOLE, VITE_PORT } = viteEnv; 30 | const isBuild = command === 'build'; 31 | return { 32 | root: PACKAGE_ROOT, 33 | resolve: { 34 | alias: { 35 | '/@/': join(PACKAGE_ROOT, 'src') + '/', 36 | '#/': join(PACKAGE_ROOT, '../shared') + '/', 37 | '~assets': join(PACKAGE_ROOT, 'assets') + '/', 38 | }, 39 | }, 40 | plugins: createVitePlugins(viteEnv, isBuild), 41 | define: { 42 | __APP_INFO__: JSON.stringify(__APP_INFO__), 43 | }, 44 | base: VITE_PUBLIC_PATH, 45 | server: { 46 | port: VITE_PORT, 47 | fs: { 48 | strict: true, 49 | }, 50 | }, 51 | build: { 52 | sourcemap: true, 53 | target: `chrome${chrome}`, 54 | outDir: OUTPUT_DIR, 55 | assetsDir: '.', 56 | terserOptions: { 57 | compress: { 58 | keep_infinity: true, 59 | drop_console: VITE_DROP_CONSOLE, 60 | }, 61 | }, 62 | rollupOptions: { 63 | input: join(PACKAGE_ROOT, 'index.html'), 64 | external: [...builtinModules], 65 | }, 66 | emptyOutDir: true, 67 | brotliSize: false, 68 | }, 69 | }; 70 | }; 71 | -------------------------------------------------------------------------------- /packages/renderer/src/components/AirColComponent/Movie2.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 28 | 29 | 85 | -------------------------------------------------------------------------------- /packages/main/src/apis/core/air/utils/airVmWorker.ts: -------------------------------------------------------------------------------- 1 | import AirVm, { VmType } from './airVm'; 2 | import { MessagePort } from 'worker_threads'; 3 | import * as socketConstList from '#/events/socket-constants'; 4 | import util from 'util'; 5 | import dayjs from 'dayjs'; 6 | import { IPages } from '/@/apis/core/air/parse'; 7 | import Articlelistrule from '/@/apis/core/database/sqlite/models/articlelistrule'; 8 | 9 | export interface AirVmContext { 10 | articlelistrule: Articlelistrule; 11 | home: any; 12 | baseUrl: string | undefined; 13 | myUrl: string; 14 | vars: { [key: string]: any }; 15 | allMyVars: { [key: string]: { [key: string]: any } }; 16 | colType: ColType; 17 | pages: IPages[]; 18 | allConfig: { [key: string]: { [key: string]: any } }; 19 | config: { [key: string]: any }; 20 | myVars: { [key: string]: any }; 21 | } 22 | 23 | export interface IAirVmWorkerParams { 24 | port: MessagePort; 25 | code: string; 26 | vmType: VmType; 27 | context: AirVmContext; 28 | documentsDir: string; 29 | sandbox?: any; 30 | rescode?: string; 31 | } 32 | 33 | export default async (params: IAirVmWorkerParams) => { 34 | const { port, code, vmType, documentsDir, context, sandbox = {}, rescode } = params; 35 | 36 | const airVm = new AirVm( 37 | vmType, 38 | { 39 | rescode, 40 | documentsDir, 41 | }, 42 | context, 43 | sandbox 44 | ); 45 | 46 | for (const key in socketConstList) { 47 | const ev = socketConstList[key]; 48 | airVm.on(ev, (...data) => { 49 | port.postMessage({ 50 | ev: ev, 51 | data, 52 | }); 53 | }); 54 | } 55 | 56 | try { 57 | const result = await airVm.vm?.run(code); 58 | return { 59 | AIR_RESULT: JSON.parse(JSON.stringify(airVm.sandbox.AIR_RESULT)), 60 | result, 61 | context: { 62 | vars: airVm.context.vars, 63 | allConfig: airVm.context.allConfig, 64 | allMyVars: airVm.context.allMyVars, 65 | }, 66 | error: null, 67 | }; 68 | } catch (e: any) { 69 | let log = `${dayjs().format('YYYY-MM-DD HH:mm:ss')} [AirVm Error] `; 70 | log += `\n------Error Stack Begin------\n${util.format( 71 | e.stack 72 | )}\n-------Error Stack End------- `; 73 | console.error(log); 74 | throw new Error(e.message); 75 | } 76 | }; 77 | -------------------------------------------------------------------------------- /packages/main/src/server/components/parse/routes/getCustomRuleResult.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | RawReplyDefaultExpression, 3 | RawRequestDefaultExpression, 4 | RawServerDefault, 5 | RouteOptions, 6 | } from 'fastify'; 7 | import articlelistrule from '/@/apis/core/database/sqlite/models/articlelistrule'; 8 | import AirParse from '/@/apis/core/air/parse'; 9 | import { Parse as ParseQuery } from '#/params'; 10 | import { getMyUrl, splitProtoUrl } from '#/utils'; 11 | import { nanoid } from 'nanoid'; 12 | 13 | export default function getRoute() { 14 | const route: RouteOptions< 15 | RawServerDefault, 16 | RawRequestDefaultExpression, 17 | RawReplyDefaultExpression, 18 | { 19 | Body: ParseQuery.GetCustomRuleResult; 20 | } 21 | > = { 22 | url: '/parse/getCustomRuleResult', 23 | method: 'POST', 24 | schema: { 25 | body: ParseQuery.GetCustomRuleResult, 26 | }, 27 | handler: async (request) => { 28 | const rule = request.body; 29 | 30 | const home = await articlelistrule.findAll(); 31 | 32 | const airParse = new AirParse(rule as any, home, (request as any).session || {}); 33 | const parseCode = rule.find_rule; 34 | const { url, params, data, encoding, method = 'GET', headers } = splitProtoUrl(rule?.url, {}); 35 | const myUrl = getMyUrl(rule?.url, {}); 36 | return airParse 37 | .parseCustomRule({ 38 | myUrl, 39 | url, 40 | params, 41 | method, 42 | data, 43 | parseCode, 44 | encoding, 45 | headers, 46 | preParseCode: rule.preRule, 47 | }) 48 | .then((res) => 49 | res.map((item) => ({ 50 | ...item, 51 | col_type: item.col_type ? item.col_type : request.body.col_type, 52 | })) 53 | ) 54 | .then((res) => { 55 | return res.map((item) => ({ ...item, id: nanoid() })); 56 | }) 57 | .finally(() => { 58 | (request as any).session.vars = airParse.vars; 59 | (request as any).session.allConfig = airParse.allConfig; 60 | (request as any).session.allMyVars = airParse.allMyVars; 61 | }); 62 | }, 63 | errorHandler: (error) => { 64 | console.error(error); 65 | return error; 66 | }, 67 | }; 68 | 69 | return route; 70 | } 71 | -------------------------------------------------------------------------------- /packages/renderer/src/utils/playUtils.ts: -------------------------------------------------------------------------------- 1 | import iinaImg from '/@/assets/images/player/iina.png'; 2 | import potplayerImg from '/@/assets/images/player/potplayer.png'; 3 | import vlcImg from '/@/assets/images/player/vlc.png'; 4 | import Cast2TvImg from '/@/assets/images/player/preview.png'; 5 | import StellarImg from '/@/assets/images/player/stellar.png'; 6 | // import ThunderImg from '/@/assets/images/player/preview.png'; 7 | 8 | export const srt2vtt = (s) => 9 | 'WEBVTT FILE\r\n\r\n' + 10 | s 11 | .replace(/\{\\([ibu])\}/g, '') 12 | .replace(/\{\\([ibu])1\}/g, '<$1>') 13 | .replace(/\{([ibu])\}/g, '<$1>') 14 | .replace(/\{\/([ibu])\}/g, '') 15 | .replace(/(\d\d:\d\d:\d\d),(\d\d\d)/g, '$1.$2') 16 | .concat('\r\n\r\n'); 17 | 18 | export function players(url, title) { 19 | return [ 20 | { 21 | name: 'IINA', 22 | icon: iinaImg, 23 | scheme: 'iina://weblink?url=' + url, 24 | }, 25 | { 26 | name: 'PotPlayer', 27 | icon: potplayerImg, 28 | scheme: 'potplayer://' + url, 29 | }, 30 | { 31 | name: 'VLC', 32 | icon: vlcImg, 33 | scheme: 'vlc://' + url, 34 | }, 35 | { 36 | name: 'Cast2Tv', 37 | icon: Cast2TvImg, 38 | scheme: 39 | 'intent:' + url + '#Intent;package=com.instantbits.cast.webvideo;S.title=' + title + ';end', 40 | }, 41 | { 42 | name: '恒星播放器', 43 | icon: StellarImg, 44 | scheme: 'stellar://weblink?url=' + url, 45 | }, 46 | // { 47 | // name: 'Thunder', 48 | // icon: ThunderImg, 49 | // scheme: 'thunder://' + Buffer.from('AA' + url + 'ZZ').toString('base64'), 50 | // }, 51 | // { 52 | // name: 'nPlayer', 53 | // icon: import('/@/assets/images/player/nplayer.png'), 54 | // scheme: 'nplayer-' + url, 55 | // }, 56 | // { 57 | // name: 'MXPlayer(Free)', 58 | // icon: import('/@/assets/images/player/mxplayer.png'), 59 | // scheme: 60 | // 'intent:' + url + '#Intent;package=com.mxtech.videoplayer.ad;S.title=' + title + ';end', 61 | // }, 62 | // { 63 | // name: 'MXPlayer(Pro)', 64 | // icon: import('/@/assets/images/player/mxplayer.png'), 65 | // scheme: 66 | // 'intent:' + url + '#Intent;package=com.mxtech.videoplayer.pro;S.title=' + title + ';end', 67 | // }, 68 | ]; 69 | } 70 | -------------------------------------------------------------------------------- /packages/main/src/events/airCoreIPC.ts: -------------------------------------------------------------------------------- 1 | import { 2 | AIR_GET_CONFIG, 3 | AIR_OPEN_FILE, 4 | AIR_SAVE_CONFIG, 5 | GET_FILE_CONTENT, 6 | IMPORT_BACKUP, 7 | WEBDAV_SYNC, 8 | } from '#/events/constants'; 9 | import { ipcMain, IpcMainEvent, shell, IpcMainInvokeEvent } from 'electron'; 10 | import air from '/@/apis/core/air'; 11 | import path from 'path'; 12 | import fs from 'fs-extra'; 13 | import Dav from '/@/apis/core/air/dav'; 14 | import { dbPathChecker } from '/@/apis/core/datastore/dbChecker'; 15 | import { importBackup } from '/@/apis/core/utils'; 16 | 17 | const STORE_PATH = path.dirname(dbPathChecker()); 18 | 19 | const handleAirSaveConfig = () => { 20 | ipcMain.on(AIR_SAVE_CONFIG, (event: IpcMainEvent, data: IObj) => { 21 | air.saveConfig(data); 22 | }); 23 | }; 24 | 25 | const handleAirGetConfig = () => { 26 | ipcMain.on(AIR_GET_CONFIG, (event: IpcMainEvent, key: string | undefined, callbackId: string) => { 27 | const result = air.getConfig(key); 28 | event.sender.send(AIR_GET_CONFIG, result, callbackId); 29 | }); 30 | }; 31 | 32 | const handleGetFileContent = () => { 33 | ipcMain.on(GET_FILE_CONTENT, (event: IpcMainEvent, fileName: string, callbackId: string) => { 34 | const abFilePath = path.join(STORE_PATH, fileName); 35 | if (fs.existsSync(abFilePath)) { 36 | const content = fs.readFileSync(abFilePath).toString(); 37 | event.sender.send(GET_FILE_CONTENT, content, callbackId); 38 | } else { 39 | event.sender.send(GET_FILE_CONTENT, '', callbackId); 40 | } 41 | }); 42 | }; 43 | 44 | const handleWebdav = () => { 45 | ipcMain.handle(WEBDAV_SYNC, async () => { 46 | const dav = new Dav(); 47 | return dav.sync(); 48 | }); 49 | }; 50 | 51 | const handleOpenFile = () => { 52 | ipcMain.on(AIR_OPEN_FILE, (event: IpcMainEvent, fileName: string) => { 53 | const abFilePath = path.join(STORE_PATH, fileName); 54 | shell.openPath(abFilePath); 55 | }); 56 | }; 57 | 58 | const handleAirImport = () => { 59 | ipcMain.handle(IMPORT_BACKUP, async (event: IpcMainInvokeEvent, filePath: string) => { 60 | return importBackup(filePath); 61 | }); 62 | }; 63 | 64 | export default { 65 | listen() { 66 | handleAirSaveConfig(); 67 | handleAirGetConfig(); 68 | handleGetFileContent(); 69 | handleWebdav(); 70 | handleOpenFile(); 71 | handleAirImport(); 72 | }, 73 | }; 74 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | const { defineConfig } = require('eslint-define-config'); 3 | module.exports = defineConfig({ 4 | root: true, 5 | env: { 6 | browser: true, 7 | node: true, 8 | es6: true, 9 | }, 10 | parser: 'vue-eslint-parser', 11 | parserOptions: { 12 | parser: '@typescript-eslint/parser', 13 | ecmaVersion: 2020, 14 | sourceType: 'module', 15 | jsxPragma: 'React', 16 | ecmaFeatures: { 17 | jsx: true, 18 | }, 19 | }, 20 | extends: [ 21 | 'plugin:vue/vue3-recommended', 22 | 'plugin:@typescript-eslint/recommended', 23 | 'prettier', 24 | 'plugin:prettier/recommended', 25 | ], 26 | rules: { 27 | 'vue/script-setup-uses-vars': 'error', 28 | '@typescript-eslint/ban-ts-ignore': 'off', 29 | '@typescript-eslint/explicit-function-return-type': 'off', 30 | '@typescript-eslint/no-explicit-any': 'off', 31 | '@typescript-eslint/no-var-requires': 'off', 32 | '@typescript-eslint/no-empty-function': 'off', 33 | 'vue/custom-event-name-casing': 'off', 34 | 'no-use-before-define': 'off', 35 | '@typescript-eslint/no-use-before-define': 'off', 36 | '@typescript-eslint/ban-ts-comment': 'off', 37 | '@typescript-eslint/ban-types': 'off', 38 | '@typescript-eslint/no-non-null-assertion': 'off', 39 | '@typescript-eslint/explicit-module-boundary-types': 'off', 40 | '@typescript-eslint/no-unused-vars': [ 41 | 'error', 42 | { 43 | argsIgnorePattern: '^_', 44 | varsIgnorePattern: '^_', 45 | }, 46 | ], 47 | 'no-unused-vars': [ 48 | 'error', 49 | { 50 | argsIgnorePattern: '^_', 51 | varsIgnorePattern: '^_', 52 | }, 53 | ], 54 | 'space-before-function-paren': 'off', 55 | 56 | 'vue/attributes-order': 'off', 57 | 'vue/one-component-per-file': 'off', 58 | 'vue/html-closing-bracket-newline': 'off', 59 | 'vue/max-attributes-per-line': 'off', 60 | 'vue/multiline-html-element-content-newline': 'off', 61 | 'vue/singleline-html-element-content-newline': 'off', 62 | 'vue/attribute-hyphenation': 'off', 63 | 'vue/require-default-prop': 'off', 64 | 'vue/html-self-closing': [ 65 | 'error', 66 | { 67 | html: { 68 | void: 'always', 69 | normal: 'never', 70 | component: 'always', 71 | }, 72 | svg: 'always', 73 | math: 'always', 74 | }, 75 | ], 76 | }, 77 | }); 78 | -------------------------------------------------------------------------------- /packages/renderer/src/plugins/artplayer-plugin-danmuku/README.md: -------------------------------------------------------------------------------- 1 | # artplayer-plugin-danmuku 2 | 3 | Danmuku plugin for ArtPlayer 4 | 5 | ## Demo 6 | 7 | [Checkout the demo](https://artplayer.org/?libs=.%2Funcompiled%2Fartplayer-plugin-danmuku.js&example=danmuku) from Github Pages 8 | 9 | ## Install 10 | 11 | Install with `npm` 12 | 13 | ```bash 14 | $ npm install artplayer-plugin-danmuku 15 | ``` 16 | 17 | Or install with `yarn` 18 | 19 | ```bash 20 | $ yarn add artplayer-plugin-danmuku 21 | ``` 22 | 23 | ```js 24 | import artplayerPluginDanmuku from 'artplayer-plugin-danmuku'; 25 | ``` 26 | 27 | Or umd builds are also available 28 | 29 | ```html 30 | 31 | ``` 32 | 33 | Will expose the global variable to `window.artplayerPluginDanmuku`. 34 | 35 | ## Usage 36 | 37 | ```js 38 | var art = new Artplayer({ 39 | container: '.artplayer-app', 40 | url: 'path/to/video.mp4', 41 | plugins: [ 42 | artplayerPluginDanmuku({ 43 | danmuku: [ 44 | { 45 | text: '666', // Danmu text 46 | time: 5, // Video time 47 | color: '#fff', // Danmu color 48 | border: false, // Danmu border 49 | mode: 0, // Danmu mode: 0-scroll or 1-static 50 | }, 51 | ], // Can be an array or return the promised function or danmuku xml url 52 | speed: 5, // Animation time 53 | opacity: 1, // Opacity 54 | color: '#fff', // Font color 55 | size: 25, // Font size 56 | maxlength: 50, // The maximum number of words in the danmu 57 | margin: [10, 20], // Margin top and margin bottom 58 | synchronousPlayback: false, // Synchronous playback speed 59 | }), 60 | ], 61 | }); 62 | 63 | // Send a new danmu 64 | art.plugins.artplayerPluginDanmuku.emit({ 65 | text: '666', // Danmu text 66 | time: 5, // Video time 67 | color: '#fff', // Danmu color 68 | size: 25, // Danmu size 69 | border: false, // Danmu border 70 | mode: 0, // Danmu mode: 0-scroll or 1-static 71 | }); 72 | 73 | // Hide the danmu 74 | art.plugins.artplayerPluginDanmuku.hide(); 75 | 76 | // Show the danmu 77 | art.plugins.artplayerPluginDanmuku.show(); 78 | 79 | // Returns whether to hide the danmu 80 | art.plugins.artplayerPluginDanmuku.isHide; 81 | 82 | // Config danmu dynamically 83 | art.plugins.artplayerPluginDanmuku.config({ 84 | // option 85 | }); 86 | ``` 87 | 88 | ## License 89 | 90 | MIT © [Harvey Zack](https://sleepy.im/) 91 | -------------------------------------------------------------------------------- /packages/renderer/src/utils/env.ts: -------------------------------------------------------------------------------- 1 | import { warn } from '/@/utils/log'; 2 | import pkg from '../../../../package.json'; 3 | import { getConfigFileName } from '../../build/getConfigFileName'; 4 | 5 | export function getCommonStoragePrefix() { 6 | const { VITE_GLOB_APP_SHORT_NAME } = getAppEnvConfig(); 7 | return `${VITE_GLOB_APP_SHORT_NAME}__${getEnv()}`.toUpperCase(); 8 | } 9 | 10 | // Generate cache key according to version 11 | export function getStorageShortName() { 12 | return `${getCommonStoragePrefix()}${`__${pkg.version}`}__`.toUpperCase(); 13 | } 14 | 15 | export function getAppEnvConfig() { 16 | const ENV_NAME = getConfigFileName(import.meta.env); 17 | 18 | const ENV = (import.meta.env.DEV 19 | ? // Get the global configuration (the configuration will be extracted independently when packaging) 20 | (import.meta.env as unknown as GlobEnvConfig) 21 | : window[ENV_NAME as any]) as unknown as GlobEnvConfig; 22 | 23 | const { 24 | VITE_GLOB_APP_TITLE, 25 | VITE_GLOB_API_URL, 26 | VITE_GLOB_APP_SHORT_NAME, 27 | VITE_GLOB_API_URL_PREFIX, 28 | VITE_GLOB_UPLOAD_URL, 29 | VITE_GLOB_PROD_MOCK, 30 | VITE_GLOB_IMG_URL, 31 | } = ENV; 32 | 33 | if (!/^[a-zA-Z\_]*$/.test(VITE_GLOB_APP_SHORT_NAME)) { 34 | warn( 35 | `VITE_GLOB_APP_SHORT_NAME Variables can only be characters/underscores, please modify in the environment variables and re-running.` 36 | ); 37 | } 38 | 39 | return { 40 | VITE_GLOB_APP_TITLE, 41 | VITE_GLOB_API_URL, 42 | VITE_GLOB_APP_SHORT_NAME, 43 | VITE_GLOB_API_URL_PREFIX, 44 | VITE_GLOB_UPLOAD_URL, 45 | VITE_GLOB_PROD_MOCK, 46 | VITE_GLOB_IMG_URL, 47 | }; 48 | } 49 | 50 | /** 51 | * @description: Development model 52 | */ 53 | export const devMode = 'development'; 54 | 55 | /** 56 | * @description: Production mode 57 | */ 58 | export const prodMode = 'production'; 59 | 60 | /** 61 | * @description: Get environment variables 62 | * @returns: 63 | * @example: 64 | */ 65 | export function getEnv(): string { 66 | return import.meta.env.MODE; 67 | } 68 | 69 | /** 70 | * @description: Is it a development mode 71 | * @returns: 72 | * @example: 73 | */ 74 | export function isDevMode(): boolean { 75 | return import.meta.env.DEV; 76 | } 77 | 78 | /** 79 | * @description: Is it a production mode 80 | * @returns: 81 | * @example: 82 | */ 83 | export function isProdMode(): boolean { 84 | return import.meta.env.PROD; 85 | } 86 | --------------------------------------------------------------------------------