神代綺凛 commited on
Commit
4c3f9c7
0 Parent(s):

Initial commit

Browse files
.eslintrc.cjs ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ module.exports = {
2
+ root: true,
3
+ env: {
4
+ browser: true,
5
+ es2022: true,
6
+ node: true,
7
+ },
8
+ extends: [
9
+ 'plugin:vue/vue3-recommended',
10
+ 'eslint:recommended',
11
+ '@vue/typescript/recommended',
12
+ '@vue/prettier',
13
+ 'plugin:import/recommended',
14
+ 'plugin:import/typescript',
15
+ ],
16
+ parserOptions: {
17
+ ecmaVersion: 2022,
18
+ parser: '@typescript-eslint/parser',
19
+ },
20
+ rules: {
21
+ 'prefer-const': ['error', { destructuring: 'all' }],
22
+ 'no-empty': ['error', { allowEmptyCatch: true }],
23
+ '@typescript-eslint/ban-types': 'off',
24
+ '@typescript-eslint/no-explicit-any': 'off',
25
+ '@typescript-eslint/ban-ts-comment': 'off',
26
+ '@typescript-eslint/consistent-type-imports': [
27
+ 'error',
28
+ {
29
+ disallowTypeAnnotations: false,
30
+ fixStyle: 'separate-type-imports',
31
+ },
32
+ ],
33
+ 'prettier/prettier': ['warn', {}, { usePrettierrc: true }],
34
+ 'import/order': ['warn', { groups: ['builtin', 'external', 'internal', 'parent', 'sibling', 'index', 'type'] }],
35
+ 'import/no-named-as-default': 'off',
36
+ },
37
+ settings: {
38
+ 'import/resolver': {
39
+ typescript: true,
40
+ node: true,
41
+ },
42
+ },
43
+ };
.gitattributes ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ # Auto detect text files and perform LF normalization
2
+ * text=auto
.gitignore ADDED
@@ -0,0 +1,106 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .vercel/output
2
+
3
+ # Logs
4
+ logs
5
+ *.log
6
+ npm-debug.log*
7
+ yarn-debug.log*
8
+ yarn-error.log*
9
+ lerna-debug.log*
10
+
11
+ # Diagnostic reports (https://nodejs.org/api/report.html)
12
+ report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
13
+
14
+ # Runtime data
15
+ pids
16
+ *.pid
17
+ *.seed
18
+ *.pid.lock
19
+
20
+ # Directory for instrumented libs generated by jscoverage/JSCover
21
+ lib-cov
22
+
23
+ # Coverage directory used by tools like istanbul
24
+ coverage
25
+ *.lcov
26
+
27
+ # nyc test coverage
28
+ .nyc_output
29
+
30
+ # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
31
+ .grunt
32
+
33
+ # Bower dependency directory (https://bower.io/)
34
+ bower_components
35
+
36
+ # node-waf configuration
37
+ .lock-wscript
38
+
39
+ # Compiled binary addons (https://nodejs.org/api/addons.html)
40
+ build/Release
41
+
42
+ # Dependency directories
43
+ node_modules/
44
+ jspm_packages/
45
+
46
+ # TypeScript v1 declaration files
47
+ typings/
48
+
49
+ # TypeScript cache
50
+ *.tsbuildinfo
51
+
52
+ # Optional npm cache directory
53
+ .npm
54
+
55
+ # Optional eslint cache
56
+ .eslintcache
57
+
58
+ # Microbundle cache
59
+ .rpt2_cache/
60
+ .rts2_cache_cjs/
61
+ .rts2_cache_es/
62
+ .rts2_cache_umd/
63
+
64
+ # Optional REPL history
65
+ .node_repl_history
66
+
67
+ # Output of 'npm pack'
68
+ *.tgz
69
+
70
+ # Yarn Integrity file
71
+ .yarn-integrity
72
+
73
+ # dotenv environment variables file
74
+ .env
75
+ .env.test
76
+
77
+ # parcel-bundler cache (https://parceljs.org/)
78
+ .cache
79
+
80
+ # Next.js build output
81
+ .next
82
+
83
+ # Nuxt.js build / generate output
84
+ .nuxt
85
+ dist
86
+
87
+ # Gatsby files
88
+ .cache/
89
+ # Comment in the public line in if your project uses Gatsby and *not* Next.js
90
+ # https://nextjs.org/blog/next-9-1#public-directory-support
91
+ # public
92
+
93
+ # vuepress build output
94
+ .vuepress/dist
95
+
96
+ # Serverless directories
97
+ .serverless/
98
+
99
+ # FuseBox cache
100
+ .fusebox/
101
+
102
+ # DynamoDB Local files
103
+ .dynamodb/
104
+
105
+ # TernJS port file
106
+ .tern-port
.prettierrc ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "printWidth": 120,
3
+ "tabWidth": 2,
4
+ "useTabs": false,
5
+ "semi": true,
6
+ "singleQuote": true,
7
+ "quoteProps": "as-needed",
8
+ "jsxSingleQuote": false,
9
+ "trailingComma": "all",
10
+ "bracketSpacing": true,
11
+ "arrowParens": "avoid",
12
+ "requirePragma": false,
13
+ "insertPragma": false,
14
+ "proseWrap": "preserve",
15
+ "htmlWhitespaceSensitivity": "css",
16
+ "vueIndentScriptAndStyle": false,
17
+ "endOfLine": "auto",
18
+ "embeddedLanguageFormatting": "auto"
19
+ }
.vscode/extensions.json ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ {
2
+ "recommendations": ["Vue.volar", "Vue.vscode-typescript-vue-plugin"]
3
+ }
.vscode/settings.json ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ {
2
+ "typescript.tsdk": "node_modules/typescript/lib"
3
+ }
LICENSE ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ MIT License
2
+
3
+ Copyright (c) 2024 kirinhuang
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
_api/qr.ts ADDED
@@ -0,0 +1,112 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Hono } from 'hono';
2
+ import { handle } from 'hono/vercel';
3
+ import { streamSSE } from 'hono/streaming';
4
+ import type { StreamingApi } from 'hono/utils/stream';
5
+
6
+ export const runtime = 'edge';
7
+
8
+ export const app = new Hono().basePath('/api');
9
+
10
+ app.get('/qr', c =>
11
+ streamSSE(c, async stream => {
12
+ let streamClosed = false;
13
+ handleStreamClose(stream, () => {
14
+ streamClosed = true;
15
+ });
16
+ while (!streamClosed) {
17
+ // await stream.writeSSE({ data: message, event: 'time-update' });
18
+ await stream.sleep(1000);
19
+ }
20
+ }),
21
+ );
22
+
23
+ export const GET = handle(app);
24
+
25
+ interface GenerateQrResp {
26
+ code: string;
27
+ message: string;
28
+ ttl: number;
29
+ data: {
30
+ url: string;
31
+ qrcode_key: string;
32
+ };
33
+ }
34
+
35
+ const generateQr = async () => {
36
+ const r = await fetch('https://passport.bilibili.com/x/passport-login/web/qrcode/generate');
37
+ const {
38
+ data: { url, qrcode_key },
39
+ } = (await r.json()) as GenerateQrResp;
40
+ return { url, key: qrcode_key };
41
+ };
42
+
43
+ interface PollQrResp {
44
+ code: string;
45
+ message: string;
46
+ ttl: number;
47
+ data: {
48
+ url: string;
49
+ refresh_token: string;
50
+ timestamp: string;
51
+ code: number;
52
+ message: string;
53
+ };
54
+ }
55
+
56
+ class Cookie {
57
+ private cookie = new Map<string, string>();
58
+
59
+ public constructor(cookies?: string[]) {
60
+ if (cookies) this.add(cookies);
61
+ }
62
+
63
+ public add(cookies: string[]) {
64
+ cookies.forEach(str => {
65
+ const [name, ...values] = str.split('=');
66
+ this.cookie.set(name, values.join('='));
67
+ });
68
+ }
69
+
70
+ public toString() {
71
+ return Array.from(this.cookie.entries())
72
+ .map(([name, value]) => `${name}=${value}`)
73
+ .join('; ');
74
+ }
75
+ }
76
+
77
+ const pollQr = async (key: string) => {
78
+ const r0 = await fetch(`https://passport.bilibili.com/x/passport-login/web/qrcode/poll?qrcode_key=${key}`);
79
+ const { data } = (await r0.json()) as PollQrResp;
80
+
81
+ if (data.code !== 0) {
82
+ return {
83
+ code: data.code,
84
+ message: data.message,
85
+ cookie: '',
86
+ };
87
+ }
88
+
89
+ const cookie = new Cookie(r0.headers.getSetCookie());
90
+ console.log('cookie1', cookie.toString());
91
+ const r1 = await fetch(data.url, { headers: { cookie: cookie.toString() } });
92
+
93
+ cookie.add(r1.headers.getSetCookie());
94
+ console.log('cookie2', cookie.toString());
95
+
96
+ return {
97
+ code: data.code,
98
+ message: data.message,
99
+ cookie: cookie.toString(),
100
+ };
101
+ };
102
+
103
+ const handleStreamClose = (stream: StreamingApi, onClose: () => any) => {
104
+ const close = stream.close.bind(stream);
105
+ stream.close = async () => {
106
+ try {
107
+ await onClose();
108
+ } finally {
109
+ await close();
110
+ }
111
+ };
112
+ };
index.html ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!doctype html>
2
+ <html>
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta
6
+ name="viewport"
7
+ content="initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no, width=device-width"
8
+ />
9
+ <title>哔哩哔哩扫码登录</title>
10
+ </head>
11
+ <body>
12
+ <div id="app"></div>
13
+ <script type="module" src="/src/main.ts"></script>
14
+ </body>
15
+ </html>
package.json ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "bilibili-qr-login",
3
+ "version": "0.0.0",
4
+ "type": "module",
5
+ "license": "MIT",
6
+ "scripts": {
7
+ "dev": "vite",
8
+ "build": "vue-tsc && vite build",
9
+ "preview": "vite preview",
10
+ "lint": "eslint .",
11
+ "lint:fix": "eslint . --fix"
12
+ },
13
+ "devDependencies": {
14
+ "@hono/node-server": "^1.4.0",
15
+ "@types/node": "^20.10.6",
16
+ "@types/nodemon": "^1.19.6",
17
+ "@typescript-eslint/eslint-plugin": "^6.17.0",
18
+ "@typescript-eslint/parser": "^6.17.0",
19
+ "@vitejs/plugin-vue": "^5.0.2",
20
+ "@vue/eslint-config-prettier": "^9.0.0",
21
+ "@vue/eslint-config-typescript": "^12.0.0",
22
+ "eslint": "^8.56.0",
23
+ "eslint-import-resolver-typescript": "^3.6.1",
24
+ "eslint-plugin-import": "^2.29.1",
25
+ "eslint-plugin-prettier": "^5.1.2",
26
+ "eslint-plugin-vue": "^9.19.2",
27
+ "hono": "^3.11.12",
28
+ "less": "^4.2.0",
29
+ "lint-staged": "^15.2.0",
30
+ "nodemon": "^3.0.2",
31
+ "prettier": "^3.1.1",
32
+ "tsx": "^4.7.0",
33
+ "typescript": "^5.3.3",
34
+ "vite": "^5.0.10",
35
+ "vite-plugin-vercel": "^2.0.0",
36
+ "vue": "^3.4.5",
37
+ "vue-tsc": "^1.8.27",
38
+ "yorkie": "^2.0.0"
39
+ },
40
+ "gitHooks": {
41
+ "pre-commit": "lint-staged"
42
+ },
43
+ "lint-staged": {
44
+ "*.{vue,ts,tsx}": [
45
+ "prettier --write",
46
+ "eslint --fix"
47
+ ],
48
+ "*.{css,less}": [
49
+ "prettier --write"
50
+ ]
51
+ }
52
+ }
server/index.ts ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ import { serve } from '@hono/node-server';
2
+ import { Hono } from 'hono';
3
+ import { app as qr } from '../_api/qr';
4
+
5
+ const app = new Hono();
6
+ app.route('/', qr);
7
+
8
+ serve(app, () => {
9
+ console.log('API server start running.');
10
+ });
server/utils.ts ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { createServer } from 'net';
2
+ import nodemon from 'nodemon';
3
+
4
+ const checkPortInUse = (port = 3000) =>
5
+ new Promise<boolean>(resolve => {
6
+ const tester = createServer()
7
+ .once('error', (err: any) => {
8
+ resolve(err.code === 'EADDRINUSE');
9
+ })
10
+ .once('listening', () => tester.once('close', () => resolve(false)).close())
11
+ .listen(port, '0.0.0.0');
12
+ });
13
+
14
+ export const startApiServer = async () => {
15
+ if (await checkPortInUse()) return;
16
+ nodemon({
17
+ script: 'server/index.ts',
18
+ ext: 'ts',
19
+ watch: ['_api'],
20
+ execMap: {
21
+ ts: 'tsx',
22
+ },
23
+ });
24
+ };
src/App.vue ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ <template>
2
+ <div>1111</div>
3
+ </template>
4
+
5
+ <script setup lang="ts"></script>
src/main.ts ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ import { createApp } from 'vue';
2
+ import App from './App.vue';
3
+
4
+ createApp(App).mount('#app');
tsconfig.json ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "lib": ["ES2022", "DOM", "DOM.Iterable"],
6
+ "skipLibCheck": true,
7
+
8
+ /* Bundler mode */
9
+ "moduleResolution": "Node",
10
+ "allowSyntheticDefaultImports": true,
11
+ "useDefineForClassFields": true,
12
+ "resolveJsonModule": true,
13
+ "isolatedModules": true,
14
+ "noEmit": true,
15
+ "jsx": "preserve",
16
+
17
+ /* Linting */
18
+ "strict": true,
19
+ // "noUnusedLocals": true,
20
+ "noUnusedParameters": true,
21
+ "noFallthroughCasesInSwitch": true
22
+ },
23
+ "include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"],
24
+ "references": [{ "path": "./tsconfig.node.json" }]
25
+ }
tsconfig.node.json ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "compilerOptions": {
3
+ "composite": true,
4
+ "skipLibCheck": true,
5
+ "module": "ESNext",
6
+ "moduleResolution": "bundler",
7
+ "allowSyntheticDefaultImports": true
8
+ },
9
+ "include": ["vite.config.ts", "_api/**/*.ts", "server/**/*.ts"]
10
+ }
vite.config.ts ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { defineConfig } from 'vite';
2
+ import vue from '@vitejs/plugin-vue';
3
+ import vercel from 'vite-plugin-vercel';
4
+ import { startApiServer } from './server/utils';
5
+
6
+ // https://vitejs.dev/config/
7
+ export default defineConfig(({ command }) => {
8
+ if (command === 'serve') startApiServer();
9
+ return {
10
+ plugins: [vue(), vercel()],
11
+ server: {
12
+ proxy: {
13
+ '/api': 'http://127.0.0.1:3000',
14
+ },
15
+ },
16
+ };
17
+ });
yarn.lock ADDED
The diff for this file is too large to render. See raw diff