├── frontend ├── .env.development ├── .prettierrc ├── .gitignore ├── src │ ├── index.css │ ├── assets │ │ └── logo.png │ ├── components │ │ ├── Logo.vue │ │ ├── Loading.vue │ │ ├── Header.vue │ │ └── HelloWorld.vue │ ├── main.js │ ├── router.js │ ├── App.vue │ ├── api │ │ └── index.js │ └── views │ │ ├── index.vue │ │ └── login.vue ├── public │ └── favicon.ico ├── postcss.config.js ├── jsconfig.json ├── .editorconfig ├── tailwind.config.js ├── index.html ├── package.json └── vite.config.js ├── backend ├── static │ ├── favicon.ico │ ├── activity.json.gz │ ├── assets │ │ ├── logo.03d6d6da.png │ │ ├── element-icons.de5eb258.ttf │ │ ├── element-icons.9c88a535.woff │ │ ├── index.34a494f0.js │ │ └── index.fa13dcb4.css │ ├── index.html │ └── activity.json ├── ecosystem.config.js ├── .env.example ├── package.json ├── .gitignore ├── app.js ├── ql.js ├── utils │ └── USER_AGENT.js ├── user.js └── sendNotify.js ├── .github └── workflows │ └── vue.yml ├── .gitignore └── README.md /frontend/.env.development: -------------------------------------------------------------------------------- 1 | VITE_API_BASE_URL=http://localhost:5701/api 2 | -------------------------------------------------------------------------------- /frontend/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "singleQuote": true 4 | } 5 | -------------------------------------------------------------------------------- /frontend/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | dist 4 | dist-ssr 5 | *.local 6 | -------------------------------------------------------------------------------- /frontend/src/index.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; -------------------------------------------------------------------------------- /backend/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/licklly/kingrom_ninja/HEAD/backend/static/favicon.ico -------------------------------------------------------------------------------- /frontend/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/licklly/kingrom_ninja/HEAD/frontend/public/favicon.ico -------------------------------------------------------------------------------- /frontend/src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/licklly/kingrom_ninja/HEAD/frontend/src/assets/logo.png -------------------------------------------------------------------------------- /backend/static/activity.json.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/licklly/kingrom_ninja/HEAD/backend/static/activity.json.gz -------------------------------------------------------------------------------- /frontend/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /backend/static/assets/logo.03d6d6da.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/licklly/kingrom_ninja/HEAD/backend/static/assets/logo.03d6d6da.png -------------------------------------------------------------------------------- /backend/static/assets/element-icons.de5eb258.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/licklly/kingrom_ninja/HEAD/backend/static/assets/element-icons.de5eb258.ttf -------------------------------------------------------------------------------- /backend/ecosystem.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | apps: [ 3 | { 4 | name: 'ninja', 5 | script: './app.js', 6 | }, 7 | ], 8 | }; 9 | -------------------------------------------------------------------------------- /backend/static/assets/element-icons.9c88a535.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/licklly/kingrom_ninja/HEAD/backend/static/assets/element-icons.9c88a535.woff -------------------------------------------------------------------------------- /frontend/jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "paths": { 5 | "@/*": ["src/*"] 6 | } 7 | }, 8 | "exclude": ["node_modules", "dist"] 9 | } 10 | -------------------------------------------------------------------------------- /frontend/.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /frontend/src/components/Logo.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 16 | -------------------------------------------------------------------------------- /frontend/tailwind.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | purge: ['./index.html', './src/**/*.{vue,js,ts,jsx,tsx}'], 3 | important: '#app', 4 | darkMode: false, // or 'media' or 'class' 5 | theme: { 6 | extend: {}, 7 | }, 8 | variants: { 9 | extend: {}, 10 | }, 11 | plugins: [], 12 | } 13 | -------------------------------------------------------------------------------- /frontend/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Ninja 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /backend/.env.example: -------------------------------------------------------------------------------- 1 | # 是否允许添加账号 不允许添加时则只允许已有账号登录 2 | ALLOW_ADD=true 3 | 4 | #允许添加账号的最大数量 5 | ALLOW_NUM=45 6 | 7 | #是否显示扫码,默认不显示 8 | #SHOW_QR=true 9 | 10 | #是否显示WSCK录入,默认不显示 11 | #SHOW_WSCK=false 12 | 13 | #是否显示CK登录,默认不显示 14 | SHOW_CK=true 15 | 16 | # 是否允许添加WSCK账号 不允许添加时则只允许已有账号登录 17 | ALLOW_WSCK_ADD=true 18 | 19 | #允许添加WSCK账号的最大数量 20 | ALLOW_WSCK_NUM=45 21 | 22 | # Ninja 运行端口 23 | NINJA_PORT=5701 24 | 25 | # Ninja 是否发送通知 26 | NINJA_NOTIFY=true 27 | 28 | # user-agent 29 | # NINJA_UA="" 30 | -------------------------------------------------------------------------------- /backend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.1.1", 3 | "scripts": { 4 | "dev": "nodemon app.js" 5 | }, 6 | "engines": { 7 | "node": ">=14" 8 | }, 9 | "dependencies": { 10 | "@koa/cors": "^3.1.0", 11 | "@koa/router": "^10.1.0", 12 | "axios": "^0.21.1", 13 | "dotenv": "^10.0.0", 14 | "got": "^11.8.2", 15 | "koa": "^2.13.1", 16 | "koa-body": "^4.2.0", 17 | "koa-log4": "^2.3.2", 18 | "koa-static": "^5.0.0", 19 | "nodemon": "^2.0.12", 20 | "qrcode": "^1.4.4" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /frontend/src/components/Loading.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /backend/static/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Ninja 8 | 9 | 10 | 11 | 12 | 13 |
14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /frontend/src/main.js: -------------------------------------------------------------------------------- 1 | import { ElButton, ElInput, ElMessage } from 'element-plus' 2 | import 'element-plus/lib/theme-chalk/base.css' 3 | import { createApp } from 'vue' 4 | import App from './App.vue' 5 | import './index.css' 6 | import router from './router' 7 | 8 | const components = [ElButton, ElInput, ElMessage] 9 | const plugins = [ElMessage] 10 | 11 | const app = createApp(App) 12 | 13 | components.forEach((component) => { 14 | app.component(component.name, component) 15 | }) 16 | 17 | plugins.forEach((plugin) => { 18 | app.use(plugin) 19 | }) 20 | 21 | app.use(router) 22 | app.mount('#app') 23 | -------------------------------------------------------------------------------- /frontend/src/router.js: -------------------------------------------------------------------------------- 1 | import Index from '@/views/index.vue' 2 | import Login from '@/views/login.vue' 3 | import { createRouter, createWebHashHistory } from 'vue-router' 4 | 5 | const routes = [ 6 | { path: '/', component: Index }, 7 | { path: '/login', component: Login }, 8 | ] 9 | 10 | const router = createRouter({ 11 | history: createWebHashHistory(), 12 | routes, 13 | }) 14 | 15 | // router.beforeEach((to, from, next) => { 16 | // if (!localStorage.getItem('eid') && to.path !== '/login') 17 | // next({ path: '/login' }) 18 | // else next() 19 | // }) 20 | 21 | export default router 22 | -------------------------------------------------------------------------------- /frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.0.0", 3 | "scripts": { 4 | "dev": "vite", 5 | "build": "vite build", 6 | "serve": "vite preview" 7 | }, 8 | "dependencies": { 9 | "element-plus": "^1.0.2-beta.62", 10 | "ky": "^0.28.5", 11 | "vue": "^3.1.5", 12 | "vue-router": "^4.0.10" 13 | }, 14 | "devDependencies": { 15 | "@vitejs/plugin-vue": "^1.2.5", 16 | "@vue/compiler-sfc": "^3.1.5", 17 | "autoprefixer": "^10.3.1", 18 | "postcss": "^8.3.6", 19 | "tailwindcss": "^2.2.7", 20 | "vite": "^2.4.3", 21 | "vite-plugin-async-catch": "^0.1.7", 22 | "vite-plugin-style-import": "^1.0.1" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /frontend/src/components/Header.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 20 | 21 | 29 | -------------------------------------------------------------------------------- /frontend/src/components/HelloWorld.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 28 | 29 | 34 | -------------------------------------------------------------------------------- /.github/workflows/vue.yml: -------------------------------------------------------------------------------- 1 | name: BuildAndCommit 2 | 3 | env: 4 | TZ: Asia/Shanghai 5 | 6 | on: 7 | workflow_dispatch: 8 | 9 | jobs: 10 | main: 11 | runs-on: ubuntu-latest 12 | if: github.event.repository.owner.id == github.event.sender.id 13 | steps: 14 | - name: Checkout 15 | uses: actions/checkout@v2 16 | 17 | - name: Install and Build 18 | run: | 19 | cd backend/static 20 | rm -f index.html 21 | cd assets 22 | shopt -s extglob 23 | rm -f !(*.png) 24 | cd .. 25 | cd .. 26 | cd .. 27 | cd frontend 28 | npm install 29 | npm run build 30 | 31 | - name: Commit changes 32 | uses: EndBug/add-and-commit@v4 33 | with: 34 | message: "GitHub Actions Auto Commit" 35 | add: "backend/static" 36 | env: 37 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 38 | -------------------------------------------------------------------------------- /frontend/vite.config.js: -------------------------------------------------------------------------------- 1 | import vue from '@vitejs/plugin-vue' 2 | import path from 'path' 3 | import { defineConfig } from 'vite' 4 | import AsyncCatch from 'vite-plugin-async-catch' 5 | import styleImport from 'vite-plugin-style-import' 6 | 7 | // https://vitejs.dev/config/ 8 | export default defineConfig({ 9 | plugins: [ 10 | vue(), 11 | AsyncCatch({ catchCode: `console.error(e)` }), 12 | styleImport({ 13 | libs: [ 14 | { 15 | libraryName: 'element-plus', 16 | esModule: true, 17 | ensureStyleFile: true, 18 | resolveStyle: (name) => { 19 | return `element-plus/lib/theme-chalk/${name}.css` 20 | }, 21 | resolveComponent: (name) => { 22 | return `element-plus/lib/${name}` 23 | }, 24 | }, 25 | ], 26 | }), 27 | ], 28 | resolve: { 29 | alias: { 30 | '@': path.resolve(__dirname, 'src'), 31 | }, 32 | }, 33 | build: { 34 | outDir: '../backend/static', 35 | }, 36 | }) 37 | -------------------------------------------------------------------------------- /backend/static/activity.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "玩一玩(可找到大多数活动)", 4 | "address": "京东 APP 首页-频道-边玩边赚", 5 | "href": "https://funearth.m.jd.com/babelDiy/Zeus/3BB1rymVZUo4XmicATEUSDUgHZND/index.html" 6 | }, 7 | { 8 | "name": "宠汪汪", 9 | "address": "京东APP-首页/玩一玩/我的-宠汪汪" 10 | }, 11 | { 12 | "name": "东东萌宠", 13 | "address": "京东APP-首页/玩一玩/我的-东东萌宠" 14 | }, 15 | { 16 | "name": "东东农场", 17 | "address": "京东APP-首页/玩一玩/我的-东东农场" 18 | }, 19 | { 20 | "name": "东东工厂", 21 | "address": "京东APP-首页/玩一玩/我的-东东工厂" 22 | }, 23 | { 24 | "name": "东东超市", 25 | "address": "京东APP-首页/玩一玩/我的-东东超市" 26 | }, 27 | { 28 | "name": "领现金", 29 | "address": "京东APP-首页/玩一玩/我的-领现金" 30 | }, 31 | { 32 | "name": "东东健康社区", 33 | "address": "京东APP-首页/玩一玩/我的-东东健康社区" 34 | }, 35 | { 36 | "name": "京喜农场", 37 | "address": "京喜APP-我的-京喜农场" 38 | }, 39 | { 40 | "name": "京喜牧场", 41 | "address": "京喜APP-我的-京喜牧场" 42 | }, 43 | { 44 | "name": "京喜工厂", 45 | "address": "京喜APP-我的-京喜工厂" 46 | }, 47 | { 48 | "name": "京喜财富岛", 49 | "address": "京喜APP-我的-京喜财富岛" 50 | }, 51 | { 52 | "name": "京东极速版红包", 53 | "address": "京东极速版APP-我的-红包" 54 | } 55 | ] 56 | -------------------------------------------------------------------------------- /frontend/src/App.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 15 | 16 | -------------------------------------------------------------------------------- /frontend/src/api/index.js: -------------------------------------------------------------------------------- 1 | import ky from 'ky' 2 | 3 | const VITE_API_BASE_URL = import.meta.env.VITE_API_BASE_URL || '/api' 4 | 5 | const api = ky.create({ prefixUrl: VITE_API_BASE_URL, retry: { limit: 0 } }) 6 | 7 | export function getInfoAPI() { 8 | return api.get('info').json() 9 | } 10 | 11 | export function CKLoginAPI(body) { 12 | return api.post('cklogin', { json: body }).json() 13 | } 14 | 15 | export function getQrcodeAPI() { 16 | return api.get('qrcode').json() 17 | } 18 | 19 | export function checkLoginAPI(body) { 20 | return api.post('check', { json: body }).json() 21 | } 22 | 23 | export function getUserInfoAPI(eid) { 24 | const searchParams = new URLSearchParams() 25 | searchParams.set('eid', eid) 26 | return api.get('userinfo', { searchParams: searchParams }).json() 27 | } 28 | 29 | export function delAccountAPI(body) { 30 | return api.post('delaccount', { json: body }).json() 31 | } 32 | 33 | export function remarkupdateAPI(body) { 34 | return api.post('update/remark', { json: body }).json() 35 | } 36 | 37 | export function WSCKLoginAPI(body) { 38 | return api.post('WSCKLogin', { json: body }).json() 39 | } 40 | 41 | export function getWSCKUserinfoAPI(eid) { 42 | const searchParams = new URLSearchParams() 43 | searchParams.set('wseid', wseid) 44 | return api.get('WSCKUserinfo', { searchParams: searchParams }).json() 45 | } 46 | 47 | export function WSCKDelaccountAPI(body) { 48 | return api.post('WSCKDelaccount', { json: body }).json() 49 | } 50 | 51 | export function remarkupdateWSCKAPI(body) { 52 | return api.post('updateWSCK/remark', { json: body }).json() 53 | } 54 | -------------------------------------------------------------------------------- /backend/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.toptal.com/developers/gitignore/api/node,windows,macos,linux 3 | # Edit at https://www.toptal.com/developers/gitignore?templates=node,windows,macos,linux 4 | 5 | ### Linux ### 6 | *~ 7 | 8 | # temporary files which can be created if a process still has a handle open of a deleted file 9 | .fuse_hidden* 10 | 11 | # KDE directory preferences 12 | .directory 13 | 14 | # Linux trash folder which might appear on any partition or disk 15 | .Trash-* 16 | 17 | # .nfs files are created when an open file is removed but is still being accessed 18 | .nfs* 19 | 20 | ### macOS ### 21 | # General 22 | .DS_Store 23 | .AppleDouble 24 | .LSOverride 25 | 26 | # Icon must end with two \r 27 | Icon 28 | 29 | 30 | # Thumbnails 31 | ._* 32 | 33 | # Files that might appear in the root of a volume 34 | .DocumentRevisions-V100 35 | .fseventsd 36 | .Spotlight-V100 37 | .TemporaryItems 38 | .Trashes 39 | .VolumeIcon.icns 40 | .com.apple.timemachine.donotpresent 41 | 42 | # Directories potentially created on remote AFP share 43 | .AppleDB 44 | .AppleDesktop 45 | Network Trash Folder 46 | Temporary Items 47 | .apdisk 48 | 49 | ### Node ### 50 | # Logs 51 | logs 52 | *.log 53 | npm-debug.log* 54 | yarn-debug.log* 55 | yarn-error.log* 56 | lerna-debug.log* 57 | .pnpm-debug.log* 58 | 59 | # Diagnostic reports (https://nodejs.org/api/report.html) 60 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 61 | 62 | # Runtime data 63 | pids 64 | *.pid 65 | *.seed 66 | *.pid.lock 67 | 68 | # Directory for instrumented libs generated by jscoverage/JSCover 69 | lib-cov 70 | 71 | # Coverage directory used by tools like istanbul 72 | coverage 73 | *.lcov 74 | 75 | # nyc test coverage 76 | .nyc_output 77 | 78 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 79 | .grunt 80 | 81 | # Bower dependency directory (https://bower.io/) 82 | bower_components 83 | 84 | # node-waf configuration 85 | .lock-wscript 86 | 87 | # Compiled binary addons (https://nodejs.org/api/addons.html) 88 | build/Release 89 | 90 | # Dependency directories 91 | node_modules/ 92 | jspm_packages/ 93 | 94 | # Snowpack dependency directory (https://snowpack.dev/) 95 | web_modules/ 96 | 97 | # TypeScript cache 98 | *.tsbuildinfo 99 | 100 | # Optional npm cache directory 101 | .npm 102 | 103 | # Optional eslint cache 104 | .eslintcache 105 | 106 | # Microbundle cache 107 | .rpt2_cache/ 108 | .rts2_cache_cjs/ 109 | .rts2_cache_es/ 110 | .rts2_cache_umd/ 111 | 112 | # Optional REPL history 113 | .node_repl_history 114 | 115 | # Output of 'npm pack' 116 | *.tgz 117 | 118 | # Yarn Integrity file 119 | .yarn-integrity 120 | 121 | # dotenv environment variables file 122 | .env 123 | .env.test 124 | .env.production 125 | 126 | # parcel-bundler cache (https://parceljs.org/) 127 | .cache 128 | .parcel-cache 129 | 130 | # Next.js build output 131 | .next 132 | out 133 | 134 | # Nuxt.js build / generate output 135 | .nuxt 136 | dist 137 | 138 | # Gatsby files 139 | .cache/ 140 | # Comment in the public line in if your project uses Gatsby and not Next.js 141 | # https://nextjs.org/blog/next-9-1#public-directory-support 142 | # public 143 | 144 | # vuepress build output 145 | .vuepress/dist 146 | 147 | # Serverless directories 148 | .serverless/ 149 | 150 | # FuseBox cache 151 | .fusebox/ 152 | 153 | # DynamoDB Local files 154 | .dynamodb/ 155 | 156 | # TernJS port file 157 | .tern-port 158 | 159 | # Stores VSCode versions used for testing VSCode extensions 160 | .vscode-test 161 | 162 | # yarn v2 163 | .yarn/cache 164 | .yarn/unplugged 165 | .yarn/build-state.yml 166 | .yarn/install-state.gz 167 | .pnp.* 168 | 169 | ### Windows ### 170 | # Windows thumbnail cache files 171 | Thumbs.db 172 | Thumbs.db:encryptable 173 | ehthumbs.db 174 | ehthumbs_vista.db 175 | 176 | # Dump file 177 | *.stackdump 178 | 179 | # Folder config file 180 | [Dd]esktop.ini 181 | 182 | # Recycle Bin used on file shares 183 | $RECYCLE.BIN/ 184 | 185 | # Windows Installer files 186 | *.cab 187 | *.msi 188 | *.msix 189 | *.msm 190 | *.msp 191 | 192 | # Windows shortcuts 193 | *.lnk 194 | 195 | # End of https://www.toptal.com/developers/gitignore/api/node,windows,macos,linux 196 | 197 | ql 198 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.toptal.com/developers/gitignore/api/node,windows,macos,linux 3 | # Edit at https://www.toptal.com/developers/gitignore?templates=node,windows,macos,linux 4 | 5 | ### Linux ### 6 | *~ 7 | 8 | # temporary files which can be created if a process still has a handle open of a deleted file 9 | .fuse_hidden* 10 | 11 | # KDE directory preferences 12 | .directory 13 | 14 | # Linux trash folder which might appear on any partition or disk 15 | .Trash-* 16 | 17 | # .nfs files are created when an open file is removed but is still being accessed 18 | .nfs* 19 | 20 | ### macOS ### 21 | # General 22 | .DS_Store 23 | .AppleDouble 24 | .LSOverride 25 | 26 | # Icon must end with two \r 27 | Icon 28 | 29 | 30 | # Thumbnails 31 | ._* 32 | 33 | # Files that might appear in the root of a volume 34 | .DocumentRevisions-V100 35 | .fseventsd 36 | .Spotlight-V100 37 | .TemporaryItems 38 | .Trashes 39 | .VolumeIcon.icns 40 | .com.apple.timemachine.donotpresent 41 | 42 | # Directories potentially created on remote AFP share 43 | .AppleDB 44 | .AppleDesktop 45 | Network Trash Folder 46 | Temporary Items 47 | .apdisk 48 | 49 | ### Node ### 50 | # Logs 51 | logs 52 | *.log 53 | npm-debug.log* 54 | yarn-debug.log* 55 | yarn-error.log* 56 | lerna-debug.log* 57 | .pnpm-debug.log* 58 | 59 | # Diagnostic reports (https://nodejs.org/api/report.html) 60 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 61 | 62 | # Runtime data 63 | pids 64 | *.pid 65 | *.seed 66 | *.pid.lock 67 | 68 | # Directory for instrumented libs generated by jscoverage/JSCover 69 | lib-cov 70 | 71 | # Coverage directory used by tools like istanbul 72 | coverage 73 | *.lcov 74 | 75 | # nyc test coverage 76 | .nyc_output 77 | 78 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 79 | .grunt 80 | 81 | # Bower dependency directory (https://bower.io/) 82 | bower_components 83 | 84 | # node-waf configuration 85 | .lock-wscript 86 | 87 | # Compiled binary addons (https://nodejs.org/api/addons.html) 88 | build/Release 89 | 90 | # Dependency directories 91 | node_modules/ 92 | jspm_packages/ 93 | 94 | # Snowpack dependency directory (https://snowpack.dev/) 95 | web_modules/ 96 | 97 | # TypeScript cache 98 | *.tsbuildinfo 99 | 100 | # Optional npm cache directory 101 | .npm 102 | 103 | # Optional eslint cache 104 | .eslintcache 105 | 106 | # Microbundle cache 107 | .rpt2_cache/ 108 | .rts2_cache_cjs/ 109 | .rts2_cache_es/ 110 | .rts2_cache_umd/ 111 | 112 | # Optional REPL history 113 | .node_repl_history 114 | 115 | # Output of 'npm pack' 116 | *.tgz 117 | 118 | # Yarn Integrity file 119 | .yarn-integrity 120 | 121 | # dotenv environment variables file 122 | .env 123 | .env.test 124 | .env.production 125 | 126 | # parcel-bundler cache (https://parceljs.org/) 127 | .cache 128 | .parcel-cache 129 | 130 | # Next.js build output 131 | .next 132 | out 133 | 134 | # Nuxt.js build / generate output 135 | .nuxt 136 | dist 137 | 138 | # Gatsby files 139 | .cache/ 140 | # Comment in the public line in if your project uses Gatsby and not Next.js 141 | # https://nextjs.org/blog/next-9-1#public-directory-support 142 | # public 143 | 144 | # vuepress build output 145 | .vuepress/dist 146 | 147 | # Serverless directories 148 | .serverless/ 149 | 150 | # FuseBox cache 151 | .fusebox/ 152 | 153 | # DynamoDB Local files 154 | .dynamodb/ 155 | 156 | # TernJS port file 157 | .tern-port 158 | 159 | # Stores VSCode versions used for testing VSCode extensions 160 | .vscode-test 161 | 162 | # yarn v2 163 | .yarn/cache 164 | .yarn/unplugged 165 | .yarn/build-state.yml 166 | .yarn/install-state.gz 167 | .pnp.* 168 | 169 | ### Windows ### 170 | # Windows thumbnail cache files 171 | Thumbs.db 172 | Thumbs.db:encryptable 173 | ehthumbs.db 174 | ehthumbs_vista.db 175 | 176 | # Dump file 177 | *.stackdump 178 | 179 | # Folder config file 180 | [Dd]esktop.ini 181 | 182 | # Recycle Bin used on file shares 183 | $RECYCLE.BIN/ 184 | 185 | # Windows Installer files 186 | *.cab 187 | *.msi 188 | *.msix 189 | *.msm 190 | *.msp 191 | 192 | # Windows shortcuts 193 | *.lnk 194 | 195 | # End of https://www.toptal.com/developers/gitignore/api/node,windows,macos,linux 196 | 197 | ql 198 | 199 | node_modules 200 | .DS_Store 201 | dist 202 | dist-ssr 203 | *.local 204 | -------------------------------------------------------------------------------- /backend/app.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Koa = require('koa'); 4 | const cors = require('@koa/cors'); 5 | const Router = require('@koa/router'); 6 | const body = require('koa-body'); 7 | const serve = require('koa-static'); 8 | const User = require('./user'); 9 | const packageJson = require('./package.json'); 10 | 11 | // Create express instance 12 | const app = new Koa(); 13 | const router = new Router(); 14 | 15 | const handler = async (ctx, next) => { 16 | try { 17 | await next(); 18 | if (ctx.body?.data.message) { 19 | ctx.body.message = ctx.body.data.message; 20 | ctx.body.data.message = undefined; 21 | } 22 | } catch (err) { 23 | console.log(err); 24 | ctx.status = err.statusCode || err.status || 500; 25 | ctx.body = { 26 | code: err.status || err.statusCode || 500, 27 | message: err.message, 28 | }; 29 | } 30 | }; 31 | 32 | app.use(serve('static')); 33 | app.use(cors()); 34 | app.use(handler); 35 | app.use(router.routes()).use(router.allowedMethods()); 36 | 37 | router.get('/api/status', (ctx) => { 38 | ctx.body = { 39 | code: 200, 40 | data: { 41 | version: packageJson.version, 42 | }, 43 | message: 'Ninja is already.', 44 | }; 45 | }); 46 | 47 | router.get('/api/info', async (ctx) => { 48 | const data = await User.getPoolInfo(); 49 | debugger 50 | ctx.body = { data }; 51 | }); 52 | 53 | router.get('/api/qrcode', async (ctx) => { 54 | const user = new User({}); 55 | await user.getQRConfig(); 56 | ctx.body = { 57 | data: { 58 | token: user.token, 59 | okl_token: user.okl_token, 60 | cookies: user.cookies, 61 | QRCode: user.QRCode, 62 | ua: user.ua, 63 | }, 64 | }; 65 | }); 66 | 67 | router.post('/api/check', body(), async (ctx) => { 68 | const body = ctx.request.body; 69 | const user = new User(body); 70 | const data = await user.checkQRLogin(); 71 | ctx.body = { data }; 72 | }); 73 | 74 | router.post('/api/cklogin', body(), async (ctx) => { 75 | const body = ctx.request.body; 76 | const user = new User(body); 77 | const data = await user.CKLogin(); 78 | ctx.body = { data }; 79 | }); 80 | 81 | router.get('/api/userinfo', async (ctx) => { 82 | const query = ctx.query; 83 | const eid = query.eid; 84 | const user = new User({ eid }); 85 | const data = await user.getUserInfoByEid(); 86 | ctx.body = { data }; 87 | }); 88 | 89 | router.post('/api/delaccount', body(), async (ctx) => { 90 | const body = ctx.request.body; 91 | const eid = body.eid; 92 | const user = new User({ eid }); 93 | const data = await user.delUserByEid(); 94 | ctx.body = { data }; 95 | }); 96 | 97 | router.post('/api/update/remark', body(), async (ctx) => { 98 | const body = ctx.request.body; 99 | const eid = body.eid; 100 | const remark = body.remark; 101 | const user = new User({ eid, remark }); 102 | const data = await user.updateRemark(); 103 | ctx.body = { data }; 104 | }); 105 | 106 | router.get('/api/users', async (ctx) => { 107 | if (ctx.host.startsWith('localhost')) { 108 | const data = await User.getUsers(); 109 | ctx.body = { data }; 110 | } else { 111 | ctx.body = { 112 | code: 401, 113 | message: '该接口仅能通过 localhost 访问', 114 | }; 115 | } 116 | }); 117 | 118 | /////////////////////////////////////////////// 119 | 120 | router.post('/api/WSCKLogin', body(), async (ctx) => { 121 | const body = ctx.request.body; 122 | const user = new User(body); 123 | const data = await user.WSCKLogin(); 124 | ctx.body = { data }; 125 | }); 126 | 127 | router.get('/api/WSCKUserinfo', async (ctx) => { 128 | const query = ctx.query; 129 | const wseid = query.wseid; 130 | const user = new User({ wseid }); 131 | const data = await user.getWSCKUserInfoByEid(); 132 | ctx.body = { data }; 133 | }); 134 | 135 | router.post('/api/WSCKDelaccount', body(), async (ctx) => { 136 | const body = ctx.request.body; 137 | const wseid = body.wseid; 138 | const user = new User({ wseid }); 139 | const data = await user.delWSCKUserByEid(); 140 | ctx.body = { data }; 141 | }); 142 | 143 | router.post('/api/updateWSCK/remark', body(), async (ctx) => { 144 | const body = ctx.request.body; 145 | const wseid = body.wseid; 146 | const remark = body.remark; 147 | const user = new User({ wseid, remark }); 148 | const data = await user.updateWSCKRemark(); 149 | ctx.body = { data }; 150 | }); 151 | 152 | /////////////////////////////////////////////// 153 | 154 | const port = process.env.NINJA_PORT || 5701; 155 | console.log('Start Ninja success! listening port: ' + port); 156 | app.listen(port); 157 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Ninja 2 | 3 | 支持CK注册,登录和删除,支持WSKEY录入和删除,登录成功进入个人中心,可修改备注。默认登录CK才可提交WSCK,主页提交WSCK容易乱,不建议。 4 | 5 | 基本功能已完善,鸽几天,有问题先仔细看此README。 6 | 7 | ## 致谢 8 | 9 | 感谢Ninja原作者:@MoonBegonia 10 | 11 | 仓库地址:https://github.com/MoonBegonia/ninja 12 | 13 | 感谢WSCK功能原作者:@huiyi9420 14 | 15 | 仓库地址:https://github.com/huiyi9420/ninja 16 | 17 | ## 新 18 | 19 | 当前:增加备用接口(针对某些半黑号) 20 | 21 | 新特性:支持Github Action前端编译并自动替换文件。Fork之后:Action->BuildAndCommit->Run workflow->Run workflow即可。 22 | 23 | ## 说明 24 | 25 | Ninja 仅供学习参考使用,请于下载后的 24 小时内删除,本人不对使用过程中出现的任何问题负责,包括但不限于 `数据丢失` `数据泄露`。 26 | 27 | Ninja 仅支持 qinglong 2.8.2+ 28 | 29 | [TG 频道](https://t.me/joinchat/sHKuteb_lfdjNmZl) 30 | 31 | ## 特性 32 | 33 | - [x] 局域网扫码,跳转登录添加/更新 cookie 34 | - [x] 添加/更新 cookie 后发送通知 35 | - [x] 扫码发送通知可关闭 36 | - [x] 默认备注为账号 37 | - [x] 可修改备注 38 | - [x] wskey有效性检测 39 | - [x] 登录界面展示自定义标语 40 | - [x] Github Action自动编译 41 | - [x] WSKEY录入 42 | 43 | ## 文档 44 | 45 | ### 容器内 46 | 47 | 1. 容器映射 5701 端口,ninja 目录至宿主机 48 | 49 | 例(docker-compose): 50 | 51 | ```diff 52 | version: "3" 53 | services: 54 | qinglong: 55 | image: whyour/qinglong:latest 56 | container_name: qinglong 57 | restart: unless-stopped 58 | tty: true 59 | ports: 60 | - 5700:5700 61 | + - 5701:5701 62 | environment: 63 | - ENABLE_HANGUP=true 64 | - ENABLE_WEB_PANEL=true 65 | volumes: 66 | - ./config:/ql/config 67 | - ./log:/ql/log 68 | - ./db:/ql/db 69 | - ./repo:/ql/repo 70 | - ./raw:/ql/raw 71 | - ./scripts:/ql/scripts 72 | - ./jbot:/ql/jbot 73 | + - ./ninja:/ql/ninja 74 | ``` 75 | 76 | 例(docker-run): 77 | 78 | ```diff 79 | docker run -dit \ 80 | -v $PWD/ql/config:/ql/config \ 81 | -v $PWD/ql/log:/ql/log \ 82 | -v $PWD/ql/db:/ql/db \ 83 | -v $PWD/ql/repo:/ql/repo \ 84 | -v $PWD/ql/raw:/ql/raw \ 85 | -v $PWD/ql/scripts:/ql/scripts \ 86 | -v $PWD/ql/jbot:/ql/jbot \ 87 | + -v $PWD/ql/ninja:/ql/ninja \ 88 | -p 5700:5700 \ 89 | + -p 5701:5701 \ 90 | --name qinglong \ 91 | --hostname qinglong \ 92 | --restart unless-stopped \ 93 | whyour/qinglong:latest 94 | ``` 95 | 96 | 2. 进容器内执行以下命令 97 | 98 | **进容器内执行以下命令** 99 | 100 | ```bash 101 | git clone https://github.com/KingRan/kingrom_ninja.git /ql/ninja 102 | cd /ql/ninja/backend 103 | pnpm install 104 | cp .env.example .env # 如有需要, 修改.env 105 | pm2 start 106 | cp sendNotify.js /ql/scripts/sendNotify.js 107 | ``` 108 | 109 | 3. 将以下内容粘贴到 `extra.sh`(重启后自动更新并启动 Ninja) 110 | 111 | ```bash 112 | cd /ql/ninja/backend 113 | git checkout . 114 | git pull 115 | pnpm install 116 | pm2 start 117 | cp sendNotify.js /ql/scripts/sendNotify.js 118 | ``` 119 | 120 | ### 容器外 121 | 122 | 此种方式需要宿主机安装 `node` `pnpm` 等环境,不做过多介绍。 123 | 124 | 使用此种方法无法跟随青龙一起启动,**无法发送扫码通知**,请知悉。 125 | 126 | ```bash 127 | git clone https://github.com/KingRan/kingrom_ninja.git 128 | cd ninja/backend 129 | pnpm install 130 | # 复制 sendNotify.js 到容器内 scripts 目录,`qinglong` 为容器名 131 | sudo docker cp sendNotify.js qinglong:/ql/scripts/sendNotify.js 132 | cp .env.example .env 133 | # 修改env文件 134 | vi .env 135 | node app.js 136 | ``` 137 | 138 | 在 `.env` 文件中添加以下内容: 139 | 140 | ```bash 141 | QL_DIR=qinglong 容器的本地路径 142 | QL_URL=http://localhost:5700 143 | ``` 144 | 145 | `node app.js` 想要在后台运行可以使用 `&` `nohup` `screen` 等命令。 146 | 147 | ### Ninja 环境变量 148 | 149 | 目前支持的环境变量有: 150 | 151 | - `SHOW_QR`:是否显示扫码卡片,默认不显示 152 | - `SHOW_WSCK`:是否显示WSCK录入,默认不显示 153 | - `SHOW_CK`:是否显示CK登录,默认不显示 154 | - `ALLOW_WSCK_ADD`:是否允许添加WSCK账号 不允许添加时则只允许已有账号登录 155 | - `ALLOW_WSCK_NUM`:允许添加WSCK账号的最大数量 156 | - `ALLOW_ADD`: 是否允许添加账号 不允许添加时则只允许已有账号登录(默认 `true`) 157 | - `ALLOW_NUM`: 允许添加账号的最大数量(默认 `45`) 158 | - `NINJA_PORT`: Ninja 运行端口(默认 `5701`) 159 | - `NINJA_NOTIFY`: 是否开启通知功能(默认 `true`) 160 | - `NINJA_UA`: 自定义 UA,默认为随机 161 | - 162 | 163 | 配置方式: 164 | 165 | ```bash 166 | cd /ql/ninja/backend 167 | cp .env.example .env 168 | vi .env 169 | pm2 start 170 | ``` 171 | 172 | **修改完成后需要 `pm2 start` 重启生效 !!!** 173 | 174 | ### SendNotify 环境变量 175 | 176 | **此环境变量在青龙中配置!!!** 177 | 178 | - `NOTIFY_SKIP_LIST`: 通知黑名单,使用 `&` 分隔,例如 `东东乐园&东东萌宠`; 179 | 180 | ### Ninja 自定义 181 | 182 | (未完成)自定义推送二维码:将 `push.jpg` 文件添加到 `/ql/ninja/backend/static/` 目录下刷新网页即可。 183 | 184 | 自定义常见活动:修改 `/ql/backend/static/activity.json` 即可 185 | 186 | ## 注意事项 187 | 188 | - 重启后务必执行一次 `ql extra` 保证 Ninja 配置成功。 189 | 190 | - 更新 Ninja 只需要在**容器**中 `ninja/backend` 目录执行 `git pull` 然后 `pm2 start` 191 | 192 | - Qinglong 需要在登录状态(`auth.json` 中有 token) 193 | 194 | ## 如何更新Ninja 195 | 196 | ```bash 197 | cd /ql/ninja 198 | git checkout . 199 | git pull 200 | cd backend 201 | pm2 start 202 | ``` 203 | 204 | ## 如何删除Ninja 205 | 206 | ```bash 207 | cd /ql/ninja 208 | pm2 delete ninja 209 | rm -rf * 210 | rm -r ./.* 211 | ``` 212 | -------------------------------------------------------------------------------- /backend/ql.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const got = require('got'); 4 | require('dotenv').config(); 5 | const { readFile } = require('fs/promises'); 6 | const path = require('path'); 7 | 8 | const qlDir = process.env.QL_DIR || '/ql'; 9 | const authFile = path.join(qlDir, 'config/auth.json'); 10 | 11 | const api = got.extend({ 12 | prefixUrl: process.env.QL_URL || 'http://localhost:5600', 13 | retry: { limit: 0 }, 14 | }); 15 | 16 | async function getToken() { 17 | const authConfig = JSON.parse(await readFile(authFile)); 18 | return authConfig.token; 19 | } 20 | 21 | module.exports.getEnvs = async () => { 22 | const token = await getToken(); 23 | const body = await api({ 24 | url: 'api/envs', 25 | searchParams: { 26 | searchValue: 'JD_COOKIE', 27 | t: Date.now(), 28 | }, 29 | headers: { 30 | Accept: 'application/json', 31 | authorization: `Bearer ${token}`, 32 | }, 33 | }).json(); 34 | return body.data; 35 | }; 36 | 37 | module.exports.getEnvsCount = async () => { 38 | const data = await this.getEnvs(); 39 | return data.length; 40 | }; 41 | 42 | module.exports.addEnv = async (cookie, remarks) => { 43 | const token = await getToken(); 44 | const body = await api({ 45 | method: 'post', 46 | url: 'api/envs', 47 | params: { t: Date.now() }, 48 | json: [{ 49 | name: 'JD_COOKIE', 50 | value: cookie, 51 | remarks, 52 | }], 53 | headers: { 54 | Accept: 'application/json', 55 | authorization: `Bearer ${token}`, 56 | 'Content-Type': 'application/json;charset=UTF-8', 57 | }, 58 | }).json(); 59 | return body; 60 | }; 61 | 62 | module.exports.updateEnv = async (cookie, eid, remarks) => { 63 | const token = await getToken(); 64 | const body = await api({ 65 | method: 'put', 66 | url: 'api/envs', 67 | params: { t: Date.now() }, 68 | json: { 69 | name: 'JD_COOKIE', 70 | value: cookie, 71 | _id: eid, 72 | remarks, 73 | }, 74 | headers: { 75 | Accept: 'application/json', 76 | authorization: `Bearer ${token}`, 77 | 'Content-Type': 'application/json;charset=UTF-8', 78 | }, 79 | }).json(); 80 | return body; 81 | }; 82 | 83 | module.exports.delEnv = async (eid) => { 84 | const token = await getToken(); 85 | const body = await api({ 86 | method: 'delete', 87 | url: 'api/envs', 88 | params: { t: Date.now() }, 89 | body: JSON.stringify([eid]), 90 | headers: { 91 | Accept: 'application/json', 92 | authorization: `Bearer ${token}`, 93 | 'Content-Type': 'application/json;charset=UTF-8', 94 | }, 95 | }).json(); 96 | return body; 97 | }; 98 | 99 | ////////////////////////////////////////////////// 100 | // wskey 101 | module.exports.getWSCKEnvs = async () => { 102 | const token = await getToken(); 103 | const body = await api({ 104 | url: 'api/envs', 105 | searchParams: { 106 | searchValue: 'JD_WSCK', 107 | t: Date.now(), 108 | }, 109 | headers: { 110 | Accept: 'application/json', 111 | authorization: `Bearer ${token}`, 112 | }, 113 | }).json(); 114 | return body.data; 115 | }; 116 | 117 | module.exports.getWSCKEnvsCount = async () => { 118 | const data = await this.getWSCKEnvs(); 119 | return data.length; 120 | }; 121 | 122 | module.exports.addWSCKEnv = async (jdwsck, remarks) => { 123 | const token = await getToken(); 124 | const body = await api({ 125 | method: 'post', 126 | url: 'api/envs', 127 | params: { t: Date.now() }, 128 | json: [{ 129 | name: 'JD_WSCK', 130 | value: jdwsck, 131 | remarks, 132 | }], 133 | headers: { 134 | Accept: 'application/json', 135 | authorization: `Bearer ${token}`, 136 | 'Content-Type': 'application/json;charset=UTF-8', 137 | }, 138 | }).json(); 139 | return body; 140 | }; 141 | 142 | module.exports.updateWSCKEnv = async (jdwsck, wseid, remarks) => { 143 | const token = await getToken(); 144 | const body = await api({ 145 | method: 'put', 146 | url: 'api/envs', 147 | params: { t: Date.now() }, 148 | json: { 149 | name: 'JD_WSCK', 150 | value: jdwsck, 151 | _id: wseid, 152 | remarks, 153 | }, 154 | headers: { 155 | Accept: 'application/json', 156 | authorization: `Bearer ${token}`, 157 | 'Content-Type': 'application/json;charset=UTF-8', 158 | }, 159 | }).json(); 160 | return body; 161 | }; 162 | 163 | module.exports.delWSCKEnv = async (wseid) => { 164 | const token = await getToken(); 165 | const body = await api({ 166 | method: 'delete', 167 | url: 'api/envs', 168 | params: { t: Date.now() }, 169 | body: JSON.stringify([wseid]), 170 | headers: { 171 | Accept: 'application/json', 172 | authorization: `Bearer ${token}`, 173 | 'Content-Type': 'application/json;charset=UTF-8', 174 | }, 175 | }).json(); 176 | return body; 177 | }; 178 | 179 | ////////////////////////////////////////////////// 180 | -------------------------------------------------------------------------------- /backend/utils/USER_AGENT.js: -------------------------------------------------------------------------------- 1 | const USER_AGENTS = [ 2 | 'jdapp;android;10.0.2;10;network/wifi;Mozilla/5.0 (Linux; Android 10; ONEPLUS A5010 Build/QKQ1.191014.012; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/77.0.3865.120 MQQBrowser/6.2 TBS/045230 Mobile Safari/537.36', 3 | 'jdapp;iPhone;10.0.2;14.3;network/wifi;Mozilla/5.0 (iPhone; CPU iPhone OS 14_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148;supportJDSHWK/1', 4 | 'jdapp;android;10.0.2;9;network/wifi;Mozilla/5.0 (Linux; Android 9; Mi Note 3 Build/PKQ1.181007.001; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/66.0.3359.126 MQQBrowser/6.2 TBS/045131 Mobile Safari/537.36', 5 | 'jdapp;android;10.0.2;10;network/wifi;Mozilla/5.0 (Linux; Android 10; GM1910 Build/QKQ1.190716.003; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/77.0.3865.120 MQQBrowser/6.2 TBS/045230 Mobile Safari/537.36', 6 | 'jdapp;android;10.0.2;9;network/wifi;Mozilla/5.0 (Linux; Android 9; 16T Build/PKQ1.190616.001; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/66.0.3359.126 MQQBrowser/6.2 TBS/044942 Mobile Safari/537.36', 7 | 'jdapp;iPhone;10.0.2;13.6;network/wifi;Mozilla/5.0 (iPhone; CPU iPhone OS 13_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148;supportJDSHWK/1', 8 | 'jdapp;iPhone;10.0.2;13.6;network/wifi;Mozilla/5.0 (iPhone; CPU iPhone OS 13_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148;supportJDSHWK/1', 9 | 'jdapp;iPhone;10.0.2;13.5;network/wifi;Mozilla/5.0 (iPhone; CPU iPhone OS 13_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148;supportJDSHWK/1', 10 | 'jdapp;iPhone;10.0.2;14.1;network/wifi;Mozilla/5.0 (iPhone; CPU iPhone OS 14_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148;supportJDSHWK/1', 11 | 'jdapp;iPhone;10.0.2;13.3;network/wifi;Mozilla/5.0 (iPhone; CPU iPhone OS 13_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148;supportJDSHWK/1', 12 | 'jdapp;iPhone;10.0.2;13.7;network/wifi;Mozilla/5.0 (iPhone; CPU iPhone OS 13_7 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148;supportJDSHWK/1', 13 | 'jdapp;iPhone;10.0.2;14.1;network/wifi;Mozilla/5.0 (iPhone; CPU iPhone OS 14_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148;supportJDSHWK/1', 14 | 'jdapp;iPhone;10.0.2;13.3;network/wifi;Mozilla/5.0 (iPhone; CPU iPhone OS 13_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148;supportJDSHWK/1', 15 | 'jdapp;iPhone;10.0.2;13.4;network/wifi;Mozilla/5.0 (iPhone; CPU iPhone OS 13_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148;supportJDSHWK/1', 16 | 'jdapp;iPhone;10.0.2;14.3;network/wifi;Mozilla/5.0 (iPhone; CPU iPhone OS 14_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148;supportJDSHWK/1', 17 | 'jdapp;android;10.0.2;9;network/wifi;Mozilla/5.0 (Linux; Android 9; MI 6 Build/PKQ1.190118.001; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/66.0.3359.126 MQQBrowser/6.2 TBS/044942 Mobile Safari/537.36', 18 | 'jdapp;android;10.0.2;11;network/wifi;Mozilla/5.0 (Linux; Android 11; Redmi K30 5G Build/RKQ1.200826.002; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/77.0.3865.120 MQQBrowser/6.2 TBS/045511 Mobile Safari/537.36', 19 | 'jdapp;iPhone;10.0.2;11.4;network/wifi;Mozilla/5.0 (iPhone; CPU iPhone OS 11_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15F79', 20 | 'jdapp;android;10.0.2;10;;network/wifi;Mozilla/5.0 (Linux; Android 10; M2006J10C Build/QP1A.190711.020; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/77.0.3865.120 MQQBrowser/6.2 TBS/045230 Mobile Safari/537.36', 21 | 'jdapp;android;10.0.2;10;network/wifi;Mozilla/5.0 (Linux; Android 10; M2006J10C Build/QP1A.190711.020; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/77.0.3865.120 MQQBrowser/6.2 TBS/045230 Mobile Safari/537.36', 22 | 'jdapp;android;10.0.2;10;network/wifi;Mozilla/5.0 (Linux; Android 10; ONEPLUS A6000 Build/QKQ1.190716.003; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/77.0.3865.120 MQQBrowser/6.2 TBS/045224 Mobile Safari/537.36', 23 | 'jdapp;android;10.0.2;9;network/wifi;Mozilla/5.0 (Linux; Android 9; MHA-AL00 Build/HUAWEIMHA-AL00; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/66.0.3359.126 MQQBrowser/6.2 TBS/044942 Mobile Safari/537.36', 24 | 'jdapp;android;10.0.2;8.1.0;network/wifi;Mozilla/5.0 (Linux; Android 8.1.0; 16 X Build/OPM1.171019.026; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/66.0.3359.126 MQQBrowser/6.2 TBS/044942 Mobile Safari/537.36', 25 | 'jdapp;android;10.0.2;8.0.0;network/wifi;Mozilla/5.0 (Linux; Android 8.0.0; HTC U-3w Build/OPR6.170623.013; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/66.0.3359.126 MQQBrowser/6.2 TBS/044942 Mobile Safari/537.36', 26 | 'jdapp;iPhone;10.0.2;14.0.1;network/wifi;Mozilla/5.0 (iPhone; CPU iPhone OS 14_0_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148;supportJDSHWK/1', 27 | 'jdapp;android;10.0.2;10;network/wifi;Mozilla/5.0 (Linux; Android 10; LYA-AL00 Build/HUAWEILYA-AL00L; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/77.0.3865.120 MQQBrowser/6.2 TBS/045230 Mobile Safari/537.36', 28 | 'jdapp;iPhone;10.0.2;14.2;network/wifi;Mozilla/5.0 (iPhone; CPU iPhone OS 14_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148;supportJDSHWK/1', 29 | 'jdapp;iPhone;10.0.2;14.3;network/wifi;Mozilla/5.0 (iPhone; CPU iPhone OS 14_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148;supportJDSHWK/1', 30 | 'jdapp;iPhone;10.0.2;14.2;network/wifi;Mozilla/5.0 (iPhone; CPU iPhone OS 14_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148;supportJDSHWK/1', 31 | 'jdapp;android;10.0.2;8.1.0;network/wifi;Mozilla/5.0 (Linux; Android 8.1.0; MI 8 Build/OPM1.171019.026; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/66.0.3359.126 MQQBrowser/6.2 TBS/045131 Mobile Safari/537.36', 32 | 'jdapp;android;10.0.2;10;network/wifi;Mozilla/5.0 (Linux; Android 10; Redmi K20 Pro Premium Edition Build/QKQ1.190825.002; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/77.0.3865.120 MQQBrowser/6.2 TBS/045227 Mobile Safari/537.36', 33 | 'jdapp;iPhone;10.0.2;14.3;network/wifi;Mozilla/5.0 (iPhone; CPU iPhone OS 14_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148;supportJDSHWK/1', 34 | 'jdapp;iPhone;10.0.2;14.3;network/wifi;Mozilla/5.0 (iPhone; CPU iPhone OS 14_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148;supportJDSHWK/1', 35 | 'jdapp;android;10.0.2;11;network/wifi;Mozilla/5.0 (Linux; Android 11; Redmi K20 Pro Premium Edition Build/RKQ1.200826.002; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/77.0.3865.120 MQQBrowser/6.2 TBS/045513 Mobile Safari/537.36', 36 | 'jdapp;android;10.0.2;10;network/wifi;Mozilla/5.0 (Linux; Android 10; MI 8 Build/QKQ1.190828.002; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/77.0.3865.120 MQQBrowser/6.2 TBS/045227 Mobile Safari/537.36', 37 | 'jdapp;iPhone;10.0.2;14.1;network/wifi;Mozilla/5.0 (iPhone; CPU iPhone OS 14_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148;supportJDSHWK/1', 38 | ]; 39 | /** 40 | * 生成随机数字 41 | * @param {number} min 最小值(包含) 42 | * @param {number} max 最大值(不包含) 43 | */ 44 | function randomNumber(min = 0, max = 100) { 45 | return Math.min(Math.floor(min + Math.random() * (max - min)), max); 46 | } 47 | 48 | const RANDOM_UA = USER_AGENTS[randomNumber(0, USER_AGENTS.length)]; 49 | 50 | const GET_RANDOM_TIME_UA = () => { 51 | return 'Mozilla/5.0 (iPhone; CPU iPhone OS 13_3_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 SP-engine/2.14.0 main%2F1.0 baiduboxapp/11.18.0.16 (Baidu; P2 13.3.1) NABar/0.0'; 52 | }; 53 | 54 | module.exports = { 55 | RANDOM_UA, 56 | GET_RANDOM_TIME_UA, 57 | }; 58 | -------------------------------------------------------------------------------- /frontend/src/views/index.vue: -------------------------------------------------------------------------------- 1 | 79 | 80 | 281 | 282 | 296 | -------------------------------------------------------------------------------- /frontend/src/views/login.vue: -------------------------------------------------------------------------------- 1 | 88 | 89 | 263 | 264 | 278 | -------------------------------------------------------------------------------- /backend/static/assets/index.34a494f0.js: -------------------------------------------------------------------------------- 1 | var e=Object.defineProperty,a=Object.defineProperties,s=Object.getOwnPropertyDescriptors,t=Object.getOwnPropertySymbols,o=Object.prototype.hasOwnProperty,c=Object.prototype.propertyIsEnumerable,r=(a,s,t)=>s in a?e(a,s,{enumerable:!0,configurable:!0,writable:!0,value:t}):a[s]=t,n=(e,a)=>{for(var s in a||(a={}))o.call(a,s)&&r(e,s,a[s]);if(t)for(var s of t(a))c.call(a,s)&&r(e,s,a[s]);return e},i=(e,t)=>a(e,s(t));"undefined"!=typeof require&&require;import{p as l,a as d,o as p,c as m,r as u,b as k,d as y,F as f,k as v,u as w,e as g,f as b,g as h,t as x,h as C,w as j,i as S,_ as P,j as _,l as K,m as A,n as W,q as I,s as L,v as O,x as V}from"./vendor.4eb73c88.js";!function(){const e=document.createElement("link").relList;if(!(e&&e.supports&&e.supports("modulepreload"))){for(const e of document.querySelectorAll('link[rel="modulepreload"]'))a(e);new MutationObserver((e=>{for(const s of e)if("childList"===s.type)for(const e of s.addedNodes)"LINK"===e.tagName&&"modulepreload"===e.rel&&a(e)})).observe(document,{childList:!0,subtree:!0})}function a(e){if(e.ep)return;e.ep=!0;const a=function(e){const a={};return e.integrity&&(a.integrity=e.integrity),e.referrerpolicy&&(a.referrerPolicy=e.referrerpolicy),"use-credentials"===e.crossorigin?a.credentials="include":"anonymous"===e.crossorigin?a.credentials="omit":a.credentials="same-origin",a}(e);fetch(e.href,a)}}();l("data-v-4b23e37a"),d();const N={},Q={class:"NinjaLogo",src:"/assets/logo.03d6d6da.png",alt:"logo"};N.render=function(e,a){return p(),m("img",Q)};const U={components:{Logo:N}};l("data-v-1f23ce5f");const q={class:"header"},D={class:"header-wrapper"},R={class:"flex items-center"},z=k("p",{class:"pl-3 select-none"},"Ninja",-1);d(),U.render=function(e,a,s,t,o,c){const r=u("Logo");return p(),m("div",q,[k("div",D,[k("div",R,[y(r,{class:"h-10 w-10"}),z])])])},U.__scopeId="data-v-1f23ce5f";const T={class:"main"},E={setup:e=>(e,a)=>{const s=u("router-view");return p(),m(f,null,[y(U),k("div",T,[y(s)])],64)}};const J=v.create({prefixUrl:"/api",retry:{limit:0}});function $(e){return J.post("WSCKLogin",{json:e}).json()}const Z={setup(){const e=w();g();let a=b({remark:"",jdwsck:void 0,nickName:void 0,timestamp:void 0});const s=async()=>{try{const e=localStorage.getItem("eid"),s=localStorage.getItem("wseid");if(!e&&!s)return void t();if(e){const s=await function(e){const a=new URLSearchParams;return a.set("eid",e),J.get("userinfo",{searchParams:a}).json()}(e);if(!s)return P.error("获取用户CK信息失败,请重重新登录"),void t();a.nickName=s.data.nickName,a.timestamp=new Date(s.data.timestamp).toLocaleString()}if(s){const e=await getWSCKUserinfoAPI(s);if(!e)return P.error("获取用户WSCK信息失败,请重重新登录"),void t();a.nickName=e.data.nickName,a.timestamp=new Date(e.data.timestamp).toLocaleString()}}catch(e){console.error(e)}};h(s);const t=()=>{localStorage.removeItem("eid"),localStorage.removeItem("wseid"),e.push("/login")};return i(n({},x(a)),{activity:[{name:"玩一玩(可找到大多数活动)",address:"京东 APP 首页-频道-边玩边赚",href:"https://funearth.m.jd.com/babelDiy/Zeus/3BB1rymVZUo4XmicATEUSDUgHZND/index.html"},{name:"宠汪汪",address:"京东APP-首页/玩一玩/我的-宠汪汪"},{name:"东东萌宠",address:"京东APP-首页/玩一玩/我的-东东萌宠"},{name:"东东农场",address:"京东APP-首页/玩一玩/我的-东东农场"},{name:"东东工厂",address:"京东APP-首页/玩一玩/我的-东东工厂"},{name:"东东超市",address:"京东APP-首页/玩一玩/我的-东东超市"},{name:"领现金",address:"京东APP-首页/玩一玩/我的-领现金"},{name:"东东健康社区",address:"京东APP-首页/玩一玩/我的-东东健康社区"},{name:"京喜农场",address:"京喜APP-我的-京喜农场"},{name:"京喜牧场",address:"京喜APP-我的-京喜牧场"},{name:"京喜工厂",address:"京喜APP-我的-京喜工厂"},{name:"京喜财富岛",address:"京喜APP-我的-京喜财富岛"},{name:"京东极速版红包",address:"京东极速版APP-我的-红包"}],getInfo:s,logout:t,delAccount:async()=>{try{const e=localStorage.getItem("eid"),a=await function(e){return J.post("delaccount",{json:e}).json()}({eid:e});200!==a.code?P.error(a.message):(P.success(a.message),setTimeout((()=>{t()}),1e3))}catch(e){console.error(e)}},changeremark:async()=>{try{const s=localStorage.getItem("eid"),t=localStorage.getItem("wseid"),o=a.remark;if(s){const e=await function(e){return J.post("update/remark",{json:e}).json()}({eid:s,remark:o});200!==e.code?P.success(e.message):P.error(e.message)}if(t){const a=await(e={wseid:t,remark:o},J.post("updateWSCK/remark",{json:e}).json());200!==a.code?P.success(a.message):P.error(a.message)}}catch(s){console.error(s)}var e},WSCKLogin:async()=>{try{const e=a.jdwsck.match(/wskey=(.*?);/)&&a.jdwsck.match(/wskey=(.*?);/)[1],s=a.jdwsck.match(/pin=(.*?);/)&&a.jdwsck.match(/pin=(.*?);/)[1];if(e&&s){const a=await $({wskey:e,pin:s});a.data.wseid?(localStorage.setItem("wseid",a.data.wseid),P.success(a.message)):P.error(a.message||"wskey 解析失败,请检查后重试!")}else P.error("wskey 解析失败,请检查后重试!")}catch(e){console.error(e)}},delWSCKAccount:async()=>{try{const e=localStorage.getItem("wseid"),a=await function(e){return J.post("WSCKDelaccount",{json:e}).json()}({wseid:e});200!==a.code?P.error(a.message):(P.success(a.message),setTimeout((()=>{t()}),1e3))}catch(e){console.error(e)}},openUrlWithJD:e=>{const a=encodeURIComponent(`{"category":"jump","des":"m","action":"to","url":"${e}"}`);window.location.href=`openapp.jdmobile://virtual?params=${a}`,console.log(window.location.href)}})}};l("data-v-71afa157");const B={class:"content"},G={class:"card"},H=k("div",{class:"card-header"},[k("p",{class:"card-title"},"个人中心")],-1),F={class:"card-body"},M={class:"card-footer"},X=A("退出登录"),Y=A("删除CK"),ee={class:"card"},ae=_('

WSCK 录入

wskey有效期长达一年,请联系管理员确认使用,慎重!

删WSCK在下方。

也可以保持pin不变,随意更改wskey,等同于删除WSCK。改密码解决一切CK泄露问题。

用户须手动提取pin和wskey,格式如:"pin=xxxxxx;wskey=xxxxxxxxxx;"。

——IOS用户手机抓包APP 点击跳转安装

——在api.m.jd.com域名下找POST请求大概率能找到wskey。

wskey在录入后立马上线,系统会在指定时间检查wskey,有效则自动转换出cookie登录

cookie失效后,也会在系统设定的指定时间内自动转换出新的cookie,实现一次录入长期有效

',1),se={class:"card-body text-center"},te={class:"card-footer"},oe=A("重新录入"),ce=A("删除WSCK"),re={class:"card"},ne=k("div",{class:"card-header"},[k("p",{class:"card-title"},"修改备注(CK和WSCK同步)")],-1),ie={class:"card-body text-center"},le={class:"card-footer"},de=A("修改"),pe={class:"card"},me=k("div",{class:"card-header"},[k("p",{class:"card-title"},"常见活动位置"),k("span",{class:"card-subtitle"},"下面是一些常见活动的位置")],-1),ue={class:"card-body"},ke={class:"pr-2"},ye=["onClick"];d(),Z.render=function(e,a,s,t,o,c){const r=u("el-button"),n=u("el-input");return p(),m("div",B,[k("div",G,[H,k("div",F,[k("p",null,"昵称:"+C(e.nickName),1),k("p",null,"更新时间:"+C(e.timestamp),1)]),k("div",M,[y(r,{size:"small",auto:"",onClick:t.logout},{default:j((()=>[X])),_:1},8,["onClick"]),y(r,{type:"danger",size:"small",auto:"",onClick:t.delAccount},{default:j((()=>[Y])),_:1},8,["onClick"])])]),k("div",ee,[ae,k("div",se,[y(n,{modelValue:e.jdwsck,"onUpdate:modelValue":a[0]||(a[0]=a=>e.jdwsck=a),placeholder:"pin=xxxxxx;wskey=xxxxxxxxxx;",size:"small",clearable:"",class:"my-4 w-full"},null,8,["modelValue"])]),k("div",te,[y(r,{type:"success",size:"small",auto:"",onClick:t.WSCKLogin},{default:j((()=>[oe])),_:1},8,["onClick"]),y(r,{type:"danger",size:"small",auto:"",onClick:t.delWSCKAccount},{default:j((()=>[ce])),_:1},8,["onClick"])])]),k("div",re,[ne,k("div",ie,[y(n,{modelValue:e.remark,"onUpdate:modelValue":a[1]||(a[1]=a=>e.remark=a),size:"small",clearable:"",class:"my-4 w-full"},null,8,["modelValue"])]),k("div",le,[y(r,{type:"success",size:"small",auto:"",onClick:t.changeremark},{default:j((()=>[de])),_:1},8,["onClick"])])]),k("div",pe,[me,k("div",ue,[k("ul",null,[(p(!0),m(f,null,S(t.activity,((e,a)=>(p(),m("li",{key:a,class:"leading-normal"},[k("span",null,C(e.name)+":",1),k("span",ke,C(e.address),1),e.href?(p(),m("a",{key:0,class:"text-blue-400",href:"#",onClick:a=>t.openUrlWithJD(e.href)},"直达链接",8,ye)):K("",!0)])))),128))])])])])},Z.__scopeId="data-v-71afa157";const fe={setup(){const e=w();g();let a=b({marginCount:0,allowAdd:!0,cookie:"",QRCode:void 0,qrCodeVisibility:!1,token:void 0,okl_token:void 0,cookies:void 0,timer:void 0,waitLogin:!1,marginWSCKCount:0,allowWSCKAdd:!0,jdwsck:void 0,showQR:!1,showWSCK:!1,showCK:!0});const s=async()=>{try{const e=(await J.get("info").json()).data;a.marginCount=e.marginCount,a.allowAdd=e.allowAdd,a.marginWSCKCount=e.marginWSCKCount,a.allowWSCKAdd=e.allowWSCKAdd,a.showQR=e.showQR,a.showWSCK=e.showWSCK,a.showCK=e.showCK}catch(e){console.error(e)}},t=async()=>{if(this.showQR)try{const e=await J.get("qrcode").json();a.token=e.data.token,a.okl_token=e.data.okl_token,a.cookies=e.data.cookies,a.QRCode=e.data.QRCode,a.QRCode&&(a.waitLogin=!0,clearInterval(a.timer),a.timer=setInterval(o,3e3))}catch(e){console.error(e),P.error("生成二维码失败!请重试或放弃")}else P.warning("扫码已禁用请手动抓包")},o=async()=>{try{const s=await function(e){return J.post("check",{json:e}).json()}({token:a.token,okl_token:a.okl_token,cookies:a.cookies});switch(null==s?void 0:s.data.errcode){case 0:localStorage.setItem("eid",s.data.eid),P.success(s.message),clearInterval(a.timer),e.push("/");break;case 176:break;default:P.error(s.message),a.waitLogin=!1,clearInterval(a.timer)}}catch(s){clearInterval(a.timer),a.waitLogin=!1}};return h((()=>{s(),t()})),i(n({},x(a)),{getInfo:s,getQrcode:t,showQrcode:async()=>{a.qrCodeVisibility=!0},ckeckLogin:o,jumpLogin:async()=>{const e=`openapp.jdmobile://virtual/ad?params={"category":"jump","des":"ThirdPartyLogin","action":"to","onekeylogin":"return","url":"https://plogin.m.jd.com/cgi-bin/m/tmauth?appid=300&client_type=m&token=${a.token}","authlogin_returnurl":"weixin://","browserlogin_fromurl":"${window.location.host}"}`;window.location.href=e},CKLogin:async()=>{try{const s=a.cookie.match(/pt_key=(.*?);/)&&a.cookie.match(/pt_key=(.*?);/)[1],t=a.cookie.match(/pt_pin=(.*?);/)&&a.cookie.match(/pt_pin=(.*?);/)[1];if(s&&t){const a=await function(e){return J.post("cklogin",{json:e}).json()}({pt_key:s,pt_pin:t});a.data.eid?(localStorage.setItem("eid",a.data.eid),P.success(a.message),e.push("/")):P.error(a.message||"cookie 解析失败,请检查后重试!")}else P.error("cookie 解析失败,请检查后重试!")}catch(s){console.error(s)}},WSCKLogin:async()=>{try{const s=a.jdwsck.match(/wskey=(.*?);/)&&a.jdwsck.match(/wskey=(.*?);/)[1],t=a.jdwsck.match(/pin=(.*?);/)&&a.jdwsck.match(/pin=(.*?);/)[1];if(s&&t){const a=await $({wskey:s,pin:t});a.data.wseid?(localStorage.setItem("wseid",a.data.wseid),P.success(a.message),e.push("/")):P.error(a.message||"wskey 解析失败,请检查后重试!")}else P.error("wskey 解析失败,请检查后重试!")}catch(s){console.error(s)}}})}};l("data-v-7065ab79");const ve={class:"content"},we=_('

KingRom提醒您

为了您的财产安全请关闭免密支付以及打开支付验密(京东-设置-支付设置-支付验密设置)。

建议京东账户绑定微信以保证提现能到账。

由于京东异地登录限制,扫码获取cookie只有2小时有效期,因此暂时关闭扫码功能,现需手动抓取Cookie。

且有效期不长,平均3-5天,因此需要及时更新。

安全起见,WSCK可以在CK登录后录入,期限半永久。
',1),ge={key:0,class:"card"},be={class:"card-header"},he={class:"flex items-center justify-between"},xe=k("p",{class:"card-title"},"扫码登录",-1),Ce={class:"ml-2 px-2 py-1 bg-gray-200 rounded-full font-normal text-xs"},je=k("span",{class:"card-subtitle"}," 请点击下方按钮登录,点击按钮后回到本网站查看是否登录成功,京东的升级提示不用管。 ",-1),Se={class:"card-body text-center"},Pe={key:0,class:"flex flex-col w-48 m-auto mt-4"},_e=A("扫描二维码登录"),Ke=A("跳转到京东 App 登录"),Ae=["src"],We=k("div",{class:"card-footer"},null,-1),Ie={key:1,class:"card"},Le={class:"card-header"},Oe={class:"flex items-center justify-between"},Ve=k("p",{class:"card-title"},"WSCK 录入",-1),Ne={class:"ml-2 px-2 py-1 bg-gray-200 rounded-full font-normal text-xs"},Qe=_('
wskey有效期长达一年,请联系管理员确认使用(删不掉,慎用)

用户须手动提取pin和wskey,格式如:"pt_pin=xxxxxx;wskey=xxxxxxxxxx;"。

——IOS用户手机抓包APP 点击跳转安装

——在api.m.jd.com域名下找POST请求大概率能找到wskey。

wskey在录入后立马上线,系统会在指定时间检查wskey,有效则自动转换出cookie登录

cookie失效后,也会在系统设定的指定时间内自动转换出新的cookie,实现一次录入长期有效

请在下方输入您的 WSCK ',2),Ue={class:"card-body text-center"},qe=A("录入"),De=k("div",{class:"card-footet"},null,-1),Re={key:2,class:"card"},ze={class:"card-header"},Te={class:"flex items-center justify-between"},Ee=k("p",{class:"card-title"},"CK 登录",-1),Je={class:"ml-2 px-2 py-1 bg-gray-200 rounded-full font-normal text-xs"},$e=k("div",{class:"card-body text-base leading-6"},[k("p",null,[A("PC用户建议使用浏览器"),k("a",{style:{},href:"https://www.juan920.com/?s=cookie",target:"_blank",id:"kingrom"},"Cookie获取教程"),A("获取cookie并在下方填写。")]),k("p",null,[A("手机用户可以使用Alook浏览器登录"),k("a",{style:{},href:"https://m.jd.com/",target:"_blank",id:"jd"},"JD官网"),A(",并在菜单-工具箱-开发者工具-Cookies中获取(Android和iPhone通用)。")]),k("p",null,"另外也可以使用抓包工具(iPhone:Stream,Android:HttpCanary)抓取京东app的ck,要注意pt_key和pt_pin字段是以app_open开头的。"),k("p",null,"cookie直接填入输入框即可,Ninja会自动正则提取pt_key和pt_pin。")],-1),Ze=k("span",{class:"card-subtitle"}," 请在下方输入您的 cookie 登录。 ",-1),Be={class:"card-body text-center"},Ge=A("登录"),He=k("div",{class:"card-footet"},null,-1);d(),fe.render=function(e,a,s,t,o,c){const r=u("el-button"),n=u("el-input");return p(),m("div",ve,[we,e.showQR?(p(),m("div",ge,[k("div",be,[k("div",he,[xe,k("span",Ce,"余量:"+C(e.marginCount),1)]),je]),k("div",Se,[e.qrCodeVisibility?(p(),m("img",{key:1,src:e.QRCode,width:256,class:"m-auto"},null,8,Ae)):(p(),m("div",Pe,[y(r,{type:"primary",round:"",onClick:t.showQrcode},{default:j((()=>[_e])),_:1},8,["onClick"]),y(r,{class:"mt-4 ml-0",type:"primary",round:"",onClick:t.jumpLogin},{default:j((()=>[Ke])),_:1},8,["onClick"])]))]),We])):K("",!0),e.showWSCK?(p(),m("div",Ie,[k("div",Le,[k("div",Oe,[Ve,k("span",Ne,"余量:"+C(e.marginWSCKCount),1)]),Qe]),k("div",Ue,[y(n,{modelValue:e.jdwsck,"onUpdate:modelValue":a[0]||(a[0]=a=>e.jdwsck=a),placeholder:"pin=xxxxxx;wskey=xxxxxxxxxx;",size:"small",clearable:"",class:"my-4 w-full"},null,8,["modelValue"]),y(r,{type:"primary",size:"small",round:"",onClick:t.WSCKLogin},{default:j((()=>[qe])),_:1},8,["onClick"])]),De])):K("",!0),e.showCK?(p(),m("div",Re,[k("div",ze,[k("div",Te,[Ee,k("span",Je,"余量:"+C(e.marginCount),1)]),$e,Ze]),k("div",Be,[y(n,{modelValue:e.cookie,"onUpdate:modelValue":a[1]||(a[1]=a=>e.cookie=a),size:"small",clearable:"",class:"my-4 w-full"},null,8,["modelValue"]),y(r,{type:"primary",size:"small",round:"",onClick:t.CKLogin},{default:j((()=>[Ge])),_:1},8,["onClick"])]),He])):K("",!0)])},fe.__scopeId="data-v-7065ab79";const Fe=[{path:"/",component:Z},{path:"/login",component:fe}],Me=W({history:I(),routes:Fe}),Xe=[O,V,P],Ye=[P],ea=L(E);Xe.forEach((e=>{ea.component(e.name,e)})),Ye.forEach((e=>{ea.use(e)})),ea.use(Me),ea.mount("#app"); 2 | -------------------------------------------------------------------------------- /backend/user.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable camelcase */ 2 | 'use strict'; 3 | 4 | const got = require('got'); 5 | const got1 = require('got'); 6 | require('dotenv').config(); 7 | const QRCode = require('qrcode'); 8 | // 新增 , addWSCKEnv, delWSCKEnv, getWSCKEnvs, getWSCKEnvsCount, updateWSCKEnv 9 | const { addEnv, delEnv, getEnvs, getEnvsCount, updateEnv , addWSCKEnv, delWSCKEnv, getWSCKEnvs, getWSCKEnvsCount, updateWSCKEnv } = require('./ql'); 10 | const path = require('path'); 11 | const qlDir = process.env.QL_DIR || '/ql'; 12 | const notifyFile = path.join(qlDir, 'shell/notify.sh'); 13 | const { exec } = require('child_process'); 14 | const { GET_RANDOM_TIME_UA } = require('./utils/USER_AGENT'); 15 | 16 | const api = got.extend({ 17 | retry: { limit: 0 }, 18 | responseType: 'json', 19 | }); 20 | 21 | module.exports = class User { 22 | ua; 23 | pt_key; 24 | pt_pin; 25 | pin;// 新增变量 26 | wskey;// 新增变量 27 | jdwsck;// 新增变量 28 | code;// 新增变量 29 | msg;// 新增变量 30 | cookie; 31 | eid; 32 | wseid 33 | timestamp; 34 | nickName; 35 | token; 36 | okl_token; 37 | cookies; 38 | QRCode; 39 | remark; 40 | #s_token; 41 | // 新增wskey构造入参 42 | constructor({ token, okl_token, cookies, pt_key, pt_pin, cookie, eid, wseid, remarks, remark, ua, pin, wskey, jdwsck}) { 43 | this.token = token; 44 | this.okl_token = okl_token; 45 | this.cookies = cookies; 46 | this.pt_key = pt_key; 47 | this.pt_pin = pt_pin; 48 | this.cookie = cookie; 49 | this.eid = eid; 50 | this.wseid = wseid; 51 | this.remark = remark; 52 | this.ua = ua; 53 | 54 | if (pt_key && pt_pin) { 55 | this.cookie = 'pt_key=' + this.pt_key + ';pt_pin=' + this.pt_pin + ';'; 56 | } 57 | 58 | if (cookie) { 59 | this.pt_pin = cookie.match(/pt_pin=(.*?);/)[1]; 60 | this.pt_key = cookie.match(/pt_key=(.*?);/)[1]; 61 | } 62 | 63 | if (remarks) { 64 | this.remark = remarks.match(/remark=(.*?);/) && remarks.match(/remark=(.*?);/)[1]; 65 | } 66 | ///////////////////////////////////////////////// 67 | // 新增pin 68 | this.pin = pin; 69 | // 新增wskey 70 | this.wskey = wskey; 71 | // 新增 jdwsck 72 | this.jdwsck = jdwsck; 73 | // 新增如果wskey和pin不是空则产生jdwsck 74 | if (pin && wskey) { 75 | this.jdwsck = 'pin=' + this.pin + ';wskey=' + this.wskey + ';'; 76 | } 77 | 78 | // 新增如果备注是空则默认取pt_pin作为备注 79 | if (this.jdwsck && this.remark === null || this.remark === '') { 80 | this.remark = this.pin; 81 | } 82 | 83 | // 新增如果nickName是空默认取pt_pin作为备注 84 | if (this.jdwsck && this.nickName === null || this.nickName === '') { 85 | this.nickName = this.pin; 86 | } 87 | ///////////////////////////////////////////////// 88 | } 89 | 90 | async getQRConfig() { 91 | this.ua = this.ua || process.env.NINJA_UA || GET_RANDOM_TIME_UA(); 92 | const taskUrl = `https://plogin.m.jd.com/cgi-bin/mm/new_login_entrance?lang=chs&appid=300&returnurl=https://wq.jd.com/passport/LoginRedirect?state=${Date.now()}&returnurl=https://home.m.jd.com/myJd/newhome.action?sceneval=2&ufc=&/myJd/home.action&source=wq_passport`; 93 | const response = await api({ 94 | url: taskUrl, 95 | headers: { 96 | Connection: 'Keep-Alive', 97 | 'Content-Type': 'application/x-www-form-urlencoded', 98 | Accept: 'application/json, text/plain, */*', 99 | 'Accept-Language': 'zh-cn', 100 | Referer: taskUrl, 101 | 'User-Agent': this.ua, 102 | Host: 'plogin.m.jd.com', 103 | }, 104 | }); 105 | const headers = response.headers; 106 | const data = response.body; 107 | await this.#formatSetCookies(headers, data); 108 | 109 | if (!this.#s_token) { 110 | throw new Error('二维码创建失败!'); 111 | } 112 | 113 | const nowTime = Date.now(); 114 | // eslint-disable-next-line prettier/prettier 115 | const taskPostUrl = `https://plogin.m.jd.com/cgi-bin/m/tmauthreflogurl?s_token=${ 116 | this.#s_token 117 | }&v=${nowTime}&remember=true`; 118 | 119 | const configRes = await api({ 120 | method: 'post', 121 | url: taskPostUrl, 122 | body: `lang=chs&appid=300&source=wq_passport&returnurl=https://wqlogin2.jd.com/passport/LoginRedirect?state=${nowTime}&returnurl=//home.m.jd.com/myJd/newhome.action?sceneval=2&ufc=&/myJd/home.action`, 123 | headers: { 124 | Connection: 'Keep-Alive', 125 | 'Content-Type': 'application/x-www-form-urlencoded', 126 | Accept: 'application/json, text/plain, */*', 127 | 'Accept-Language': 'zh-cn', 128 | Referer: taskUrl, 129 | 'User-Agent': this.ua, 130 | Host: 'plogin.m.jd.com', 131 | Cookie: this.cookies, 132 | }, 133 | }); 134 | const configHeaders = configRes.headers; 135 | const configData = configRes.body; 136 | 137 | this.token = configData.token; 138 | if (this.token) 139 | this.QRCode = await QRCode.toDataURL( 140 | `https://plogin.m.jd.com/cgi-bin/m/tmauth?appid=300&client_type=m&token=${this.token}` 141 | ); 142 | const cookies = configHeaders['set-cookie'][0]; 143 | this.okl_token = cookies.substring(cookies.indexOf('=') + 1, cookies.indexOf(';')); 144 | } 145 | 146 | async checkQRLogin() { 147 | if(true){ 148 | return { 149 | errcode: 200, 150 | message: '扫码登录已关闭,请自行抓包手动CK登录', 151 | }; 152 | } 153 | if (!this.token || !this.okl_token || !this.cookies) { 154 | throw new Error('初始化登录请求失败!'); 155 | } 156 | const nowTime = Date.now(); 157 | const loginUrl = `https://plogin.m.jd.com/login/login?appid=300&returnurl=https://wqlogin2.jd.com/passport/LoginRedirect?state=${nowTime}&returnurl=//home.m.jd.com/myJd/newhome.action?sceneval=2&ufc=&/myJd/home.action&source=wq_passport`; 158 | const getUserCookieUrl = `https://plogin.m.jd.com/cgi-bin/m/tmauthchecktoken?&token=${this.token}&ou_state=0&okl_token=${this.okl_token}`; 159 | const response = await api({ 160 | method: 'POST', 161 | url: getUserCookieUrl, 162 | body: `lang=chs&appid=300&source=wq_passport&returnurl=https://wqlogin2.jd.com/passport/LoginRedirect?state=${nowTime}&returnurl=//home.m.jd.com/myJd/newhome.action?sceneval=2&ufc=&/myJd/home.action`, 163 | headers: { 164 | Connection: 'Keep-Alive', 165 | 'Content-Type': 'application/x-www-form-urlencoded; Charset=UTF-8', 166 | Accept: 'application/json, text/plain, */*', 167 | 'Accept-Language': 'zh-cn', 168 | Referer: loginUrl, 169 | 'User-Agent': this.ua, 170 | Cookie: this.cookies, 171 | }, 172 | }); 173 | const data = response.body; 174 | const headers = response.headers; 175 | if (data.errcode === 0) { 176 | const pt_key = headers['set-cookie'][1]; 177 | this.pt_key = pt_key.substring(pt_key.indexOf('=') + 1, pt_key.indexOf(';')); 178 | const pt_pin = headers['set-cookie'][2]; 179 | this.pt_pin = pt_pin.substring(pt_pin.indexOf('=') + 1, pt_pin.indexOf(';')); 180 | this.cookie = 'pt_key=' + this.pt_key + ';pt_pin=' + this.pt_pin + ';'; 181 | 182 | const result = await this.CKLogin(); 183 | result.errcode = 0; 184 | return result; 185 | } 186 | 187 | return { 188 | errcode: data.errcode, 189 | message: data.message, 190 | }; 191 | } 192 | 193 | async CKLogin() { 194 | let message; 195 | await this.#getNickname(); 196 | const envs = await getEnvs(); 197 | const poolInfo = await User.getPoolInfo(); 198 | const env = await envs.find((item) => item.value.match(/pt_pin=(.*?);/)[1] === this.pt_pin); 199 | if (!env) { 200 | // 新用户 201 | if (!poolInfo.allowAdd) { 202 | throw new UserError('管理员已关闭注册,去其他地方看看吧', 210, 200); 203 | } else if (poolInfo.marginCount === 0) { 204 | throw new UserError('本站已到达注册上限,你来晚啦', 211, 200); 205 | } else { 206 | const remarks = `remark=${this.nickName};`; 207 | const body = await addEnv(this.cookie, remarks); 208 | if (body.code !== 200) { 209 | throw new UserError(body.message || '添加账户错误,请重试', 220, body.code || 200); 210 | } 211 | this.eid = body.data[0]._id; 212 | this.timestamp = body.data[0].timestamp; 213 | message = `注册成功,${this.nickName}`; 214 | this.#sendNotify('Ninja 运行通知', `用户 ${this.nickName}(${decodeURIComponent(this.pt_pin)}) 已上线`); 215 | } 216 | } else { 217 | this.eid = env._id; 218 | const body = await updateEnv(this.cookie, this.eid); 219 | if (body.code !== 200) { 220 | throw new UserError(body.message || '更新账户错误,请重试', 221, body.code || 200); 221 | } 222 | this.timestamp = body.data.timestamp; 223 | message = `欢迎回来,${this.nickName}`; 224 | this.#sendNotify('Ninja 运行通知', `用户 ${this.nickName}(${decodeURIComponent(this.pt_pin)}) 已更新 CK`); 225 | } 226 | return { 227 | nickName: this.nickName, 228 | eid: this.eid, 229 | timestamp: this.timestamp, 230 | message, 231 | }; 232 | } 233 | 234 | async getUserInfoByEid() { 235 | const envs = await getEnvs(); 236 | const env = await envs.find((item) => item._id === this.eid); 237 | if (!env) { 238 | throw new UserError('没有找到这个账户,重新登录试试看哦', 230, 200); 239 | } 240 | this.cookie = env.value; 241 | this.timestamp = env.timestamp; 242 | const remarks = env.remarks; 243 | if (remarks) { 244 | this.remark = remarks.match(/remark=(.*?);/) && remarks.match(/remark=(.*?);/)[1]; 245 | } 246 | await this.#getNickname(); 247 | return { 248 | nickName: this.nickName, 249 | eid: this.eid, 250 | timestamp: this.timestamp, 251 | remark: this.remark, 252 | }; 253 | } 254 | 255 | async updateRemark() { 256 | if (!this.eid || !this.remark || this.remark.replace(/(^\s*)|(\s*$)/g, '') === '') { 257 | throw new UserError('eid参数错误', 240, 200); 258 | } 259 | 260 | const envs = await getEnvs(); 261 | const env = await envs.find((item) => item._id === this.eid); 262 | if (!env) { 263 | throw new UserError('没有找到这个ck账户,重新登录试试看哦', 230, 200); 264 | } 265 | this.cookie = env.value; 266 | 267 | const remarks = `remark=${this.remark};`; 268 | 269 | const updateEnvBody = await updateEnv(this.cookie, this.eid, remarks); 270 | if (updateEnvBody.code !== 200) { 271 | throw new UserError('ck更新/上传备注出错,请重试', 241, 200); 272 | } 273 | 274 | return { 275 | message: 'ck更新/上传备注成功', 276 | }; 277 | } 278 | 279 | async delUserByEid() { 280 | await this.getUserInfoByEid(); 281 | const body = await delEnv(this.eid); 282 | if (body.code !== 200) { 283 | throw new UserError(body.message || '删除账户错误,请重试', 240, body.code || 200); 284 | } 285 | this.#sendNotify('Ninja 运行通知', `用户 ${this.nickName}(${decodeURIComponent(this.pt_pin)}) 删号跑路了`); 286 | return { 287 | message: '账户已移除', 288 | }; 289 | } 290 | 291 | ///////////////////////////////////////////////// 292 | // 新增同步方法 293 | async WSCKLogin() { 294 | let message; 295 | await this.#getWSCKCheck(); 296 | const envs = await getWSCKEnvs();// 1 297 | const poolInfo = await User.getPoolInfo(); 298 | const env = await envs.find((item) => item.value.match(/pin=(.*?);/)[1] === this.pin); 299 | if (!env) { 300 | // 新用户 301 | if (!poolInfo.allowWSCKAdd) { 302 | throw new UserError('管理员已关闭注册,去其他地方看看吧', 210, 200); 303 | } else if (poolInfo.marginWSCKCount === 0) { 304 | throw new UserError('本站已到达注册上限,你来晚啦', 211, 200); 305 | } else { 306 | const remarks = `remark=${this.nickName};`; 307 | const body = await addWSCKEnv(this.jdwsck, remarks); 308 | if (body.code !== 200) { 309 | throw new UserError(body.message || '添加账户错误,请重试', 220, body.code || 200); 310 | } 311 | this.wseid = body.data[0]._id; 312 | this.timestamp = body.data[0].timestamp; 313 | message = `录入成功,${this.pin}`; 314 | this.#sendNotify('Ninja 运行通知', `用户 ${this.pin} WSCK 添加成功`); 315 | } 316 | } else { 317 | this.wseid = env._id; 318 | const body = await updateWSCKEnv(this.jdwsck, this.wseid); 319 | if (body.code !== 200) { 320 | throw new UserError(body.message || '更新账户错误,请重试', 221, body.code || 200); 321 | } 322 | this.timestamp = body.data.timestamp; 323 | message = `欢迎回来,${this.nickName}`; 324 | this.#sendNotify('Ninja 运行通知', `用户 ${this.pin} 已更新 WSCK`); 325 | } 326 | 327 | 328 | return { 329 | nickName: this.nickName, 330 | eid: this.eid, 331 | wseid: this.wseid, 332 | timestamp: this.timestamp, 333 | message, 334 | }; 335 | } 336 | 337 | //不查nickname了,用remark代替 338 | async getWSCKUserInfoByEid() { 339 | const envs = await getWSCKEnvs(); 340 | const env = await envs.find((item) => item._id === this.wseid); 341 | if (!env) { 342 | throw new UserError('没有找到这个账户,重新登录试试看哦', 230, 200); 343 | } 344 | this.jdwsck = env.value; 345 | this.timestamp = env.timestamp; 346 | const remarks = env.remarks; 347 | if (remarks) { 348 | this.remark = remarks.match(/remark=(.*?);/) && remarks.match(/remark=(.*?);/)[1]; 349 | } 350 | // await this.#getNickname(); 351 | return { 352 | nickName: this.remark, 353 | wseid: this.wseid, 354 | timestamp: this.timestamp, 355 | remark: this.remark, 356 | }; 357 | } 358 | 359 | async updateWSCKRemark() { 360 | if (!this.wseid || !this.remark || this.remark.replace(/(^\s*)|(\s*$)/g, '') === '') { 361 | throw new UserError('wseid参数错误', 240, 200); 362 | } 363 | 364 | const envs = await getWSCKEnvs(); 365 | const env = await envs.find((item) => item._id === this.wseid); 366 | if (!env) { 367 | throw new UserError('没有找到这个wskey账户,重新登录试试看哦', 230, 200); 368 | } 369 | this.jdwsck = env.value; 370 | 371 | const remarks = `remark=${this.remark};`; 372 | 373 | const updateEnvBody = await updateWSCKEnv(this.jdwsck, this.wseid, remarks); 374 | if (updateEnvBody.code !== 200) { 375 | throw new UserError('wskey更新/上传备注出错,请重试', 241, 200); 376 | } 377 | 378 | return { 379 | message: 'wskey更新/上传备注成功', 380 | }; 381 | } 382 | 383 | async delWSCKUserByEid() { 384 | await this.getWSCKUserInfoByEid(); 385 | const body = await delWSCKEnv(this.wseid); 386 | if (body.code !== 200) { 387 | throw new UserError(body.message || '删除账户错误,请重试', 240, body.code || 200); 388 | } 389 | this.#sendNotify('Ninja 运行通知', `用户 ${this.remark}(${decodeURIComponent(this.remark)}) 删号跑路了,CK将无法自动更新并会在不知道那天内自动失效`); 390 | return { 391 | message: 'wskey账户已移除', 392 | }; 393 | } 394 | 395 | ///////////////////////////////////////////////// 396 | 397 | static async getPoolInfo() { 398 | const count = await getEnvsCount(); 399 | const countWSCK = await getWSCKEnvsCount(); 400 | const allowCount = (process.env.ALLOW_NUM || 40) - count; 401 | const allowWSCKCount = (process.env.ALLOW_WSCK_NUM || 40) - countWSCK; 402 | return { 403 | marginCount: allowCount >= 0 ? allowCount : 0, 404 | marginWSCKCount: allowWSCKCount >= 0 ? allowWSCKCount : 0, 405 | allowAdd: Boolean(process.env.ALLOW_ADD) || false, 406 | allowWSCKAdd: Boolean(process.env.ALLOW_WSCK_ADD) || false, 407 | showQR: Boolean(process.env.SHOW_QR) || false, 408 | showWSCK: Boolean(process.env.SHOW_WSCK) || false, 409 | showCK: Boolean(process.env.SHOW_CK) || false, 410 | }; 411 | } 412 | 413 | static async getUsers() { 414 | const envs = await getEnvs(); 415 | const result = envs.map(async (env) => { 416 | const user = new User({ cookie: env.value, remarks: env.remarks }); 417 | await user.#getNickname(true); 418 | return { 419 | pt_pin: user.pt_pin, 420 | nickName: user.nickName, 421 | remark: user.remark || user.nickName, 422 | }; 423 | }); 424 | return Promise.all(result); 425 | } 426 | 427 | async #getNickname(nocheck) { 428 | let body; 429 | let body_bak; 430 | body = await api({ 431 | url: `https://me-api.jd.com/user_new/info/GetJDUserInfoUnion?orgFlag=JD_PinGou_New&callSource=mainorder&channel=4&isHomewhite=0&sceneval=2&_=${Date.now()}&sceneval=2&g_login_type=1&g_ty=ls`, 432 | headers: { 433 | Accept: '*/*', 434 | 'Accept-Encoding': 'gzip, deflate, br', 435 | 'Accept-Language': 'zh-cn', 436 | Connection: 'keep-alive', 437 | Cookie: this.cookie, 438 | Referer: 'https://home.m.jd.com/myJd/newhome.action', 439 | 'User-Agent': 440 | 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.111 Safari/537.36', 441 | Host: 'me-api.jd.com', 442 | }, 443 | }).json(); 444 | 445 | if (!body.data?.userInfo && !nocheck) { 446 | body_bak = await api({ 447 | url: `https://wq.jd.com/user_new/info/GetJDUserInfoUnion?orgFlag=JD_PinGou_New&callSource=mainorder`, 448 | headers: { 449 | Connection: 'keep-alive', 450 | Cookie: this.cookie, 451 | Referer: 'https://home.m.jd.com/myJd/home.action', 452 | 'User-Agent': 453 | 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.111 Safari/537.36', 454 | }, 455 | }).json(); 456 | } 457 | 458 | if (!body.data?.userInfo && !body_bak?.data.userInfo && this.jdwsck && !nocheck) { 459 | throw new UserError('获取用户信息失败,请检查您的 wskey !', 201, 200); 460 | } else if (!body.data?.userInfo && !body_bak?.data.userInfo && !nocheck) { 461 | throw new UserError('获取用户信息失败,请检查您的 cookie !', 201, 200); 462 | } 463 | this.nickName = (body.data?.userInfo.baseInfo.nickname || body_bak?.data.userInfo.baseInfo.nickname) || decodeURIComponent(this.pt_pin); 464 | 465 | // if (!body.data?.userInfo && this.jdwsck) { 466 | // throw new UserError('获取用户信息失败,请检查您的 wskey !', 201, 200); 467 | // } else if (!body.data?.userInfo && !nocheck) { 468 | // throw new UserError('获取用户信息失败,请检查您的 cookie !', 201, 200); 469 | // } 470 | // this.nickName = body.data?.userInfo.baseInfo.nickname || decodeURIComponent(this.pt_pin); 471 | } 472 | 473 | #formatSetCookies(headers, body) { 474 | return new Promise((resolve) => { 475 | let guid, lsid, ls_token; 476 | this.#s_token = body.s_token; 477 | guid = headers['set-cookie'][0]; 478 | guid = guid.substring(guid.indexOf('=') + 1, guid.indexOf(';')); 479 | lsid = headers['set-cookie'][2]; 480 | lsid = lsid.substring(lsid.indexOf('=') + 1, lsid.indexOf(';')); 481 | ls_token = headers['set-cookie'][3]; 482 | ls_token = ls_token.substring(ls_token.indexOf('=') + 1, ls_token.indexOf(';')); 483 | this.cookies = `guid=${guid};lang=chs;lsid=${lsid};ls_token=${ls_token};`; 484 | resolve(); 485 | }); 486 | } 487 | 488 | #sendNotify(title, content) { 489 | const notify = process.env.NINJA_NOTIFY || true; 490 | if (!notify) { 491 | console.log('Ninja 通知已关闭\n' + title + '\n' + content + '\n' + '已跳过发送'); 492 | return; 493 | } 494 | exec(`${notifyFile} "${title}" "${content}"`, (error, stdout, stderr) => { 495 | if (error) { 496 | console.log(stderr); 497 | } else { 498 | console.log(stdout); 499 | } 500 | }); 501 | } 502 | ////////////////////////////////////////////// 503 | async #getWSCKCheck() { 504 | const s = await api({url: `https://pan.smxy.xyz/sign`}).json(); 505 | const clientVersion = s['clientVersion'] 506 | const client = s['client'] 507 | const sv = s['sv'] 508 | const st = s['st'] 509 | const uuid = s['uuid'] 510 | const sign = s['sign'] 511 | if (!sv||!st||!uuid||!sign) { 512 | throw new UserError('获取签名失败,请等待Ninja修理 !', 200, 200); 513 | } 514 | const body = await api({ 515 | method: 'POST', 516 | url: `https://api.m.jd.com/client.action?functionId=genToken&clientVersion=${clientVersion}&client=${client}&uuid=${uuid}&st=${st}&sign=${sign}&sv=${sv}`, 517 | body: 'body=%7B%22action%22%3A%22to%22%2C%22to%22%3A%22https%253A%252F%252Fplogin.m.jd.com%252Fcgi-bin%252Fm%252Fthirdapp_auth_page%253Ftoken%253DAAEAIEijIw6wxF2s3bNKF0bmGsI8xfw6hkQT6Ui2QVP7z1Xg%2526client_type%253Dandroid%2526appid%253D879%2526appup_type%253D1%22%7D&', 518 | headers: { 519 | Cookie: this.jdwsck, 520 | 'User-Agent': 'okhttp/3.12.1;jdmall;android;version/10.1.2;build/89743;screen/1440x3007;os/11;network/wifi;', 521 | 'Content-type': 'application/x-www-form-urlencoded; charset=UTF-8', 522 | 'Accept-Charset': 'UTF-8', 523 | 'Accept-Encoding': 'br,gzip,deflate' 524 | }, 525 | }).json(); 526 | const response = await got1({ 527 | followRedirect:false, 528 | url: `https://un.m.jd.com/cgi-bin/app/appjmp?tokenKey=${body['tokenKey']}&to=https://plogin.m.jd.com/cgi-bin/m/thirdapp_auth_page?token=AAEAIEijIw6wxF2s3bNKF0bmGsI8xfw6hkQT6Ui2QVP7z1Xg&client_type=android&appid=879&appup_type=1`, 529 | headers: { 530 | 'User-Agent': 'okhttp/3.12.1;jdmall;android;version/10.1.2;build/89743;screen/1440x3007;os/11;network/wifi;', 531 | Accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3', 532 | 'Accept-Charset': 'UTF-8', 533 | 'Accept-Encoding': 'br,gzip,deflate' 534 | }, 535 | }); 536 | const headers = response.headers; 537 | if (headers['set-cookie']) { 538 | const pt_key = headers['set-cookie'][2]; 539 | this.pt_key = pt_key.substring(pt_key.indexOf('=') + 1, pt_key.indexOf(';')); 540 | const pt_pin = headers['set-cookie'][3]; 541 | this.pt_pin = pt_pin.substring(pt_pin.indexOf('=') + 1, pt_pin.indexOf(';')); 542 | } 543 | if (this.pt_key&&this.pt_pin) { 544 | this.cookie = 'pt_key=' + this.pt_key + ';pt_pin=' + this.pt_pin + ';'; 545 | const result = await this.CKLogin(); 546 | this.eid = result.eid 547 | result.errcode = 0; 548 | return result; 549 | } 550 | } 551 | //////////////////////////////////////////////// 552 | }; 553 | 554 | class UserError extends Error { 555 | constructor(message, status, statusCode) { 556 | super(message); 557 | this.name = 'UserError'; 558 | this.status = status; 559 | this.statusCode = statusCode || 200; 560 | } 561 | } 562 | -------------------------------------------------------------------------------- /backend/sendNotify.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: lxk0301 https://gitee.com/lxk0301 3 | * @Date: 2020-08-19 16:12:40 4 | * @Last Modified by: whyour 5 | * @Last Modified time: 2021-5-1 15:00:54 6 | * sendNotify 推送通知功能 7 | * @param text 通知头 8 | * @param desp 通知体 9 | * @param params 某些推送通知方式点击弹窗可跳转, 例:{ url: 'https://abc.com' } 10 | * @param author 作者仓库等信息 例:`本通知 By:https://github.com/whyour/qinglong` 11 | */ 12 | 13 | const querystring = require('querystring'); 14 | const $ = new Env(); 15 | const timeout = 15000; //超时时间(单位毫秒) 16 | // =======================================go-cqhttp通知设置区域=========================================== 17 | //gobot_url 填写请求地址http://127.0.0.1/send_private_msg 18 | //gobot_token 填写在go-cqhttp文件设置的访问密钥 19 | //gobot_qq 填写推送到个人QQ或者QQ群号 20 | //go-cqhttp相关API https://docs.go-cqhttp.org/api 21 | let GOBOT_URL = ''; // 推送到个人QQ: http://127.0.0.1/send_private_msg 群:http://127.0.0.1/send_group_msg 22 | let GOBOT_TOKEN = ''; //访问密钥 23 | let GOBOT_QQ = ''; // 如果GOBOT_URL设置 /send_private_msg 则需要填入 user_id=个人QQ 相反如果是 /send_group_msg 则需要填入 group_id=QQ群 24 | 25 | // =======================================微信server酱通知设置区域=========================================== 26 | //此处填你申请的SCKEY. 27 | //(环境变量名 PUSH_KEY) 28 | let SCKEY = ''; 29 | 30 | // =======================================Bark App通知设置区域=========================================== 31 | //此处填你BarkAPP的信息(IP/设备码,例如:https://api.day.app/XXXXXXXX) 32 | let BARK_PUSH = ''; 33 | //BARK app推送铃声,铃声列表去APP查看复制填写 34 | let BARK_SOUND = ''; 35 | //BARK app推送消息的分组, 默认为"QingLong" 36 | let BARK_GROUP = 'QingLong'; 37 | 38 | // =======================================telegram机器人通知设置区域=========================================== 39 | //此处填你telegram bot 的Token,telegram机器人通知推送必填项.例如:1077xxx4424:AAFjv0FcqxxxxxxgEMGfi22B4yh15R5uw 40 | //(环境变量名 TG_BOT_TOKEN) 41 | let TG_BOT_TOKEN = ''; 42 | //此处填你接收通知消息的telegram用户的id,telegram机器人通知推送必填项.例如:129xxx206 43 | //(环境变量名 TG_USER_ID) 44 | let TG_USER_ID = ''; 45 | //tg推送HTTP代理设置(不懂可忽略,telegram机器人通知推送功能中非必填) 46 | let TG_PROXY_HOST = ''; //例如:127.0.0.1(环境变量名:TG_PROXY_HOST) 47 | let TG_PROXY_PORT = ''; //例如:1080(环境变量名:TG_PROXY_PORT) 48 | let TG_PROXY_AUTH = ''; //tg代理配置认证参数 49 | //Telegram api自建的反向代理地址(不懂可忽略,telegram机器人通知推送功能中非必填),默认tg官方api(环境变量名:TG_API_HOST) 50 | let TG_API_HOST = 'api.telegram.org'; 51 | // =======================================钉钉机器人通知设置区域=========================================== 52 | //此处填你钉钉 bot 的webhook,例如:5a544165465465645d0f31dca676e7bd07415asdasd 53 | //(环境变量名 DD_BOT_TOKEN) 54 | let DD_BOT_TOKEN = ''; 55 | //密钥,机器人安全设置页面,加签一栏下面显示的SEC开头的字符串 56 | let DD_BOT_SECRET = ''; 57 | 58 | // =======================================企业微信机器人通知设置区域=========================================== 59 | //此处填你企业微信机器人的 webhook(详见文档 https://work.weixin.qq.com/api/doc/90000/90136/91770),例如:693a91f6-7xxx-4bc4-97a0-0ec2sifa5aaa 60 | //(环境变量名 QYWX_KEY) 61 | let QYWX_KEY = ''; 62 | 63 | // =======================================企业微信应用消息通知设置区域=========================================== 64 | /* 65 | 此处填你企业微信应用消息的值(详见文档 https://work.weixin.qq.com/api/doc/90000/90135/90236) 66 | 环境变量名 QYWX_AM依次填入 corpid,corpsecret,touser(注:多个成员ID使用|隔开),agentid,消息类型(选填,不填默认文本消息类型) 67 | 注意用,号隔开(英文输入法的逗号),例如:wwcff56746d9adwers,B-791548lnzXBE6_BWfxdf3kSTMJr9vFEPKAbh6WERQ,mingcheng,1000001,2COXgjH2UIfERF2zxrtUOKgQ9XklUqMdGSWLBoW_lSDAdafat 68 | 可选推送消息类型(推荐使用图文消息(mpnews)): 69 | - 文本卡片消息: 0 (数字零) 70 | - 文本消息: 1 (数字一) 71 | - 图文消息(mpnews): 素材库图片id, 可查看此教程(http://note.youdao.com/s/HMiudGkb)或者(https://note.youdao.com/ynoteshare1/index.html?id=1a0c8aff284ad28cbd011b29b3ad0191&type=note) 72 | */ 73 | let QYWX_AM = ''; 74 | 75 | // =======================================iGot聚合推送通知设置区域=========================================== 76 | //此处填您iGot的信息(推送key,例如:https://push.hellyw.com/XXXXXXXX) 77 | let IGOT_PUSH_KEY = ''; 78 | 79 | // =======================================push+设置区域======================================= 80 | //官方文档:http://www.pushplus.plus/ 81 | //PUSH_PLUS_TOKEN:微信扫码登录后一对一推送或一对多推送下面的token(您的Token),不提供PUSH_PLUS_USER则默认为一对一推送 82 | //PUSH_PLUS_USER: 一对多推送的“群组编码”(一对多推送下面->您的群组(如无则新建)->群组编码,如果您是创建群组人。也需点击“查看二维码”扫描绑定,否则不能接受群组消息推送) 83 | let PUSH_PLUS_TOKEN = ''; 84 | let PUSH_PLUS_USER = ''; 85 | 86 | //==========================云端环境变量的判断与接收========================= 87 | if (process.env.GOBOT_URL) { 88 | GOBOT_URL = process.env.GOBOT_URL; 89 | } 90 | if (process.env.GOBOT_TOKEN) { 91 | GOBOT_TOKEN = process.env.GOBOT_TOKEN; 92 | } 93 | if (process.env.GOBOT_QQ) { 94 | GOBOT_QQ = process.env.GOBOT_QQ; 95 | } 96 | 97 | if (process.env.PUSH_KEY) { 98 | SCKEY = process.env.PUSH_KEY; 99 | } 100 | 101 | if (process.env.QQ_SKEY) { 102 | QQ_SKEY = process.env.QQ_SKEY; 103 | } 104 | 105 | if (process.env.QQ_MODE) { 106 | QQ_MODE = process.env.QQ_MODE; 107 | } 108 | 109 | if (process.env.BARK_PUSH) { 110 | if (process.env.BARK_PUSH.indexOf('https') > -1 || process.env.BARK_PUSH.indexOf('http') > -1) { 111 | //兼容BARK自建用户 112 | BARK_PUSH = process.env.BARK_PUSH; 113 | } else { 114 | BARK_PUSH = `https://api.day.app/${process.env.BARK_PUSH}`; 115 | } 116 | if (process.env.BARK_SOUND) { 117 | BARK_SOUND = process.env.BARK_SOUND; 118 | } 119 | if (process.env.BARK_GROUP) { 120 | BARK_GROUP = process.env.BARK_GROUP; 121 | } 122 | } else { 123 | if (BARK_PUSH && BARK_PUSH.indexOf('https') === -1 && BARK_PUSH.indexOf('http') === -1) { 124 | //兼容BARK本地用户只填写设备码的情况 125 | BARK_PUSH = `https://api.day.app/${BARK_PUSH}`; 126 | } 127 | } 128 | if (process.env.TG_BOT_TOKEN) { 129 | TG_BOT_TOKEN = process.env.TG_BOT_TOKEN; 130 | } 131 | if (process.env.TG_USER_ID) { 132 | TG_USER_ID = process.env.TG_USER_ID; 133 | } 134 | if (process.env.TG_PROXY_AUTH) TG_PROXY_AUTH = process.env.TG_PROXY_AUTH; 135 | if (process.env.TG_PROXY_HOST) TG_PROXY_HOST = process.env.TG_PROXY_HOST; 136 | if (process.env.TG_PROXY_PORT) TG_PROXY_PORT = process.env.TG_PROXY_PORT; 137 | if (process.env.TG_API_HOST) TG_API_HOST = process.env.TG_API_HOST; 138 | 139 | if (process.env.DD_BOT_TOKEN) { 140 | DD_BOT_TOKEN = process.env.DD_BOT_TOKEN; 141 | if (process.env.DD_BOT_SECRET) { 142 | DD_BOT_SECRET = process.env.DD_BOT_SECRET; 143 | } 144 | } 145 | 146 | if (process.env.QYWX_KEY) { 147 | QYWX_KEY = process.env.QYWX_KEY; 148 | } 149 | 150 | if (process.env.QYWX_AM) { 151 | QYWX_AM = process.env.QYWX_AM; 152 | } 153 | 154 | if (process.env.IGOT_PUSH_KEY) { 155 | IGOT_PUSH_KEY = process.env.IGOT_PUSH_KEY; 156 | } 157 | 158 | if (process.env.PUSH_PLUS_TOKEN) { 159 | PUSH_PLUS_TOKEN = process.env.PUSH_PLUS_TOKEN; 160 | } 161 | if (process.env.PUSH_PLUS_USER) { 162 | PUSH_PLUS_USER = process.env.PUSH_PLUS_USER; 163 | } 164 | //==========================云端环境变量的判断与接收========================= 165 | 166 | /** 167 | * sendNotify 推送通知功能 168 | * @param text 通知头 169 | * @param desp 通知体 170 | * @param params 某些推送通知方式点击弹窗可跳转, 例:{ url: 'https://abc.com' } 171 | * @param author 作者仓库等信息 例:`本通知 By:https://github.com/whyour/qinglong` 172 | * @returns {Promise} 173 | */ 174 | async function sendNotify(text, desp, params = {}, author = '\n\n本通知 By:https://github.com/whyour/qinglong') { 175 | try { 176 | const notifySkipList = process.env.NOTIFY_SKIP_LIST ? process.env.NOTIFY_SKIP_LIST.split('&') : []; 177 | const titleIndex = notifySkipList.findIndex((item) => item === text); 178 | 179 | if (titleIndex !== -1) { 180 | console.log(`${text} 在推送黑名单中,已跳过推送`); 181 | return; 182 | } 183 | 184 | const got = require('got'); 185 | 186 | const body = await got('http://localhost:5701/api/users').json(); 187 | const users = body.data; 188 | 189 | for (const user of users) { 190 | if (user.pt_pin && user.nickName && user.remark) { 191 | desp = desp.replace(new RegExp(`${user.pt_pin}|${user.nickName}`, 'gm'), user.remark); 192 | } 193 | } 194 | 195 | } catch (error) { 196 | console.error(error); 197 | } 198 | 199 | //提供6种通知 200 | desp += author; //增加作者信息,防止被贩卖等 201 | await Promise.all([ 202 | serverNotify(text, desp), //微信server酱 203 | pushPlusNotify(text, desp), //pushplus(推送加) 204 | ]); 205 | //由于上述两种微信通知需点击进去才能查看到详情,故text(标题内容)携带了账号序号以及昵称信息,方便不点击也可知道是哪个京东哪个活动 206 | text = text.match(/.*?(?=\s?-)/g) ? text.match(/.*?(?=\s?-)/g)[0] : text; 207 | await Promise.all([ 208 | BarkNotify(text, desp, params), //iOS Bark APP 209 | tgBotNotify(text, desp), //telegram 机器人 210 | ddBotNotify(text, desp), //钉钉机器人 211 | qywxBotNotify(text, desp), //企业微信机器人 212 | qywxamNotify(text, desp), //企业微信应用消息推送 213 | iGotNotify(text, desp, params), //iGot 214 | gobotNotify(text, desp), //go-cqhttp 215 | ]); 216 | } 217 | 218 | function gobotNotify(text, desp, time = 2100) { 219 | return new Promise((resolve) => { 220 | if (GOBOT_URL) { 221 | const options = { 222 | url: `${GOBOT_URL}?access_token=${GOBOT_TOKEN}&${GOBOT_QQ}`, 223 | body: `message=${text}\n${desp}`, 224 | headers: { 225 | 'Content-Type': 'application/x-www-form-urlencoded', 226 | }, 227 | timeout, 228 | }; 229 | setTimeout(() => { 230 | $.post(options, (err, resp, data) => { 231 | try { 232 | if (err) { 233 | console.log('发送go-cqhttp通知调用API失败!!\n'); 234 | console.log(err); 235 | } else { 236 | data = JSON.parse(data); 237 | if (data.retcode === 0) { 238 | console.log('go-cqhttp发送通知消息成功🎉\n'); 239 | } else if (data.retcode === 100) { 240 | console.log(`go-cqhttp发送通知消息异常: ${data.errmsg}\n`); 241 | } else { 242 | console.log(`go-cqhttp发送通知消息异常\n${JSON.stringify(data)}`); 243 | } 244 | } 245 | } catch (e) { 246 | $.logErr(e, resp); 247 | } finally { 248 | resolve(data); 249 | } 250 | }); 251 | }, time); 252 | } else { 253 | resolve(); 254 | } 255 | }); 256 | } 257 | 258 | function serverNotify(text, desp, time = 2100) { 259 | return new Promise((resolve) => { 260 | if (SCKEY) { 261 | //微信server酱推送通知一个\n不会换行,需要两个\n才能换行,故做此替换 262 | desp = desp.replace(/[\n\r]/g, '\n\n'); 263 | const options = { 264 | url: SCKEY.includes('SCT') ? `https://sctapi.ftqq.com/${SCKEY}.send` : `https://sc.ftqq.com/${SCKEY}.send`, 265 | body: `text=${text}&desp=${desp}`, 266 | headers: { 267 | 'Content-Type': 'application/x-www-form-urlencoded', 268 | }, 269 | timeout, 270 | }; 271 | setTimeout(() => { 272 | $.post(options, (err, resp, data) => { 273 | try { 274 | if (err) { 275 | console.log('发送通知调用API失败!!\n'); 276 | console.log(err); 277 | } else { 278 | data = JSON.parse(data); 279 | //server酱和Server酱·Turbo版的返回json格式不太一样 280 | if (data.errno === 0 || data.data.errno === 0) { 281 | console.log('server酱发送通知消息成功🎉\n'); 282 | } else if (data.errno === 1024) { 283 | // 一分钟内发送相同的内容会触发 284 | console.log(`server酱发送通知消息异常: ${data.errmsg}\n`); 285 | } else { 286 | console.log(`server酱发送通知消息异常\n${JSON.stringify(data)}`); 287 | } 288 | } 289 | } catch (e) { 290 | $.logErr(e, resp); 291 | } finally { 292 | resolve(data); 293 | } 294 | }); 295 | }, time); 296 | } else { 297 | resolve(); 298 | } 299 | }); 300 | } 301 | 302 | function CoolPush(text, desp) { 303 | return new Promise((resolve) => { 304 | if (QQ_SKEY) { 305 | let options = { 306 | url: `https://push.xuthus.cc/${QQ_MODE}/${QQ_SKEY}`, 307 | headers: { 308 | 'Content-Type': 'application/json', 309 | }, 310 | }; 311 | 312 | // 已知敏感词 313 | text = text.replace(/京豆/g, '豆豆'); 314 | desp = desp.replace(/京豆/g, ''); 315 | desp = desp.replace(/🐶/g, ''); 316 | desp = desp.replace(/红包/g, 'H包'); 317 | 318 | switch (QQ_MODE) { 319 | case 'email': 320 | options.json = { 321 | t: text, 322 | c: desp, 323 | }; 324 | break; 325 | default: 326 | options.body = `${text}\n\n${desp}`; 327 | } 328 | 329 | let pushMode = function (t) { 330 | switch (t) { 331 | case 'send': 332 | return '个人'; 333 | case 'group': 334 | return 'QQ群'; 335 | case 'wx': 336 | return '微信'; 337 | case 'ww': 338 | return '企业微信'; 339 | case 'email': 340 | return '邮件'; 341 | default: 342 | return '未知方式'; 343 | } 344 | }; 345 | 346 | $.post(options, (err, resp, data) => { 347 | try { 348 | if (err) { 349 | console.log(`发送${pushMode(QQ_MODE)}通知调用API失败!!\n`); 350 | console.log(err); 351 | } else { 352 | data = JSON.parse(data); 353 | if (data.code === 200) { 354 | console.log(`酷推发送${pushMode(QQ_MODE)}通知消息成功🎉\n`); 355 | } else if (data.code === 400) { 356 | console.log(`QQ酷推(Cool Push)发送${pushMode(QQ_MODE)}推送失败:${data.msg}\n`); 357 | } else if (data.code === 503) { 358 | console.log(`QQ酷推出错,${data.message}:${data.data}\n`); 359 | } else { 360 | console.log(`酷推推送异常: ${JSON.stringify(data)}`); 361 | } 362 | } 363 | } catch (e) { 364 | $.logErr(e, resp); 365 | } finally { 366 | resolve(data); 367 | } 368 | }); 369 | } else { 370 | resolve(); 371 | } 372 | }); 373 | } 374 | 375 | function BarkNotify(text, desp, params = {}) { 376 | return new Promise((resolve) => { 377 | if (BARK_PUSH) { 378 | const options = { 379 | url: `${BARK_PUSH}/${encodeURIComponent(text)}/${encodeURIComponent( 380 | desp 381 | )}?sound=${BARK_SOUND}&group=${BARK_GROUP}&${querystring.stringify(params)}`, 382 | headers: { 383 | 'Content-Type': 'application/x-www-form-urlencoded', 384 | }, 385 | timeout, 386 | }; 387 | $.get(options, (err, resp, data) => { 388 | try { 389 | if (err) { 390 | console.log('Bark APP发送通知调用API失败!!\n'); 391 | console.log(err); 392 | } else { 393 | data = JSON.parse(data); 394 | if (data.code === 200) { 395 | console.log('Bark APP发送通知消息成功🎉\n'); 396 | } else { 397 | console.log(`${data.message}\n`); 398 | } 399 | } 400 | } catch (e) { 401 | $.logErr(e, resp); 402 | } finally { 403 | resolve(); 404 | } 405 | }); 406 | } else { 407 | resolve(); 408 | } 409 | }); 410 | } 411 | 412 | function tgBotNotify(text, desp) { 413 | return new Promise((resolve) => { 414 | if (TG_BOT_TOKEN && TG_USER_ID) { 415 | const options = { 416 | url: `https://${TG_API_HOST}/bot${TG_BOT_TOKEN}/sendMessage`, 417 | body: `chat_id=${TG_USER_ID}&text=${text}\n\n${desp}&disable_web_page_preview=true`, 418 | headers: { 419 | 'Content-Type': 'application/x-www-form-urlencoded', 420 | }, 421 | timeout, 422 | }; 423 | if (TG_PROXY_HOST && TG_PROXY_PORT) { 424 | const tunnel = require('tunnel'); 425 | const agent = { 426 | https: tunnel.httpsOverHttp({ 427 | proxy: { 428 | host: TG_PROXY_HOST, 429 | port: TG_PROXY_PORT * 1, 430 | proxyAuth: TG_PROXY_AUTH, 431 | }, 432 | }), 433 | }; 434 | Object.assign(options, { agent }); 435 | } 436 | $.post(options, (err, resp, data) => { 437 | try { 438 | if (err) { 439 | console.log('telegram发送通知消息失败!!\n'); 440 | console.log(err); 441 | } else { 442 | data = JSON.parse(data); 443 | if (data.ok) { 444 | console.log('Telegram发送通知消息成功🎉。\n'); 445 | } else if (data.error_code === 400) { 446 | console.log('请主动给bot发送一条消息并检查接收用户ID是否正确。\n'); 447 | } else if (data.error_code === 401) { 448 | console.log('Telegram bot token 填写错误。\n'); 449 | } 450 | } 451 | } catch (e) { 452 | $.logErr(e, resp); 453 | } finally { 454 | resolve(data); 455 | } 456 | }); 457 | } else { 458 | resolve(); 459 | } 460 | }); 461 | } 462 | function ddBotNotify(text, desp) { 463 | return new Promise((resolve) => { 464 | const options = { 465 | url: `https://oapi.dingtalk.com/robot/send?access_token=${DD_BOT_TOKEN}`, 466 | json: { 467 | msgtype: 'text', 468 | text: { 469 | content: ` ${text}\n\n${desp}`, 470 | }, 471 | }, 472 | headers: { 473 | 'Content-Type': 'application/json', 474 | }, 475 | timeout, 476 | }; 477 | if (DD_BOT_TOKEN && DD_BOT_SECRET) { 478 | const crypto = require('crypto'); 479 | const dateNow = Date.now(); 480 | const hmac = crypto.createHmac('sha256', DD_BOT_SECRET); 481 | hmac.update(`${dateNow}\n${DD_BOT_SECRET}`); 482 | const result = encodeURIComponent(hmac.digest('base64')); 483 | options.url = `${options.url}×tamp=${dateNow}&sign=${result}`; 484 | $.post(options, (err, resp, data) => { 485 | try { 486 | if (err) { 487 | console.log('钉钉发送通知消息失败!!\n'); 488 | console.log(err); 489 | } else { 490 | data = JSON.parse(data); 491 | if (data.errcode === 0) { 492 | console.log('钉钉发送通知消息成功🎉。\n'); 493 | } else { 494 | console.log(`${data.errmsg}\n`); 495 | } 496 | } 497 | } catch (e) { 498 | $.logErr(e, resp); 499 | } finally { 500 | resolve(data); 501 | } 502 | }); 503 | } else if (DD_BOT_TOKEN) { 504 | $.post(options, (err, resp, data) => { 505 | try { 506 | if (err) { 507 | console.log('钉钉发送通知消息失败!!\n'); 508 | console.log(err); 509 | } else { 510 | data = JSON.parse(data); 511 | if (data.errcode === 0) { 512 | console.log('钉钉发送通知消息完成。\n'); 513 | } else { 514 | console.log(`${data.errmsg}\n`); 515 | } 516 | } 517 | } catch (e) { 518 | $.logErr(e, resp); 519 | } finally { 520 | resolve(data); 521 | } 522 | }); 523 | } else { 524 | resolve(); 525 | } 526 | }); 527 | } 528 | 529 | function qywxBotNotify(text, desp) { 530 | return new Promise((resolve) => { 531 | const options = { 532 | url: `https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=${QYWX_KEY}`, 533 | json: { 534 | msgtype: 'text', 535 | text: { 536 | content: ` ${text}\n\n${desp}`, 537 | }, 538 | }, 539 | headers: { 540 | 'Content-Type': 'application/json', 541 | }, 542 | timeout, 543 | }; 544 | if (QYWX_KEY) { 545 | $.post(options, (err, resp, data) => { 546 | try { 547 | if (err) { 548 | console.log('企业微信发送通知消息失败!!\n'); 549 | console.log(err); 550 | } else { 551 | data = JSON.parse(data); 552 | if (data.errcode === 0) { 553 | console.log('企业微信发送通知消息成功🎉。\n'); 554 | } else { 555 | console.log(`${data.errmsg}\n`); 556 | } 557 | } 558 | } catch (e) { 559 | $.logErr(e, resp); 560 | } finally { 561 | resolve(data); 562 | } 563 | }); 564 | } else { 565 | resolve(); 566 | } 567 | }); 568 | } 569 | 570 | function ChangeUserId(desp) { 571 | const QYWX_AM_AY = QYWX_AM.split(','); 572 | if (QYWX_AM_AY[2]) { 573 | const userIdTmp = QYWX_AM_AY[2].split('|'); 574 | let userId = ''; 575 | for (let i = 0; i < userIdTmp.length; i++) { 576 | const count = '账号' + (i + 1); 577 | const count2 = '签到号 ' + (i + 1); 578 | if (desp.match(count2)) { 579 | userId = userIdTmp[i]; 580 | } 581 | } 582 | if (!userId) userId = QYWX_AM_AY[2]; 583 | return userId; 584 | } else { 585 | return '@all'; 586 | } 587 | } 588 | 589 | function qywxamNotify(text, desp) { 590 | return new Promise((resolve) => { 591 | if (QYWX_AM) { 592 | const QYWX_AM_AY = QYWX_AM.split(','); 593 | const options_accesstoken = { 594 | url: `https://qyapi.weixin.qq.com/cgi-bin/gettoken`, 595 | json: { 596 | corpid: `${QYWX_AM_AY[0]}`, 597 | corpsecret: `${QYWX_AM_AY[1]}`, 598 | }, 599 | headers: { 600 | 'Content-Type': 'application/json', 601 | }, 602 | timeout, 603 | }; 604 | $.post(options_accesstoken, (err, resp, data) => { 605 | html = desp.replace(/\n/g, '
'); 606 | var json = JSON.parse(data); 607 | accesstoken = json.access_token; 608 | let options; 609 | 610 | switch (QYWX_AM_AY[4]) { 611 | case '0': 612 | options = { 613 | msgtype: 'textcard', 614 | textcard: { 615 | title: `${text}`, 616 | description: `${desp}`, 617 | url: 'https://github.com/whyour/qinglong', 618 | btntxt: '更多', 619 | }, 620 | }; 621 | break; 622 | 623 | case '1': 624 | options = { 625 | msgtype: 'text', 626 | text: { 627 | content: `${text}\n\n${desp}`, 628 | }, 629 | }; 630 | break; 631 | 632 | default: 633 | options = { 634 | msgtype: 'mpnews', 635 | mpnews: { 636 | articles: [ 637 | { 638 | title: `${text}`, 639 | thumb_media_id: `${QYWX_AM_AY[4]}`, 640 | author: `智能助手`, 641 | content_source_url: ``, 642 | content: `${html}`, 643 | digest: `${desp}`, 644 | }, 645 | ], 646 | }, 647 | }; 648 | } 649 | if (!QYWX_AM_AY[4]) { 650 | //如不提供第四个参数,则默认进行文本消息类型推送 651 | options = { 652 | msgtype: 'text', 653 | text: { 654 | content: `${text}\n\n${desp}`, 655 | }, 656 | }; 657 | } 658 | options = { 659 | url: `https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token=${accesstoken}`, 660 | json: { 661 | touser: `${ChangeUserId(desp)}`, 662 | agentid: `${QYWX_AM_AY[3]}`, 663 | safe: '0', 664 | ...options, 665 | }, 666 | headers: { 667 | 'Content-Type': 'application/json', 668 | }, 669 | }; 670 | 671 | $.post(options, (err, resp, data) => { 672 | try { 673 | if (err) { 674 | console.log('成员ID:' + ChangeUserId(desp) + '企业微信应用消息发送通知消息失败!!\n'); 675 | console.log(err); 676 | } else { 677 | data = JSON.parse(data); 678 | if (data.errcode === 0) { 679 | console.log('成员ID:' + ChangeUserId(desp) + '企业微信应用消息发送通知消息成功🎉。\n'); 680 | } else { 681 | console.log(`${data.errmsg}\n`); 682 | } 683 | } 684 | } catch (e) { 685 | $.logErr(e, resp); 686 | } finally { 687 | resolve(data); 688 | } 689 | }); 690 | }); 691 | } else { 692 | resolve(); 693 | } 694 | }); 695 | } 696 | 697 | function iGotNotify(text, desp, params = {}) { 698 | return new Promise((resolve) => { 699 | if (IGOT_PUSH_KEY) { 700 | // 校验传入的IGOT_PUSH_KEY是否有效 701 | const IGOT_PUSH_KEY_REGX = new RegExp('^[a-zA-Z0-9]{24}$'); 702 | if (!IGOT_PUSH_KEY_REGX.test(IGOT_PUSH_KEY)) { 703 | console.log('您所提供的IGOT_PUSH_KEY无效\n'); 704 | resolve(); 705 | return; 706 | } 707 | const options = { 708 | url: `https://push.hellyw.com/${IGOT_PUSH_KEY.toLowerCase()}`, 709 | body: `title=${text}&content=${desp}&${querystring.stringify(params)}`, 710 | headers: { 711 | 'Content-Type': 'application/x-www-form-urlencoded', 712 | }, 713 | timeout, 714 | }; 715 | $.post(options, (err, resp, data) => { 716 | try { 717 | if (err) { 718 | console.log('发送通知调用API失败!!\n'); 719 | console.log(err); 720 | } else { 721 | if (typeof data === 'string') data = JSON.parse(data); 722 | if (data.ret === 0) { 723 | console.log('iGot发送通知消息成功🎉\n'); 724 | } else { 725 | console.log(`iGot发送通知消息失败:${data.errMsg}\n`); 726 | } 727 | } 728 | } catch (e) { 729 | $.logErr(e, resp); 730 | } finally { 731 | resolve(data); 732 | } 733 | }); 734 | } else { 735 | resolve(); 736 | } 737 | }); 738 | } 739 | 740 | function pushPlusNotify(text, desp) { 741 | return new Promise((resolve) => { 742 | if (PUSH_PLUS_TOKEN) { 743 | desp = desp.replace(/[\n\r]/g, '
'); // 默认为html, 不支持plaintext 744 | const body = { 745 | token: `${PUSH_PLUS_TOKEN}`, 746 | title: `${text}`, 747 | content: `${desp}`, 748 | topic: `${PUSH_PLUS_USER}`, 749 | }; 750 | const options = { 751 | url: `https://www.pushplus.plus/send`, 752 | body: JSON.stringify(body), 753 | headers: { 754 | 'Content-Type': ' application/json', 755 | }, 756 | timeout, 757 | }; 758 | $.post(options, (err, resp, data) => { 759 | try { 760 | if (err) { 761 | console.log(`push+发送${PUSH_PLUS_USER ? '一对多' : '一对一'}通知消息失败!!\n`); 762 | console.log(err); 763 | } else { 764 | data = JSON.parse(data); 765 | if (data.code === 200) { 766 | console.log(`push+发送${PUSH_PLUS_USER ? '一对多' : '一对一'}通知消息完成。\n`); 767 | } else { 768 | console.log(`push+发送${PUSH_PLUS_USER ? '一对多' : '一对一'}通知消息失败:${data.msg}\n`); 769 | } 770 | } 771 | } catch (e) { 772 | $.logErr(e, resp); 773 | } finally { 774 | resolve(data); 775 | } 776 | }); 777 | } else { 778 | resolve(); 779 | } 780 | }); 781 | } 782 | 783 | module.exports = { 784 | sendNotify, 785 | BARK_PUSH, 786 | }; 787 | 788 | // prettier-ignore 789 | function Env(t, s) { return new (class { constructor(t, s) { (this.name = t), (this.data = null), (this.dataFile = 'box.dat'), (this.logs = []), (this.logSeparator = '\n'), (this.startTime = new Date().getTime()), Object.assign(this, s), this.log('', `\ud83d\udd14${this.name}, \u5f00\u59cb!`); } isNode() { return 'undefined' != typeof module && !!module.exports; } isQuanX() { return 'undefined' != typeof $task; } isSurge() { return 'undefined' != typeof $httpClient && 'undefined' == typeof $loon; } isLoon() { return 'undefined' != typeof $loon; } getScript(t) { return new Promise((s) => { $.get({ url: t }, (t, e, i) => s(i)); }); } runScript(t, s) { return new Promise((e) => { let i = this.getdata('@chavy_boxjs_userCfgs.httpapi'); i = i ? i.replace(/\n/g, '').trim() : i; let o = this.getdata('@chavy_boxjs_userCfgs.httpapi_timeout'); (o = o ? 1 * o : 20), (o = s && s.timeout ? s.timeout : o); const [h, a] = i.split('@'), r = { url: `http://${a}/v1/scripting/evaluate`, body: { script_text: t, mock_type: 'cron', timeout: o }, headers: { 'X-Key': h, Accept: '*/*' }, }; $.post(r, (t, s, i) => e(i)); }).catch((t) => this.logErr(t)); } loaddata() { if (!this.isNode()) return {}; { (this.fs = this.fs ? this.fs : require('fs')), (this.path = this.path ? this.path : require('path')); const t = this.path.resolve(this.dataFile), s = this.path.resolve(process.cwd(), this.dataFile), e = this.fs.existsSync(t), i = !e && this.fs.existsSync(s); if (!e && !i) return {}; { const i = e ? t : s; try { return JSON.parse(this.fs.readFileSync(i)); } catch (t) { return {}; } } } } writedata() { if (this.isNode()) { (this.fs = this.fs ? this.fs : require('fs')), (this.path = this.path ? this.path : require('path')); const t = this.path.resolve(this.dataFile), s = this.path.resolve(process.cwd(), this.dataFile), e = this.fs.existsSync(t), i = !e && this.fs.existsSync(s), o = JSON.stringify(this.data); e ? this.fs.writeFileSync(t, o) : i ? this.fs.writeFileSync(s, o) : this.fs.writeFileSync(t, o); } } lodash_get(t, s, e) { const i = s.replace(/\[(\d+)\]/g, '.$1').split('.'); let o = t; for (const t of i) if (((o = Object(o)[t]), void 0 === o)) return e; return o; } lodash_set(t, s, e) { return Object(t) !== t ? t : (Array.isArray(s) || (s = s.toString().match(/[^.[\]]+/g) || []), (s .slice(0, -1) .reduce( (t, e, i) => (Object(t[e]) === t[e] ? t[e] : (t[e] = Math.abs(s[i + 1]) >> 0 == +s[i + 1] ? [] : {})), t )[s[s.length - 1]] = e), t); } getdata(t) { let s = this.getval(t); if (/^@/.test(t)) { const [, e, i] = /^@(.*?)\.(.*?)$/.exec(t), o = e ? this.getval(e) : ''; if (o) try { const t = JSON.parse(o); s = t ? this.lodash_get(t, i, '') : s; } catch (t) { s = ''; } } return s; } setdata(t, s) { let e = !1; if (/^@/.test(s)) { const [, i, o] = /^@(.*?)\.(.*?)$/.exec(s), h = this.getval(i), a = i ? ('null' === h ? null : h || '{}') : '{}'; try { const s = JSON.parse(a); this.lodash_set(s, o, t), (e = this.setval(JSON.stringify(s), i)); } catch (s) { const h = {}; this.lodash_set(h, o, t), (e = this.setval(JSON.stringify(h), i)); } } else e = $.setval(t, s); return e; } getval(t) { return this.isSurge() || this.isLoon() ? $persistentStore.read(t) : this.isQuanX() ? $prefs.valueForKey(t) : this.isNode() ? ((this.data = this.loaddata()), this.data[t]) : (this.data && this.data[t]) || null; } setval(t, s) { return this.isSurge() || this.isLoon() ? $persistentStore.write(t, s) : this.isQuanX() ? $prefs.setValueForKey(t, s) : this.isNode() ? ((this.data = this.loaddata()), (this.data[s] = t), this.writedata(), !0) : (this.data && this.data[s]) || null; } initGotEnv(t) { (this.got = this.got ? this.got : require('got')), (this.cktough = this.cktough ? this.cktough : require('tough-cookie')), (this.ckjar = this.ckjar ? this.ckjar : new this.cktough.CookieJar()), t && ((t.headers = t.headers ? t.headers : {}), void 0 === t.headers.Cookie && void 0 === t.cookieJar && (t.cookieJar = this.ckjar)); } get(t, s = () => {}) { t.headers && (delete t.headers['Content-Type'], delete t.headers['Content-Length']), this.isSurge() || this.isLoon() ? $httpClient.get(t, (t, e, i) => { !t && e && ((e.body = i), (e.statusCode = e.status)), s(t, e, i); }) : this.isQuanX() ? $task.fetch(t).then( (t) => { const { statusCode: e, statusCode: i, headers: o, body: h } = t; s(null, { status: e, statusCode: i, headers: o, body: h }, h); }, (t) => s(t) ) : this.isNode() && (this.initGotEnv(t), this.got(t) .on('redirect', (t, s) => { try { const e = t.headers['set-cookie'].map(this.cktough.Cookie.parse).toString(); this.ckjar.setCookieSync(e, null), (s.cookieJar = this.ckjar); } catch (t) { this.logErr(t); } }) .then( (t) => { const { statusCode: e, statusCode: i, headers: o, body: h } = t; s(null, { status: e, statusCode: i, headers: o, body: h }, h); }, (t) => s(t) )); } post(t, s = () => {}) { if ( (t.body && t.headers && !t.headers['Content-Type'] && (t.headers['Content-Type'] = 'application/x-www-form-urlencoded'), delete t.headers['Content-Length'], this.isSurge() || this.isLoon()) ) $httpClient.post(t, (t, e, i) => { !t && e && ((e.body = i), (e.statusCode = e.status)), s(t, e, i); }); else if (this.isQuanX()) (t.method = 'POST'), $task.fetch(t).then( (t) => { const { statusCode: e, statusCode: i, headers: o, body: h } = t; s(null, { status: e, statusCode: i, headers: o, body: h }, h); }, (t) => s(t) ); else if (this.isNode()) { this.initGotEnv(t); const { url: e, ...i } = t; this.got.post(e, i).then( (t) => { const { statusCode: e, statusCode: i, headers: o, body: h } = t; s(null, { status: e, statusCode: i, headers: o, body: h }, h); }, (t) => s(t) ); } } time(t) { let s = { 'M+': new Date().getMonth() + 1, 'd+': new Date().getDate(), 'H+': new Date().getHours(), 'm+': new Date().getMinutes(), 's+': new Date().getSeconds(), 'q+': Math.floor((new Date().getMonth() + 3) / 3), S: new Date().getMilliseconds(), }; /(y+)/.test(t) && (t = t.replace(RegExp.$1, (new Date().getFullYear() + '').substr(4 - RegExp.$1.length))); for (let e in s) new RegExp('(' + e + ')').test(t) && (t = t.replace(RegExp.$1, 1 == RegExp.$1.length ? s[e] : ('00' + s[e]).substr(('' + s[e]).length))); return t; } msg(s = t, e = '', i = '', o) { const h = (t) => !t || (!this.isLoon() && this.isSurge()) ? t : 'string' == typeof t ? this.isLoon() ? t : this.isQuanX() ? { 'open-url': t } : void 0 : 'object' == typeof t && (t['open-url'] || t['media-url']) ? this.isLoon() ? t['open-url'] : this.isQuanX() ? t : void 0 : void 0; $.isMute || (this.isSurge() || this.isLoon() ? $notification.post(s, e, i, h(o)) : this.isQuanX() && $notify(s, e, i, h(o))), this.logs.push('', '==============\ud83d\udce3\u7cfb\u7edf\u901a\u77e5\ud83d\udce3=============='), this.logs.push(s), e && this.logs.push(e), i && this.logs.push(i); } log(...t) { t.length > 0 ? (this.logs = [...this.logs, ...t]) : console.log(this.logs.join(this.logSeparator)); } logErr(t, s) { const e = !this.isSurge() && !this.isQuanX() && !this.isLoon(); e ? $.log('', `\u2757\ufe0f${this.name}, \u9519\u8bef!`, t.stack) : $.log('', `\u2757\ufe0f${this.name}, \u9519\u8bef!`, t); } wait(t) { return new Promise((s) => setTimeout(s, t)); } done(t = {}) { const s = new Date().getTime(), e = (s - this.startTime) / 1e3; this.log('', `\ud83d\udd14${this.name}, \u7ed3\u675f! \ud83d\udd5b ${e} \u79d2`), this.log(), (this.isSurge() || this.isQuanX() || this.isLoon()) && $done(t); } })(t, s); } 790 | -------------------------------------------------------------------------------- /backend/static/assets/index.fa13dcb4.css: -------------------------------------------------------------------------------- 1 | @charset "UTF-8";.el-button{--el-button-font-weight:var(--el-font-weight-primary);--el-button-border-color:var(--el-border-color-base);--el-button-background-color:var(--el-color-white);--el-button-font-color:var(--el-text-color-regular);--el-button-disabled-font-color:var(--el-text-color-placeholder);--el-button-disabled-background-color:var(--el-color-white);--el-button-disabled-border-color:var(--el-border-color-light);--el-button-divide-border-color:rgba(255, 255, 255, .5) }.el-button{display:inline-block;line-height:1;min-height:40px;white-space:nowrap;cursor:pointer;background:var(--el-button-background-color,var(--el-color-white));border:var(--el-border-base);border-color:var(--el-button-border-color,var(--el-border-color-base));color:var(--el-button-font-color,var(--el-text-color-regular));-webkit-appearance:none;text-align:center;-webkit-box-sizing:border-box;box-sizing:border-box;outline:0;margin:0;-webkit-transition:.1s;transition:.1s;font-weight:var(--el-button-font-weight);-moz-user-select:none;-webkit-user-select:none;-ms-user-select:none;padding:12px 20px;font-size:var(--el-font-size-base,14px);border-radius:var(--el-border-radius-base)}.el-button+.el-button{margin-left:10px}.el-button.is-round{padding:12px 20px}.el-button:focus,.el-button:hover{color:var(--el-color-primary);border-color:var(--el-color-primary-light-7);background-color:var(--el-color-primary-light-9);outline:0}.el-button:active{color:#3a8ee6;border-color:#3a8ee6;outline:0}.el-button::-moz-focus-inner{border:0}.el-button [class*=el-icon-]+span{margin-left:5px}.el-button.is-plain:focus,.el-button.is-plain:hover{background:var(--el-color-white);border-color:var(--el-color-primary);color:var(--el-color-primary)}.el-button.is-plain:active{background:var(--el-color-white);border-color:#3a8ee6;color:#3a8ee6;outline:0}.el-button.is-active{color:#3a8ee6;border-color:#3a8ee6}.el-button.is-disabled,.el-button.is-disabled:focus,.el-button.is-disabled:hover{color:var(--el-button-disabled-font-color);cursor:not-allowed;background-image:none;background-color:var(--el-button-disabled-background-color);border-color:var(--el-button-disabled-border-color)}.el-button.is-disabled.el-button--text{background-color:transparent}.el-button.is-disabled.is-plain,.el-button.is-disabled.is-plain:focus,.el-button.is-disabled.is-plain:hover{background-color:var(--el-color-white);border-color:var(--el-button-disabled-border-color);color:var(--el-button-disabled-font-color)}.el-button.is-loading{position:relative;pointer-events:none}.el-button.is-loading:before{pointer-events:none;content:"";position:absolute;left:-1px;top:-1px;right:-1px;bottom:-1px;border-radius:inherit;background-color:#ffffff59}.el-button.is-round{border-radius:var(--el-border-radius-round);padding:12px 23px}.el-button.is-circle{border-radius:50%;padding:12px}.el-button--primary{--el-button-font-color:#ffffff;--el-button-background-color:#409eff;--el-button-border-color:#409eff;--el-button-hover-color:#66b1ff;--el-button-active-font-color:#e6e6e6;--el-button-active-background-color:#0d84ff;--el-button-active-border-color:#0d84ff }.el-button--primary:focus,.el-button--primary:hover{background:var(--el-button-hover-color);border-color:var(--el-button-hover-color);color:var(--el-button-font-color)}.el-button--primary:active{background:var(--el-button-active-background-color);border-color:var(--el-button-active-border-color);color:var(--el-button-active-font-color);outline:0}.el-button--primary.is-active{background:var(--el-button-active-background-color);border-color:var(--el-button-active-border-color);color:var(--el-button-active-font-color)}.el-button--primary.is-disabled,.el-button--primary.is-disabled:active,.el-button--primary.is-disabled:focus,.el-button--primary.is-disabled:hover{color:#fff;background-color:#a0cfff;border-color:#a0cfff}.el-button--primary.is-plain{color:var(--el-button-background-color);background-color:#ecf5ff;border-color:#b3d8ff}.el-button--primary.is-plain:focus,.el-button--primary.is-plain:hover{background:var(--el-button-background-color);border-color:var(--el-button-background-color);color:var(--el-color-white)}.el-button--primary.is-plain:active{background:var(--el-button-active-background-color);border-color:var(--el-button-active-border-color);color:var(--el-color-white);outline:0}.el-button--primary.is-plain.is-disabled,.el-button--primary.is-plain.is-disabled:active,.el-button--primary.is-plain.is-disabled:focus,.el-button--primary.is-plain.is-disabled:hover{color:#8cc5ff;background-color:#ecf5ff;border-color:#d9ecff}.el-button--success{--el-button-font-color:#ffffff;--el-button-background-color:#67c23a;--el-button-border-color:#67c23a;--el-button-hover-color:#85ce61;--el-button-active-font-color:#e6e6e6;--el-button-active-background-color:#529b2e;--el-button-active-border-color:#529b2e }.el-button--success:focus,.el-button--success:hover{background:var(--el-button-hover-color);border-color:var(--el-button-hover-color);color:var(--el-button-font-color)}.el-button--success:active{background:var(--el-button-active-background-color);border-color:var(--el-button-active-border-color);color:var(--el-button-active-font-color);outline:0}.el-button--success.is-active{background:var(--el-button-active-background-color);border-color:var(--el-button-active-border-color);color:var(--el-button-active-font-color)}.el-button--success.is-disabled,.el-button--success.is-disabled:active,.el-button--success.is-disabled:focus,.el-button--success.is-disabled:hover{color:#fff;background-color:#b3e19d;border-color:#b3e19d}.el-button--success.is-plain{color:var(--el-button-background-color);background-color:#f0f9eb;border-color:#c2e7b0}.el-button--success.is-plain:focus,.el-button--success.is-plain:hover{background:var(--el-button-background-color);border-color:var(--el-button-background-color);color:var(--el-color-white)}.el-button--success.is-plain:active{background:var(--el-button-active-background-color);border-color:var(--el-button-active-border-color);color:var(--el-color-white);outline:0}.el-button--success.is-plain.is-disabled,.el-button--success.is-plain.is-disabled:active,.el-button--success.is-plain.is-disabled:focus,.el-button--success.is-plain.is-disabled:hover{color:#a4da89;background-color:#f0f9eb;border-color:#e1f3d8}.el-button--warning{--el-button-font-color:#ffffff;--el-button-background-color:#e6a23c;--el-button-border-color:#e6a23c;--el-button-hover-color:#ebb563;--el-button-active-font-color:#e6e6e6;--el-button-active-background-color:#d48a1b;--el-button-active-border-color:#d48a1b }.el-button--warning:focus,.el-button--warning:hover{background:var(--el-button-hover-color);border-color:var(--el-button-hover-color);color:var(--el-button-font-color)}.el-button--warning:active{background:var(--el-button-active-background-color);border-color:var(--el-button-active-border-color);color:var(--el-button-active-font-color);outline:0}.el-button--warning.is-active{background:var(--el-button-active-background-color);border-color:var(--el-button-active-border-color);color:var(--el-button-active-font-color)}.el-button--warning.is-disabled,.el-button--warning.is-disabled:active,.el-button--warning.is-disabled:focus,.el-button--warning.is-disabled:hover{color:#fff;background-color:#f3d19e;border-color:#f3d19e}.el-button--warning.is-plain{color:var(--el-button-background-color);background-color:#fdf6ec;border-color:#f5dab1}.el-button--warning.is-plain:focus,.el-button--warning.is-plain:hover{background:var(--el-button-background-color);border-color:var(--el-button-background-color);color:var(--el-color-white)}.el-button--warning.is-plain:active{background:var(--el-button-active-background-color);border-color:var(--el-button-active-border-color);color:var(--el-color-white);outline:0}.el-button--warning.is-plain.is-disabled,.el-button--warning.is-plain.is-disabled:active,.el-button--warning.is-plain.is-disabled:focus,.el-button--warning.is-plain.is-disabled:hover{color:#f0c78a;background-color:#fdf6ec;border-color:#faecd8}.el-button--danger{--el-button-font-color:#ffffff;--el-button-background-color:#f56c6c;--el-button-border-color:#f56c6c;--el-button-hover-color:#f78989;--el-button-active-font-color:#e6e6e6;--el-button-active-background-color:#f23c3c;--el-button-active-border-color:#f23c3c }.el-button--danger:focus,.el-button--danger:hover{background:var(--el-button-hover-color);border-color:var(--el-button-hover-color);color:var(--el-button-font-color)}.el-button--danger:active{background:var(--el-button-active-background-color);border-color:var(--el-button-active-border-color);color:var(--el-button-active-font-color);outline:0}.el-button--danger.is-active{background:var(--el-button-active-background-color);border-color:var(--el-button-active-border-color);color:var(--el-button-active-font-color)}.el-button--danger.is-disabled,.el-button--danger.is-disabled:active,.el-button--danger.is-disabled:focus,.el-button--danger.is-disabled:hover{color:#fff;background-color:#fab6b6;border-color:#fab6b6}.el-button--danger.is-plain{color:var(--el-button-background-color);background-color:#fef0f0;border-color:#fbc4c4}.el-button--danger.is-plain:focus,.el-button--danger.is-plain:hover{background:var(--el-button-background-color);border-color:var(--el-button-background-color);color:var(--el-color-white)}.el-button--danger.is-plain:active{background:var(--el-button-active-background-color);border-color:var(--el-button-active-border-color);color:var(--el-color-white);outline:0}.el-button--danger.is-plain.is-disabled,.el-button--danger.is-plain.is-disabled:active,.el-button--danger.is-plain.is-disabled:focus,.el-button--danger.is-plain.is-disabled:hover{color:#f9a7a7;background-color:#fef0f0;border-color:#fde2e2}.el-button--info{--el-button-font-color:#ffffff;--el-button-background-color:#909399;--el-button-border-color:#909399;--el-button-hover-color:#a6a9ad;--el-button-active-font-color:#e6e6e6;--el-button-active-background-color:#767980;--el-button-active-border-color:#767980 }.el-button--info:focus,.el-button--info:hover{background:var(--el-button-hover-color);border-color:var(--el-button-hover-color);color:var(--el-button-font-color)}.el-button--info:active{background:var(--el-button-active-background-color);border-color:var(--el-button-active-border-color);color:var(--el-button-active-font-color);outline:0}.el-button--info.is-active{background:var(--el-button-active-background-color);border-color:var(--el-button-active-border-color);color:var(--el-button-active-font-color)}.el-button--info.is-disabled,.el-button--info.is-disabled:active,.el-button--info.is-disabled:focus,.el-button--info.is-disabled:hover{color:#fff;background-color:#c8c9cc;border-color:#c8c9cc}.el-button--info.is-plain{color:var(--el-button-background-color);background-color:#f4f4f5;border-color:#d3d4d6}.el-button--info.is-plain:focus,.el-button--info.is-plain:hover{background:var(--el-button-background-color);border-color:var(--el-button-background-color);color:var(--el-color-white)}.el-button--info.is-plain:active{background:var(--el-button-active-background-color);border-color:var(--el-button-active-border-color);color:var(--el-color-white);outline:0}.el-button--info.is-plain.is-disabled,.el-button--info.is-plain.is-disabled:active,.el-button--info.is-plain.is-disabled:focus,.el-button--info.is-plain.is-disabled:hover{color:#bcbec2;background-color:#f4f4f5;border-color:#e9e9eb}.el-button--medium{min-height:36px;padding:10px 20px;font-size:var(--el-font-size-base,14px);border-radius:var(--el-border-radius-base)}.el-button--medium.is-round{padding:10px 20px}.el-button--medium.is-circle{padding:10px}.el-button--small{min-height:32px;padding:9px 15px;font-size:12px;border-radius:calc(var(--el-border-radius-base) - 1px)}.el-button--small.is-round{padding:9px 15px}.el-button--small.is-circle{padding:9px}.el-button--mini{min-height:28px;padding:7px 15px;font-size:12px;border-radius:calc(var(--el-border-radius-base) - 1px)}.el-button--mini.is-round{padding:7px 15px}.el-button--mini.is-circle{padding:7px}.el-button--text{border-color:transparent;color:var(--el-color-primary);background:0 0;padding-left:0;padding-right:0}.el-button--text:focus,.el-button--text:hover{color:var(--el-color-primary-light-2);border-color:transparent;background-color:transparent}.el-button--text:active{color:#3a8ee6;border-color:transparent;background-color:transparent}.el-button--text.is-disabled,.el-button--text.is-disabled:focus,.el-button--text.is-disabled:hover{border-color:transparent}.el-textarea{--el-input-font-color:var(--el-text-color-regular);--el-input-border:var(--el-border-base);--el-input-border-color:var(--el-border-color-base);--el-input-border-radius:var(--el-border-radius-base);--el-input-background-color:var(--el-color-white);--el-input-icon-color:var(--el-text-color-placeholder);--el-input-placeholder-color:var(--el-text-color-placeholder);--el-input-hover-border:var(--el-border-color-hover);--el-input-clear-hover-color:var(--el-text-color-secondary);--el-input-focus-border:var(--el-color-primary) }.el-textarea{position:relative;display:inline-block;width:100%;vertical-align:bottom;font-size:var(--el-font-size-base)}.el-textarea__inner{display:block;resize:vertical;padding:5px 15px;line-height:1.5;-webkit-box-sizing:border-box;box-sizing:border-box;width:100%;font-size:inherit;color:var(--el-input-font-color,var(--el-text-color-regular));background-color:var(--el-input-background-color,var(--el-color-white));background-image:none;border:var(--el-input-border,var(--el-border-base));border-radius:var(--el-input-border-radius,var(--el-border-radius-base));-webkit-transition:var(--el-transition-border);transition:var(--el-transition-border)}.el-textarea__inner::-webkit-input-placeholder{color:var(--el-input-placeholder-color,var(--el-text-color-placeholder))}.el-textarea__inner::-moz-placeholder{color:var(--el-input-placeholder-color,var(--el-text-color-placeholder))}.el-textarea__inner:-ms-input-placeholder{color:var(--el-input-placeholder-color,var(--el-text-color-placeholder))}.el-textarea__inner::-ms-input-placeholder{color:var(--el-input-placeholder-color,var(--el-text-color-placeholder))}.el-textarea__inner::placeholder{color:var(--el-input-placeholder-color,var(--el-text-color-placeholder))}.el-textarea__inner:hover{border-color:var(--el-input-hover-border,)}.el-textarea__inner:focus{outline:0;border-color:var(--el-input-focus-border,)}.el-textarea .el-input__count{color:var(--el-color-info);background:var(--el-color-white);position:absolute;font-size:12px;line-height:14px;bottom:5px;right:10px}.el-textarea.is-disabled .el-textarea__inner{background-color:var(--el-disabled-fill-base);border-color:var(--el-disabled-border-base);color:var(--el-disabled-color-base);cursor:not-allowed}.el-textarea.is-disabled .el-textarea__inner::-webkit-input-placeholder{color:var(--el-text-color-placeholder)}.el-textarea.is-disabled .el-textarea__inner::-moz-placeholder{color:var(--el-text-color-placeholder)}.el-textarea.is-disabled .el-textarea__inner:-ms-input-placeholder{color:var(--el-text-color-placeholder)}.el-textarea.is-disabled .el-textarea__inner::-ms-input-placeholder{color:var(--el-text-color-placeholder)}.el-textarea.is-disabled .el-textarea__inner::placeholder{color:var(--el-text-color-placeholder)}.el-textarea.is-exceed .el-textarea__inner{border-color:var(--el-color-danger)}.el-textarea.is-exceed .el-input__count{color:var(--el-color-danger)}.el-input{--el-input-font-color:var(--el-text-color-regular);--el-input-border:var(--el-border-base);--el-input-border-color:var(--el-border-color-base);--el-input-border-radius:var(--el-border-radius-base);--el-input-background-color:var(--el-color-white);--el-input-icon-color:var(--el-text-color-placeholder);--el-input-placeholder-color:var(--el-text-color-placeholder);--el-input-hover-border:var(--el-border-color-hover);--el-input-clear-hover-color:var(--el-text-color-secondary);--el-input-focus-border:var(--el-color-primary);position:relative;font-size:var(--el-font-size-base);display:inline-block;width:100%;line-height:40px}.el-input::-webkit-scrollbar{z-index:11;width:6px}.el-input::-webkit-scrollbar:horizontal{height:6px}.el-input::-webkit-scrollbar-thumb{border-radius:5px;width:6px;background:#b4bccc}.el-input::-webkit-scrollbar-corner{background:#fff}.el-input::-webkit-scrollbar-track{background:#fff}.el-input::-webkit-scrollbar-track-piece{background:#fff;width:6px}.el-input .el-input__clear{color:var(--el-input-icon-color);font-size:var(--el-font-size-base,14px);cursor:pointer;-webkit-transition:var(--el-transition-color);transition:var(--el-transition-color)}.el-input .el-input__clear:hover{color:var(--el-input-clear-hover-color)}.el-input .el-input__count{height:100%;display:-webkit-inline-box;display:-ms-inline-flexbox;display:inline-flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;color:var(--el-color-info);font-size:12px}.el-input .el-input__count .el-input__count-inner{background:#fff;line-height:initial;display:inline-block;padding:0 5px}.el-input__inner{-webkit-appearance:none;background-color:var(--el-input-background-color,var(--el-color-white));background-image:none;border-radius:var(--el-input-border-radius,var(--el-border-radius-base));border:var(--el-input-border,var(--el-border-base));-webkit-box-sizing:border-box;box-sizing:border-box;color:var(--el-input-font-color,var(--el-text-color-regular));display:inline-block;font-size:inherit;height:40px;line-height:40px;outline:0;padding:0 15px;-webkit-transition:var(--el-transition-border);transition:var(--el-transition-border);width:100%}.el-input__inner::-webkit-input-placeholder{color:var(--el-input-placeholder-color,var(--el-text-color-placeholder))}.el-input__inner::-moz-placeholder{color:var(--el-input-placeholder-color,var(--el-text-color-placeholder))}.el-input__inner:-ms-input-placeholder{color:var(--el-input-placeholder-color,var(--el-text-color-placeholder))}.el-input__inner::-ms-input-placeholder{color:var(--el-input-placeholder-color,var(--el-text-color-placeholder))}.el-input__inner::placeholder{color:var(--el-input-placeholder-color,var(--el-text-color-placeholder))}.el-input__inner:hover{border-color:var(--el-input-hover-border,var(--el-border-color-hover))}.el-input__inner:focus{outline:0;border-color:var(--el-input-focus-border,var(--el-color-primary))}.el-input__suffix{position:absolute;height:100%;right:5px;top:0;text-align:center;color:var(--el-input-icon-color,var(--el-text-color-placeholder));-webkit-transition:all var(--el-transition-duration);transition:all var(--el-transition-duration);pointer-events:none}.el-input__suffix-inner{pointer-events:all}.el-input__prefix{position:absolute;height:100%;left:5px;top:0;text-align:center;color:var(--el-input-icon-color,var(--el-text-color-placeholder));-webkit-transition:all var(--el-transition-duration);transition:all var(--el-transition-duration)}.el-input__icon{width:25px;text-align:center;-webkit-transition:all var(--el-transition-duration);transition:all var(--el-transition-duration);line-height:40px}.el-input__icon:after{content:"";height:100%;width:0;display:inline-block;vertical-align:middle}.el-input__validateIcon{pointer-events:none}.el-input.is-active .el-input__inner{outline:0;border-color:var(--el-input-focus-border,)}.el-input.is-disabled .el-input__inner{background-color:var(--el-disabled-fill-base);border-color:var(--el-disabled-border-base);color:var(--el-disabled-color-base);cursor:not-allowed}.el-input.is-disabled .el-input__inner::-webkit-input-placeholder{color:var(--el-text-color-placeholder)}.el-input.is-disabled .el-input__inner::-moz-placeholder{color:var(--el-text-color-placeholder)}.el-input.is-disabled .el-input__inner:-ms-input-placeholder{color:var(--el-text-color-placeholder)}.el-input.is-disabled .el-input__inner::-ms-input-placeholder{color:var(--el-text-color-placeholder)}.el-input.is-disabled .el-input__inner::placeholder{color:var(--el-text-color-placeholder)}.el-input.is-disabled .el-input__icon{cursor:not-allowed}.el-input.is-exceed .el-input__inner{border-color:var(--el-color-danger)}.el-input.is-exceed .el-input__suffix .el-input__count{color:var(--el-color-danger)}.el-input--suffix .el-input__inner{padding-right:30px}.el-input--suffix--password-clear .el-input__inner{padding-right:55px}.el-input--prefix .el-input__inner{padding-left:30px}.el-input--medium{font-size:14px;line-height:36px}.el-input--medium .el-input__inner{height:36px;line-height:36px}.el-input--medium .el-input__icon{line-height:36px}.el-input--small{font-size:13px;line-height:32px}.el-input--small .el-input__inner{height:32px;line-height:32px}.el-input--small .el-input__icon{line-height:32px}.el-input--mini{font-size:12px;line-height:28px}.el-input--mini .el-input__inner{height:28px;line-height:28px}.el-input--mini .el-input__icon{line-height:28px}.el-input-group{line-height:normal;display:inline-table;width:100%;border-collapse:separate;border-spacing:0}.el-input-group>.el-input__inner{vertical-align:middle;display:table-cell}.el-input-group__append,.el-input-group__prepend{background-color:var(--el-background-color-base);color:var(--el-color-info);vertical-align:middle;display:table-cell;position:relative;border:1px solid #dcdfe6;border-radius:var(--el-input-border-radius);padding:0 20px;width:1px;white-space:nowrap}.el-input-group__append:focus,.el-input-group__prepend:focus{outline:0}.el-input-group__append .el-button,.el-input-group__append .el-select,.el-input-group__prepend .el-button,.el-input-group__prepend .el-select{display:inline-block;margin:-10px -20px}.el-input-group__append button.el-button,.el-input-group__append div.el-select .el-input__inner,.el-input-group__append div.el-select:hover .el-input__inner,.el-input-group__prepend button.el-button,.el-input-group__prepend div.el-select .el-input__inner,.el-input-group__prepend div.el-select:hover .el-input__inner{border-color:transparent;background-color:transparent;color:inherit;border-top:0;border-bottom:0}.el-input-group__append .el-button,.el-input-group__append .el-input,.el-input-group__prepend .el-button,.el-input-group__prepend .el-input{font-size:inherit}.el-input-group__prepend{border-right:0;border-top-right-radius:0;border-bottom-right-radius:0}.el-input-group__append{border-left:0;border-top-left-radius:0;border-bottom-left-radius:0}.el-input-group--prepend .el-input__inner{border-top-left-radius:0;border-bottom-left-radius:0}.el-input-group--prepend .el-select .el-input.is-focus .el-input__inner{border-color:transparent}.el-input-group--append .el-input__inner{border-top-right-radius:0;border-bottom-right-radius:0}.el-input-group--append .el-select .el-input.is-focus .el-input__inner{border-color:transparent}.el-input__inner::-ms-clear{display:none;width:0;height:0}.el-message{--el-message-min-width:380px;--el-message-background-color:#edf2fc;--el-message-padding:15px 15px 15px 20px;--el-message-close-size:16px;--el-message-close-icon-color:var(--el-text-color-placeholder);--el-message-close-hover-color:var(--el-text-color-secondary) }.el-message{min-width:var(--el-message-min-width);-webkit-box-sizing:border-box;box-sizing:border-box;border-radius:var(--el-border-radius-base);border-width:var(--el-border-width-base);border-style:var(--el-border-style-base);border-color:var(--el-border-color-lighter);position:fixed;left:50%;top:20px;-webkit-transform:translateX(-50%);transform:translate(-50%);-webkit-transition:opacity .3s,top .4s,-webkit-transform .4s;transition:opacity .3s,top .4s,-webkit-transform .4s;transition:opacity .3s,transform .4s,top .4s;transition:opacity .3s,transform .4s,top .4s,-webkit-transform .4s;background-color:var(--el-message-background-color);-webkit-transition:opacity var(--el-transition-duration),top .4s,-webkit-transform .4s;transition:opacity var(--el-transition-duration),top .4s,-webkit-transform .4s;transition:opacity var(--el-transition-duration),transform .4s,top .4s;transition:opacity var(--el-transition-duration),transform .4s,top .4s,-webkit-transform .4s;overflow:hidden;padding:var(--el-message-padding);display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.el-message.is-center{-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center}.el-message.is-closable .el-message__content{padding-right:16px}.el-message p{margin:0}.el-message--info .el-message__content{color:var(--el-message-info-font-color)}.el-message--success{background-color:#f0f9eb;border-color:#e1f3d8;--el-message-font-color:var(--el-color-success) }.el-message--success .el-message__content{color:var(--el-message-font-color)}.el-message--info{background-color:#f4f4f5;border-color:#e9e9eb;--el-message-font-color:var(--el-color-info) }.el-message--info .el-message__content{color:var(--el-message-font-color)}.el-message--warning{background-color:#fdf6ec;border-color:#faecd8;--el-message-font-color:var(--el-color-warning) }.el-message--warning .el-message__content{color:var(--el-message-font-color)}.el-message--error{background-color:#fef0f0;border-color:#fde2e2;--el-message-font-color:var(--el-color-error) }.el-message--error .el-message__content{color:var(--el-message-font-color)}.el-message__icon{margin-right:10px}.el-message__content{padding:0;font-size:14px;line-height:1}.el-message__content:focus{outline-width:0}.el-message__closeBtn{position:absolute;top:50%;right:15px;-webkit-transform:translateY(-50%);transform:translateY(-50%);cursor:pointer;color:var(--el-message-close-icon-color);font-size:var(--el-message-close-size,16px)}.el-message__closeBtn:focus{outline-width:0}.el-message__closeBtn:hover{color:var(--el-message-close-hover-color)}.el-message .el-icon-success{--el-message-font-color:var(--el-color-success);color:var(--el-message-font-color)}.el-message .el-icon-info{--el-message-font-color:var(--el-color-info);color:var(--el-message-font-color)}.el-message .el-icon-warning{--el-message-font-color:var(--el-color-warning);color:var(--el-message-font-color)}.el-message .el-icon-error{--el-message-font-color:var(--el-color-error);color:var(--el-message-font-color)}.el-message-fade-enter-from,.el-message-fade-leave-to{opacity:0;-webkit-transform:translate(-50%,-100%);transform:translate(-50%,-100%)}:root{--el-color-white:#ffffff;--el-color-black:#000000;--el-color-primary:#409eff;--el-color-primary-light-1:#53a8ff;--el-color-primary-light-2:#66b1ff;--el-color-primary-light-3:#79bbff;--el-color-primary-light-4:#8cc5ff;--el-color-primary-light-5:#a0cfff;--el-color-primary-light-6:#b3d8ff;--el-color-primary-light-7:#c6e2ff;--el-color-primary-light-8:#d9ecff;--el-color-primary-light-9:#ecf5ff;--el-color-success:#67c23a;--el-color-success-light:#e1f3d8;--el-color-success-lighter:#f0f9eb;--el-color-warning:#e6a23c;--el-color-warning-light:#faecd8;--el-color-warning-lighter:#fdf6ec;--el-color-danger:#f56c6c;--el-color-danger-light:#fde2e2;--el-color-danger-lighter:#fef0f0;--el-color-error:#f56c6c;--el-color-error-light:#fde2e2;--el-color-error-lighter:#fef0f0;--el-color-info:#909399;--el-color-info-light:#e9e9eb;--el-color-info-lighter:#f4f4f5;--el-text-color-primary:#303133;--el-text-color-regular:#606266;--el-text-color-secondary:#909399;--el-text-color-placeholder:#c0c4cc;--el-border-color-base:#dcdfe6;--el-border-color-light:#e4e7ed;--el-border-color-lighter:#ebeef5;--el-border-color-extra-light:#f2f6fc;--el-background-color-base:#f5f7fa;--el-border-width-base:1px;--el-border-style-base:solid;--el-border-color-hover:var(--el-text-color-placeholder);--el-border-base:var(--el-border-width-base) var(--el-border-style-base) var(--el-border-color-base);--el-border-radius-base:4px;--el-border-radius-small:2px;--el-border-radius-round:20px;--el-border-radius-circle:100%;--el-box-shadow-base:0 2px 4px rgba(0, 0, 0, .12),0 0 6px rgba(0, 0, 0, .04);--el-box-shadow-light:0 2px 12px 0 rgba(0, 0, 0, .1);--el-svg-monochrome-grey:#dcdde0;--el-fill-base:var(--el-color-white);--el-font-size-extra-large:20px;--el-font-size-large:18px;--el-font-size-medium:16px;--el-font-size-base:14px;--el-font-size-small:13px;--el-font-size-extra-small:12px;--el-font-weight-primary:500;--el-font-line-height-primary:24px;--el-font-color-disabled-base:#bbb;--el-index-normal:1;--el-index-top:1000;--el-index-popper:2000;--el-disabled-fill-base:var(--el-background-color-base);--el-disabled-color-base:var(--el-text-color-placeholder);--el-disabled-border-base:var(--el-border-color-light);--el-transition-duration:.3s;--el-transition-duration-fast:.2s;--el-transition-function-ease-in-out-bezier:cubic-bezier(.645, .045, .355, 1);--el-transition-function-fast-bezier:cubic-bezier(.23, 1, .32, 1);--el-transition-all:all var(--el-transition-duration) var(--el-transition-function-ease-in-out-bezier);--el-transition-fade:opacity var(--el-transition-duration) var(--el-transition-function-fast-bezier);--el-transition-md-fade:transform var(--el-transition-duration) var(--el-transition-function-fast-bezier),opacity var(--el-transition-duration) var(--el-transition-function-fast-bezier);--el-transition-fade-linear:opacity var(--el-transition-duration-fast) linear;--el-transition-border:border-color var(--el-transition-duration-fast) var(--el-transition-function-ease-in-out-bezier);--el-transition-color:color var(--el-transition-duration-fast) var(--el-transition-function-ease-in-out-bezier) }.fade-in-linear-enter-active,.fade-in-linear-leave-active{-webkit-transition:var(--el-transition-fade-linear);transition:var(--el-transition-fade-linear)}.fade-in-linear-enter-from,.fade-in-linear-leave-to{opacity:0}.el-fade-in-linear-enter-active,.el-fade-in-linear-leave-active{-webkit-transition:var(--el-transition-fade-linear);transition:var(--el-transition-fade-linear)}.el-fade-in-linear-enter-from,.el-fade-in-linear-leave-to{opacity:0}.el-fade-in-enter-active,.el-fade-in-leave-active{-webkit-transition:all var(--el-transition-duration) cubic-bezier(.55,0,.1,1);transition:all var(--el-transition-duration) cubic-bezier(.55,0,.1,1)}.el-fade-in-enter-from,.el-fade-in-leave-active{opacity:0}.el-zoom-in-center-enter-active,.el-zoom-in-center-leave-active{-webkit-transition:all var(--el-transition-duration) cubic-bezier(.55,0,.1,1);transition:all var(--el-transition-duration) cubic-bezier(.55,0,.1,1)}.el-zoom-in-center-enter-from,.el-zoom-in-center-leave-active{opacity:0;-webkit-transform:scaleX(0);transform:scaleX(0)}.el-zoom-in-top-enter-active,.el-zoom-in-top-leave-active{opacity:1;-webkit-transform:scaleY(1);transform:scaleY(1);-webkit-transition:var(--el-transition-md-fade);transition:var(--el-transition-md-fade);-webkit-transform-origin:center top;transform-origin:center top}.el-zoom-in-top-enter-active[data-popper-placement^=top],.el-zoom-in-top-leave-active[data-popper-placement^=top]{-webkit-transform-origin:center bottom;transform-origin:center bottom}.el-zoom-in-top-enter-from,.el-zoom-in-top-leave-active{opacity:0;-webkit-transform:scaleY(0);transform:scaleY(0)}.el-zoom-in-bottom-enter-active,.el-zoom-in-bottom-leave-active{opacity:1;-webkit-transform:scaleY(1);transform:scaleY(1);-webkit-transition:var(--el-transition-md-fade);transition:var(--el-transition-md-fade);-webkit-transform-origin:center bottom;transform-origin:center bottom}.el-zoom-in-bottom-enter-from,.el-zoom-in-bottom-leave-active{opacity:0;-webkit-transform:scaleY(0);transform:scaleY(0)}.el-zoom-in-left-enter-active,.el-zoom-in-left-leave-active{opacity:1;-webkit-transform:scale(1,1);transform:scale(1);-webkit-transition:var(--el-transition-md-fade);transition:var(--el-transition-md-fade);-webkit-transform-origin:top left;transform-origin:top left}.el-zoom-in-left-enter-from,.el-zoom-in-left-leave-active{opacity:0;-webkit-transform:scale(.45,.45);transform:scale(.45)}.collapse-transition{-webkit-transition:var(--el-transition-duration) height ease-in-out,var(--el-transition-duration) padding-top ease-in-out,var(--el-transition-duration) padding-bottom ease-in-out;transition:var(--el-transition-duration) height ease-in-out,var(--el-transition-duration) padding-top ease-in-out,var(--el-transition-duration) padding-bottom ease-in-out}.horizontal-collapse-transition{-webkit-transition:var(--el-transition-duration) width ease-in-out,var(--el-transition-duration) padding-left ease-in-out,var(--el-transition-duration) padding-right ease-in-out;transition:var(--el-transition-duration) width ease-in-out,var(--el-transition-duration) padding-left ease-in-out,var(--el-transition-duration) padding-right ease-in-out}.el-list-enter-active,.el-list-leave-active{-webkit-transition:all 1s;transition:all 1s}.el-list-enter-from,.el-list-leave-active{opacity:0;-webkit-transform:translateY(-30px);transform:translateY(-30px)}.el-opacity-transition{-webkit-transition:opacity var(--el-transition-duration) cubic-bezier(.55,0,.1,1);transition:opacity var(--el-transition-duration) cubic-bezier(.55,0,.1,1)}@font-face{font-family:element-icons;src:url(/assets/element-icons.9c88a535.woff) format("woff"),url(/assets/element-icons.de5eb258.ttf) format("truetype");font-weight:400;font-display:auto;font-style:normal}[class*=" el-icon-"],[class^=el-icon-]{font-family:element-icons!important;speak:none;font-style:normal;font-weight:400;font-variant:normal;text-transform:none;line-height:1;vertical-align:baseline;display:inline-block;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.el-icon-ice-cream-round:before{content:"\e6a0"}.el-icon-ice-cream-square:before{content:"\e6a3"}.el-icon-lollipop:before{content:"\e6a4"}.el-icon-potato-strips:before{content:"\e6a5"}.el-icon-milk-tea:before{content:"\e6a6"}.el-icon-ice-drink:before{content:"\e6a7"}.el-icon-ice-tea:before{content:"\e6a9"}.el-icon-coffee:before{content:"\e6aa"}.el-icon-orange:before{content:"\e6ab"}.el-icon-pear:before{content:"\e6ac"}.el-icon-apple:before{content:"\e6ad"}.el-icon-cherry:before{content:"\e6ae"}.el-icon-watermelon:before{content:"\e6af"}.el-icon-grape:before{content:"\e6b0"}.el-icon-refrigerator:before{content:"\e6b1"}.el-icon-goblet-square-full:before{content:"\e6b2"}.el-icon-goblet-square:before{content:"\e6b3"}.el-icon-goblet-full:before{content:"\e6b4"}.el-icon-goblet:before{content:"\e6b5"}.el-icon-cold-drink:before{content:"\e6b6"}.el-icon-coffee-cup:before{content:"\e6b8"}.el-icon-water-cup:before{content:"\e6b9"}.el-icon-hot-water:before{content:"\e6ba"}.el-icon-ice-cream:before{content:"\e6bb"}.el-icon-dessert:before{content:"\e6bc"}.el-icon-sugar:before{content:"\e6bd"}.el-icon-tableware:before{content:"\e6be"}.el-icon-burger:before{content:"\e6bf"}.el-icon-knife-fork:before{content:"\e6c1"}.el-icon-fork-spoon:before{content:"\e6c2"}.el-icon-chicken:before{content:"\e6c3"}.el-icon-food:before{content:"\e6c4"}.el-icon-dish-1:before{content:"\e6c5"}.el-icon-dish:before{content:"\e6c6"}.el-icon-moon-night:before{content:"\e6ee"}.el-icon-moon:before{content:"\e6f0"}.el-icon-cloudy-and-sunny:before{content:"\e6f1"}.el-icon-partly-cloudy:before{content:"\e6f2"}.el-icon-cloudy:before{content:"\e6f3"}.el-icon-sunny:before{content:"\e6f6"}.el-icon-sunset:before{content:"\e6f7"}.el-icon-sunrise-1:before{content:"\e6f8"}.el-icon-sunrise:before{content:"\e6f9"}.el-icon-heavy-rain:before{content:"\e6fa"}.el-icon-lightning:before{content:"\e6fb"}.el-icon-light-rain:before{content:"\e6fc"}.el-icon-wind-power:before{content:"\e6fd"}.el-icon-baseball:before{content:"\e712"}.el-icon-soccer:before{content:"\e713"}.el-icon-football:before{content:"\e715"}.el-icon-basketball:before{content:"\e716"}.el-icon-ship:before{content:"\e73f"}.el-icon-truck:before{content:"\e740"}.el-icon-bicycle:before{content:"\e741"}.el-icon-mobile-phone:before{content:"\e6d3"}.el-icon-service:before{content:"\e6d4"}.el-icon-key:before{content:"\e6e2"}.el-icon-unlock:before{content:"\e6e4"}.el-icon-lock:before{content:"\e6e5"}.el-icon-watch:before{content:"\e6fe"}.el-icon-watch-1:before{content:"\e6ff"}.el-icon-timer:before{content:"\e702"}.el-icon-alarm-clock:before{content:"\e703"}.el-icon-map-location:before{content:"\e704"}.el-icon-delete-location:before{content:"\e705"}.el-icon-add-location:before{content:"\e706"}.el-icon-location-information:before{content:"\e707"}.el-icon-location-outline:before{content:"\e708"}.el-icon-location:before{content:"\e79e"}.el-icon-place:before{content:"\e709"}.el-icon-discover:before{content:"\e70a"}.el-icon-first-aid-kit:before{content:"\e70b"}.el-icon-trophy-1:before{content:"\e70c"}.el-icon-trophy:before{content:"\e70d"}.el-icon-medal:before{content:"\e70e"}.el-icon-medal-1:before{content:"\e70f"}.el-icon-stopwatch:before{content:"\e710"}.el-icon-mic:before{content:"\e711"}.el-icon-copy-document:before{content:"\e718"}.el-icon-full-screen:before{content:"\e719"}.el-icon-switch-button:before{content:"\e71b"}.el-icon-aim:before{content:"\e71c"}.el-icon-crop:before{content:"\e71d"}.el-icon-odometer:before{content:"\e71e"}.el-icon-time:before{content:"\e71f"}.el-icon-bangzhu:before{content:"\e724"}.el-icon-close-notification:before{content:"\e726"}.el-icon-microphone:before{content:"\e727"}.el-icon-turn-off-microphone:before{content:"\e728"}.el-icon-position:before{content:"\e729"}.el-icon-postcard:before{content:"\e72a"}.el-icon-message:before{content:"\e72b"}.el-icon-chat-line-square:before{content:"\e72d"}.el-icon-chat-dot-square:before{content:"\e72e"}.el-icon-chat-dot-round:before{content:"\e72f"}.el-icon-chat-square:before{content:"\e730"}.el-icon-chat-line-round:before{content:"\e731"}.el-icon-chat-round:before{content:"\e732"}.el-icon-set-up:before{content:"\e733"}.el-icon-turn-off:before{content:"\e734"}.el-icon-open:before{content:"\e735"}.el-icon-connection:before{content:"\e736"}.el-icon-link:before{content:"\e737"}.el-icon-cpu:before{content:"\e738"}.el-icon-thumb:before{content:"\e739"}.el-icon-female:before{content:"\e73a"}.el-icon-male:before{content:"\e73b"}.el-icon-guide:before{content:"\e73c"}.el-icon-news:before{content:"\e73e"}.el-icon-price-tag:before{content:"\e744"}.el-icon-discount:before{content:"\e745"}.el-icon-wallet:before{content:"\e747"}.el-icon-coin:before{content:"\e748"}.el-icon-money:before{content:"\e749"}.el-icon-bank-card:before{content:"\e74a"}.el-icon-box:before{content:"\e74b"}.el-icon-present:before{content:"\e74c"}.el-icon-sell:before{content:"\e6d5"}.el-icon-sold-out:before{content:"\e6d6"}.el-icon-shopping-bag-2:before{content:"\e74d"}.el-icon-shopping-bag-1:before{content:"\e74e"}.el-icon-shopping-cart-2:before{content:"\e74f"}.el-icon-shopping-cart-1:before{content:"\e750"}.el-icon-shopping-cart-full:before{content:"\e751"}.el-icon-smoking:before{content:"\e752"}.el-icon-no-smoking:before{content:"\e753"}.el-icon-house:before{content:"\e754"}.el-icon-table-lamp:before{content:"\e755"}.el-icon-school:before{content:"\e756"}.el-icon-office-building:before{content:"\e757"}.el-icon-toilet-paper:before{content:"\e758"}.el-icon-notebook-2:before{content:"\e759"}.el-icon-notebook-1:before{content:"\e75a"}.el-icon-files:before{content:"\e75b"}.el-icon-collection:before{content:"\e75c"}.el-icon-receiving:before{content:"\e75d"}.el-icon-suitcase-1:before{content:"\e760"}.el-icon-suitcase:before{content:"\e761"}.el-icon-film:before{content:"\e763"}.el-icon-collection-tag:before{content:"\e765"}.el-icon-data-analysis:before{content:"\e766"}.el-icon-pie-chart:before{content:"\e767"}.el-icon-data-board:before{content:"\e768"}.el-icon-data-line:before{content:"\e76d"}.el-icon-reading:before{content:"\e769"}.el-icon-magic-stick:before{content:"\e76a"}.el-icon-coordinate:before{content:"\e76b"}.el-icon-mouse:before{content:"\e76c"}.el-icon-brush:before{content:"\e76e"}.el-icon-headset:before{content:"\e76f"}.el-icon-umbrella:before{content:"\e770"}.el-icon-scissors:before{content:"\e771"}.el-icon-mobile:before{content:"\e773"}.el-icon-attract:before{content:"\e774"}.el-icon-monitor:before{content:"\e775"}.el-icon-search:before{content:"\e778"}.el-icon-takeaway-box:before{content:"\e77a"}.el-icon-paperclip:before{content:"\e77d"}.el-icon-printer:before{content:"\e77e"}.el-icon-document-add:before{content:"\e782"}.el-icon-document:before{content:"\e785"}.el-icon-document-checked:before{content:"\e786"}.el-icon-document-copy:before{content:"\e787"}.el-icon-document-delete:before{content:"\e788"}.el-icon-document-remove:before{content:"\e789"}.el-icon-tickets:before{content:"\e78b"}.el-icon-folder-checked:before{content:"\e77f"}.el-icon-folder-delete:before{content:"\e780"}.el-icon-folder-remove:before{content:"\e781"}.el-icon-folder-add:before{content:"\e783"}.el-icon-folder-opened:before{content:"\e784"}.el-icon-folder:before{content:"\e78a"}.el-icon-edit-outline:before{content:"\e764"}.el-icon-edit:before{content:"\e78c"}.el-icon-date:before{content:"\e78e"}.el-icon-c-scale-to-original:before{content:"\e7c6"}.el-icon-view:before{content:"\e6ce"}.el-icon-loading:before{content:"\e6cf"}.el-icon-rank:before{content:"\e6d1"}.el-icon-sort-down:before{content:"\e7c4"}.el-icon-sort-up:before{content:"\e7c5"}.el-icon-sort:before{content:"\e6d2"}.el-icon-finished:before{content:"\e6cd"}.el-icon-refresh-left:before{content:"\e6c7"}.el-icon-refresh-right:before{content:"\e6c8"}.el-icon-refresh:before{content:"\e6d0"}.el-icon-video-play:before{content:"\e7c0"}.el-icon-video-pause:before{content:"\e7c1"}.el-icon-d-arrow-right:before{content:"\e6dc"}.el-icon-d-arrow-left:before{content:"\e6dd"}.el-icon-arrow-up:before{content:"\e6e1"}.el-icon-arrow-down:before{content:"\e6df"}.el-icon-arrow-right:before{content:"\e6e0"}.el-icon-arrow-left:before{content:"\e6de"}.el-icon-top-right:before{content:"\e6e7"}.el-icon-top-left:before{content:"\e6e8"}.el-icon-top:before{content:"\e6e6"}.el-icon-bottom:before{content:"\e6eb"}.el-icon-right:before{content:"\e6e9"}.el-icon-back:before{content:"\e6ea"}.el-icon-bottom-right:before{content:"\e6ec"}.el-icon-bottom-left:before{content:"\e6ed"}.el-icon-caret-top:before{content:"\e78f"}.el-icon-caret-bottom:before{content:"\e790"}.el-icon-caret-right:before{content:"\e791"}.el-icon-caret-left:before{content:"\e792"}.el-icon-d-caret:before{content:"\e79a"}.el-icon-share:before{content:"\e793"}.el-icon-menu:before{content:"\e798"}.el-icon-s-grid:before{content:"\e7a6"}.el-icon-s-check:before{content:"\e7a7"}.el-icon-s-data:before{content:"\e7a8"}.el-icon-s-opportunity:before{content:"\e7aa"}.el-icon-s-custom:before{content:"\e7ab"}.el-icon-s-claim:before{content:"\e7ad"}.el-icon-s-finance:before{content:"\e7ae"}.el-icon-s-comment:before{content:"\e7af"}.el-icon-s-flag:before{content:"\e7b0"}.el-icon-s-marketing:before{content:"\e7b1"}.el-icon-s-shop:before{content:"\e7b4"}.el-icon-s-open:before{content:"\e7b5"}.el-icon-s-management:before{content:"\e7b6"}.el-icon-s-ticket:before{content:"\e7b7"}.el-icon-s-release:before{content:"\e7b8"}.el-icon-s-home:before{content:"\e7b9"}.el-icon-s-promotion:before{content:"\e7ba"}.el-icon-s-operation:before{content:"\e7bb"}.el-icon-s-unfold:before{content:"\e7bc"}.el-icon-s-fold:before{content:"\e7a9"}.el-icon-s-platform:before{content:"\e7bd"}.el-icon-s-order:before{content:"\e7be"}.el-icon-s-cooperation:before{content:"\e7bf"}.el-icon-bell:before{content:"\e725"}.el-icon-message-solid:before{content:"\e799"}.el-icon-video-camera:before{content:"\e772"}.el-icon-video-camera-solid:before{content:"\e796"}.el-icon-camera:before{content:"\e779"}.el-icon-camera-solid:before{content:"\e79b"}.el-icon-download:before{content:"\e77c"}.el-icon-upload2:before{content:"\e77b"}.el-icon-upload:before{content:"\e7c3"}.el-icon-picture-outline-round:before{content:"\e75f"}.el-icon-picture-outline:before{content:"\e75e"}.el-icon-picture:before{content:"\e79f"}.el-icon-close:before{content:"\e6db"}.el-icon-check:before{content:"\e6da"}.el-icon-plus:before{content:"\e6d9"}.el-icon-minus:before{content:"\e6d8"}.el-icon-help:before{content:"\e73d"}.el-icon-s-help:before{content:"\e7b3"}.el-icon-circle-close:before{content:"\e78d"}.el-icon-circle-check:before{content:"\e720"}.el-icon-circle-plus-outline:before{content:"\e723"}.el-icon-remove-outline:before{content:"\e722"}.el-icon-zoom-out:before{content:"\e776"}.el-icon-zoom-in:before{content:"\e777"}.el-icon-error:before{content:"\e79d"}.el-icon-success:before{content:"\e79c"}.el-icon-circle-plus:before{content:"\e7a0"}.el-icon-remove:before{content:"\e7a2"}.el-icon-info:before{content:"\e7a1"}.el-icon-question:before{content:"\e7a4"}.el-icon-warning-outline:before{content:"\e6c9"}.el-icon-warning:before{content:"\e7a3"}.el-icon-goods:before{content:"\e7c2"}.el-icon-s-goods:before{content:"\e7b2"}.el-icon-star-off:before{content:"\e717"}.el-icon-star-on:before{content:"\e797"}.el-icon-more-outline:before{content:"\e6cc"}.el-icon-more:before{content:"\e794"}.el-icon-phone-outline:before{content:"\e6cb"}.el-icon-phone:before{content:"\e795"}.el-icon-user:before{content:"\e6e3"}.el-icon-user-solid:before{content:"\e7a5"}.el-icon-setting:before{content:"\e6ca"}.el-icon-s-tools:before{content:"\e7ac"}.el-icon-delete:before{content:"\e6d7"}.el-icon-delete-solid:before{content:"\e7c9"}.el-icon-eleme:before{content:"\e7c7"}.el-icon-platform-eleme:before{content:"\e7ca"}.el-icon-loading{-webkit-animation:rotating 2s linear infinite;animation:rotating 2s linear infinite}.el-icon--right{margin-left:5px}.el-icon--left{margin-right:5px}@-webkit-keyframes rotating{0%{-webkit-transform:rotateZ(0);transform:rotate(0)}to{-webkit-transform:rotateZ(360deg);transform:rotate(360deg)}}@keyframes rotating{0%{-webkit-transform:rotateZ(0);transform:rotate(0)}to{-webkit-transform:rotateZ(360deg);transform:rotate(360deg)}}.el-icon{--color:inherit;--font-size:14px;height:1em;width:1em;line-height:1em;text-align:center;display:inline-block;position:relative;fill:currentColor;color:var(--color);font-size:var(--font-size)}.el-icon.is-loading{-webkit-animation:rotating 2s linear infinite;animation:rotating 2s linear infinite}.el-icon svg{height:1em;width:1em}a[data-v-4b23e37a]{color:#42b983}.NinjaLogo{-webkit-animation:1s appear;animation:1s appear}@-webkit-keyframes appear{0%{opacity:0}}@keyframes appear{0%{opacity:0}}.header[data-v-1f23ce5f]{height:4rem;--tw-bg-opacity: 1;background-color:rgba(255,255,255,var(--tw-bg-opacity));--tw-shadow: 0 4px 6px -1px rgba(0, 0, 0, .1), 0 2px 4px -1px rgba(0, 0, 0, .06);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow);--tw-drop-shadow: drop-shadow(0 1px 1px rgba(0,0,0,.05))}.header-wrapper[data-v-1f23ce5f]{margin:auto;display:flex;height:100%;width:91.666667%;max-width:64rem;align-items:center;justify-content:space-between;font-size:1.5rem;line-height:2rem;font-weight:700}#app{font-family:"Source Sans Pro",-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;font-size:16px;word-spacing:1px;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;box-sizing:border-box;min-height:100vh;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;--tw-bg-opacity: 1;background-color:rgba(249,250,251,var(--tw-bg-opacity));padding-bottom:1.25rem}*,*:before,*:after{box-sizing:border-box;margin:0}.content{margin:auto;width:91.666667%;max-width:64rem}.card{margin:1.25rem auto auto;border-radius:.5rem;--tw-bg-opacity: 1;background-color:rgba(255,255,255,var(--tw-bg-opacity));--tw-shadow: 0 10px 15px -3px rgba(0, 0, 0, .1), 0 4px 6px -2px rgba(0, 0, 0, .05);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.card-header{border-bottom-width:1px;padding:1rem}.card-title{font-size:1.125rem;line-height:1.75rem;font-weight:500}.card-subtitle{font-size:.875rem;line-height:1.25rem;font-weight:400;--tw-text-opacity: 1;color:rgba(107,114,128,var(--tw-text-opacity))}.card-body{padding:1rem;font-size:.875rem;line-height:1.25rem}.card-footer{padding-right:1rem;padding-bottom:1rem;padding-left:1rem;text-align:right}*,:before,:after{box-sizing:border-box}html{-moz-tab-size:4;-o-tab-size:4;tab-size:4}html{line-height:1.15;-webkit-text-size-adjust:100%}body{margin:0}body{font-family:system-ui,-apple-system,"Segoe UI",Roboto,Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji"}hr{height:0;color:inherit}abbr[title]{-webkit-text-decoration:underline dotted;text-decoration:underline dotted}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:ui-monospace,SFMono-Regular,Consolas,"Liberation Mono",Menlo,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;line-height:1.15;margin:0}button,select{text-transform:none}button,[type=button]{-webkit-appearance:button}legend{padding:0}progress{vertical-align:baseline}summary{display:list-item}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}button{background-color:transparent;background-image:none}fieldset{margin:0;padding:0}ol,ul{list-style:none;margin:0;padding:0}html{font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";line-height:1.5}body{font-family:inherit;line-height:inherit}*,:before,:after{box-sizing:border-box;border-width:0;border-style:solid;border-color:currentColor}hr{border-top-width:1px}img{border-style:solid}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input:-ms-input-placeholder,textarea:-ms-input-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}button{cursor:pointer}table{border-collapse:collapse}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}button,input,optgroup,select,textarea{padding:0;line-height:inherit;color:inherit}pre,code,kbd,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}*,:before,:after{--tw-border-opacity: 1;border-color:rgba(229,231,235,var(--tw-border-opacity))}#app .absolute{position:absolute}#app .z-50{z-index:50}#app .m-auto{margin:auto}#app .my-4{margin-top:1rem;margin-bottom:1rem}#app .mt-4{margin-top:1rem}#app .mt-5{margin-top:1.25rem}#app .ml-0{margin-left:0}#app .ml-2{margin-left:.5rem}#app .flex{display:flex}#app .table{display:table}#app .h-10{height:2.5rem}#app .h-16{height:4rem}#app .h-full{height:100%}#app .h-screen{height:100vh}#app .min-h-screen{min-height:100vh}#app .w-10{width:2.5rem}#app .w-48{width:12rem}#app .w-11\/12{width:91.666667%}#app .w-full{width:100%}#app .w-screen{width:100vw}#app .max-w-5xl{max-width:64rem}@-webkit-keyframes spin{to{transform:rotate(360deg)}}@keyframes spin{to{transform:rotate(360deg)}}@-webkit-keyframes ping{75%,to{transform:scale(2);opacity:0}}@keyframes ping{75%,to{transform:scale(2);opacity:0}}@-webkit-keyframes pulse{50%{opacity:.5}}@keyframes pulse{50%{opacity:.5}}@-webkit-keyframes bounce{0%,to{transform:translateY(-25%);-webkit-animation-timing-function:cubic-bezier(.8,0,1,1);animation-timing-function:cubic-bezier(.8,0,1,1)}50%{transform:none;-webkit-animation-timing-function:cubic-bezier(0,0,.2,1);animation-timing-function:cubic-bezier(0,0,.2,1)}}@keyframes bounce{0%,to{transform:translateY(-25%);-webkit-animation-timing-function:cubic-bezier(.8,0,1,1);animation-timing-function:cubic-bezier(.8,0,1,1)}50%{transform:none;-webkit-animation-timing-function:cubic-bezier(0,0,.2,1);animation-timing-function:cubic-bezier(0,0,.2,1)}}#app .select-none{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}#app .flex-col{flex-direction:column}#app .items-center{align-items:center}#app .justify-center{justify-content:center}#app .justify-between{justify-content:space-between}#app .rounded-lg{border-radius:.5rem}#app .rounded-full{border-radius:9999px}#app .border-b{border-bottom-width:1px}#app .bg-black{--tw-bg-opacity: 1;background-color:rgba(0,0,0,var(--tw-bg-opacity))}#app .bg-gray-50{--tw-bg-opacity: 1;background-color:rgba(249,250,251,var(--tw-bg-opacity))}#app .bg-gray-200{--tw-bg-opacity: 1;background-color:rgba(229,231,235,var(--tw-bg-opacity))}#app .bg-opacity-30{--tw-bg-opacity: .3}#app .px-2{padding-left:.5rem;padding-right:.5rem}#app .py-1{padding-top:.25rem;padding-bottom:.25rem}#app .pr-2{padding-right:.5rem}#app .pr-4{padding-right:1rem}#app .pb-5{padding-bottom:1.25rem}#app .pl-3{padding-left:.75rem}#app .pl-4{padding-left:1rem}#app .text-center{text-align:center}#app .text-right{text-align:right}#app .text-xs{font-size:.75rem;line-height:1rem}#app .text-sm{font-size:.875rem;line-height:1.25rem}#app .text-base{font-size:1rem;line-height:1.5rem}#app .text-2xl{font-size:1.5rem;line-height:2rem}#app .font-normal{font-weight:400}#app .font-medium{font-weight:500}#app .font-bold{font-weight:700}#app .leading-6{line-height:1.5rem}#app .leading-normal{line-height:1.5}#app .text-blue-400{--tw-text-opacity: 1;color:rgba(96,165,250,var(--tw-text-opacity))}*,:before,:after{--tw-shadow: 0 0 #0000}#app .shadow-md{--tw-shadow: 0 4px 6px -1px rgba(0, 0, 0, .1), 0 2px 4px -1px rgba(0, 0, 0, .06);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}#app .shadow-lg{--tw-shadow: 0 10px 15px -3px rgba(0, 0, 0, .1), 0 4px 6px -2px rgba(0, 0, 0, .05);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}*,:before,:after{--tw-ring-inset: var(--tw-empty, );--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgba(59, 130, 246, .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000}#app .drop-shadow-sm{--tw-drop-shadow: drop-shadow(0 1px 1px rgba(0,0,0,.05))}#app .backdrop-filter{--tw-backdrop-blur: var(--tw-empty, );--tw-backdrop-brightness: var(--tw-empty, );--tw-backdrop-contrast: var(--tw-empty, );--tw-backdrop-grayscale: var(--tw-empty, );--tw-backdrop-hue-rotate: var(--tw-empty, );--tw-backdrop-invert: var(--tw-empty, );--tw-backdrop-opacity: var(--tw-empty, );--tw-backdrop-saturate: var(--tw-empty, );--tw-backdrop-sepia: var(--tw-empty, );-webkit-backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia)}#app .backdrop-blur-sm{--tw-backdrop-blur: blur(4px)}#app .transition{transition-property:background-color,border-color,color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:background-color,border-color,color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:background-color,border-color,color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}a[data-v-71afa157]:link{color:#b321ff}a[data-v-71afa157]{color:#eecdff}a[data-v-71afa157]:hover{color:red}a[data-v-7065ab79]:link{color:#b321ff}a[data-v-7065ab79]{color:#eecdff}a[data-v-7065ab79]:hover{color:red} 2 | --------------------------------------------------------------------------------