├── .nvmrc ├── public ├── favicon.ico ├── images │ └── og-media.png ├── robots.txt └── sitemap.xml ├── src ├── assets │ ├── logo.png │ ├── images │ │ └── j9rSZOk.png │ └── style.css ├── constants.js ├── components │ ├── Analysis.vue │ ├── GetComments.vue │ ├── CommentItem.vue │ └── MainArea.vue ├── main.js ├── App.vue └── store.js ├── vercel.json ├── .eslintrc.json ├── .gitignore ├── package.json ├── README.md ├── vite.config.js └── index.html /.nvmrc: -------------------------------------------------------------------------------- 1 | 18 -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/circle-hotaru/bilibili-comment2png/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/circle-hotaru/bilibili-comment2png/HEAD/src/assets/logo.png -------------------------------------------------------------------------------- /public/images/og-media.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/circle-hotaru/bilibili-comment2png/HEAD/public/images/og-media.png -------------------------------------------------------------------------------- /src/assets/images/j9rSZOk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/circle-hotaru/bilibili-comment2png/HEAD/src/assets/images/j9rSZOk.png -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # * 2 | User-agent: * 3 | Allow: / 4 | 5 | # Sitemaps 6 | Sitemap: https://bilibili.circlehotarux.me/sitemap.xml 7 | -------------------------------------------------------------------------------- /vercel.json: -------------------------------------------------------------------------------- 1 | { 2 | "rewrites": [ 3 | { 4 | "source": "/bili.api/:path*", 5 | "destination": "https://api.bilibili.com/:path*" 6 | } 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "@babel/eslint-parser", 3 | "root": true, 4 | "env": { 5 | "node": true, 6 | "browser": true 7 | }, 8 | "extends": ["eslint:recommended", "plugin:vue/essential"] 9 | } 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | 6 | # local env files 7 | .env.local 8 | .env.*.local 9 | 10 | # Log files 11 | npm-debug.log* 12 | yarn-debug.log* 13 | yarn-error.log* 14 | pnpm-debug.log* 15 | 16 | # Editor directories and files 17 | .idea 18 | .vscode 19 | *.suo 20 | *.ntvs* 21 | *.njsproj 22 | *.sln 23 | *.sw? 24 | -------------------------------------------------------------------------------- /src/constants.js: -------------------------------------------------------------------------------- 1 | export const SYSTEM_MESSAGE = 2 | 'You are a professional social media analyst. I will send you the comments from a video, please provide a brief summary and analysis of these comments, including the sentiment tendencies of the comments, as well as extract valuable suggestions from the comments. Please communicate with me in Chinese.' 3 | -------------------------------------------------------------------------------- /src/components/Analysis.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import App from './App.vue' 3 | import { BootstrapVue, IconsPlugin } from 'bootstrap-vue' 4 | import 'normalize.css/normalize.css' 5 | import 'bootstrap/dist/css/bootstrap.css' 6 | import 'bootstrap-vue/dist/bootstrap-vue.css' 7 | import './assets/style.css' 8 | 9 | Vue.use(BootstrapVue) 10 | Vue.use(IconsPlugin) 11 | 12 | Vue.config.productionTip = false 13 | 14 | new Vue({ 15 | render: h => h(App), 16 | }).$mount('#app') 17 | -------------------------------------------------------------------------------- /src/assets/style.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --comment-general-background: #f9f9f9; 3 | --comment-text-primary: #222; 4 | --comment-text-secondary: #aaa; 5 | --comment-dark-background: #181818; 6 | --comment-dark-text-primary: #fff; 7 | } 8 | 9 | body { 10 | font-family: -apple-system, system-ui, BlinkMacSystemFont, sans-serif; 11 | font-size: 0.9rem; 12 | font-weight: 400; 13 | line-height: 1.6; 14 | color: #212529; 15 | text-align: left; 16 | background-color: #f2f2f2; 17 | } 18 | -------------------------------------------------------------------------------- /public/sitemap.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | https://bilibili.circlehotarux.me2022-08-231 4 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bilibili-comment2png", 3 | "version": "0.1.3", 4 | "private": true, 5 | "engines": { 6 | "node": ">=18" 7 | }, 8 | "scripts": { 9 | "dev": "vite", 10 | "build": "vite build", 11 | "serve": "vite preview" 12 | }, 13 | "dependencies": { 14 | "axios": "^0.28.0", 15 | "bootstrap": "4.5.3", 16 | "bootstrap-vue": "2.18.1", 17 | "file-saver": "2.0.2", 18 | "html2canvas": "1.0.0-rc.7", 19 | "jszip": "3.5.0", 20 | "normalize.css": "8.0.1", 21 | "vue": "^2.6.11" 22 | }, 23 | "devDependencies": { 24 | "eslint": "^8.22.0", 25 | "eslint-plugin-vue": "^8.7.1", 26 | "vite": "^2.7.2", 27 | "vite-plugin-env-compatible": "^1.1.1", 28 | "vite-plugin-html": "3.2.0", 29 | "vite-plugin-vue2": "^1.9.0" 30 | }, 31 | "browserslist": [ 32 | "> 1%", 33 | "last 2 versions", 34 | "not dead" 35 | ] 36 | } 37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Bilibili 评论转图片神器 2 | 3 | 供 Bilibili UP 主与观众互动,将 Bilibili 视频评论区的评论转换成 PNG,应用场景如:呈现观众评论、评论互动、频道 Q&A,将会陆续完善其他功能,敬请期待! 4 | 5 | 目前功能特色: 6 | 7 | - AI 总结评论 8 | - 无评论获取数量上限 9 | - 自由修改评论外观 10 | - ZIP 压缩包打包下载 11 | - 评论内容为文件名 12 | 13 | ## Demo 14 | 15 | https://bilibili.incircle.dev 16 | 17 | ## Technology Stack 18 | 19 | - Vue 20 | - Bootstrap 21 | - html2canvas 22 | - JSZip 23 | 24 | ## Installation & Usage 25 | 26 | 1. Clone or Download the repository (Depending on whether you are using HTTPS or SSH) 27 | 28 | ``` 29 | git clone https://github.com/circle-hotaru/bilibili-comment2png.git 30 | cd bilibili-comment2png 31 | ``` 32 | 33 | 2. Install dependencies 34 | 35 | ``` 36 | npm i 37 | ``` 38 | 39 | 3. Start the application 40 | 41 | ``` 42 | npm run serve 43 | ``` 44 | 45 | After the application starts visit http://localhost:8080 to view it in the browser. 46 | 47 | ## 🙏 感谢 48 | 49 | https://github.com/SocialSisterYi/bilibili-API-collect 50 | -------------------------------------------------------------------------------- /vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import path from 'path' 3 | import { createVuePlugin } from 'vite-plugin-vue2' 4 | import envCompatible from 'vite-plugin-env-compatible' 5 | import { createHtmlPlugin } from 'vite-plugin-html' 6 | 7 | // https://vitejs.dev/config/ 8 | export default defineConfig({ 9 | resolve: { 10 | alias: [ 11 | { 12 | find: /^~/, 13 | replacement: '', 14 | }, 15 | { 16 | find: '@', 17 | replacement: path.resolve(__dirname, './src'), //eslint-disable-line 18 | }, 19 | ], 20 | extensions: ['.mjs', '.js', '.ts', '.jsx', '.tsx', '.json', '.vue'], 21 | }, 22 | plugins: [ 23 | createVuePlugin({ jsx: true }), 24 | envCompatible(), 25 | createHtmlPlugin({ 26 | inject: { 27 | data: { 28 | title: 'bilibili-comment2png', 29 | }, 30 | }, 31 | }), 32 | ], 33 | base: './', 34 | build: {}, 35 | server: { 36 | proxy: { 37 | '/bili.api': { 38 | target: 'https://api.bilibili.com', 39 | changeOrigin: true, 40 | rewrite: (path) => path.replace(/^\/bili.api/, ''), 41 | }, 42 | }, 43 | }, 44 | }) 45 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 41 | 42 | 56 | 57 | 66 | -------------------------------------------------------------------------------- /src/components/GetComments.vue: -------------------------------------------------------------------------------- 1 | 46 | 47 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 16 | 17 | 18 | 19 | 23 | 24 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 38 | 42 | 43 | Bilibili 评论转图片神器 44 | 45 | 46 | 47 | 53 |
54 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /src/store.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | import { SYSTEM_MESSAGE } from './constants' 3 | 4 | export const store = { 5 | state: { 6 | AVId: '', 7 | BVId: '', 8 | title: '', 9 | fetching: false, 10 | pending: false, 11 | comments: [], 12 | // 排序模式 0按时间,2按热度 13 | mode: 2, 14 | // 调整评论样式圆角 15 | borderRadius: 10, 16 | displayTime: true, 17 | darkTheme: false, 18 | // 评论数 19 | count: 0, 20 | // 当前页 21 | currentPage: 1, 22 | // 页面尺寸 23 | perPage: 20, 24 | // 下载评论开关 25 | download: false, 26 | fetchingAnalysis: false, 27 | analysis: undefined, 28 | }, 29 | getId() { 30 | this.state.fetching = true 31 | 32 | const rawBVID = this.state.BVId 33 | const regex = /BV[0-9A-Z]+/i 34 | const matches = rawBVID.match(regex) 35 | const realBVID = matches ? matches[0].replace(/^BV/, '') : rawBVID 36 | 37 | const url = `/bili.api/x/web-interface/view?bvid=${realBVID}` 38 | axios 39 | .get(url) 40 | .then((response) => { 41 | const { data } = response 42 | this.state.AVId = data.data.aid 43 | this.state.title = data.data.title 44 | this.getComments() 45 | }) 46 | .catch((error) => console.error(error)) 47 | }, 48 | getComments() { 49 | const url = `/bili.api/x/v2/reply?type=1&oid=${this.state.AVId}&sort=${this.state.mode}&pn=${this.state.currentPage}&ps=${this.state.perPage}&nohot=1` 50 | axios 51 | .get(url) 52 | .then((response) => { 53 | const { data } = response 54 | if (data.code === 0) { 55 | this.state.comments = data.data.replies 56 | this.state.count = data.data.page.count 57 | this.state.fetching = false 58 | } else { 59 | this.state.fetching = false 60 | console.log(data.message) 61 | } 62 | }) 63 | .catch((error) => { 64 | this.state.fetching = false 65 | console.error(error) 66 | }) 67 | }, 68 | downloadComments() { 69 | this.state.download = true 70 | }, 71 | getAnalysis() { 72 | this.state.fetchingAnalysis = true 73 | const comments = this.state.comments 74 | .map((comment) => comment.content.message) 75 | .join(';') 76 | const apiURL = `${process.env.VUE_APP_OPENAI_API_URL}/v1/chat/completions` 77 | const messages = [ 78 | { 79 | role: 'system', 80 | content: SYSTEM_MESSAGE, 81 | }, 82 | { 83 | role: 'user', 84 | content: comments, 85 | }, 86 | ] 87 | axios 88 | .post( 89 | apiURL, 90 | { 91 | model: 'gpt-4o-2024-05-13', 92 | messages: messages, 93 | }, 94 | { 95 | headers: { 96 | 'Content-Type': 'application/json', 97 | Authorization: `Bearer ${process.env.VUE_APP_OPENAI_API_KEY}`, 98 | }, 99 | } 100 | ) 101 | .then(({ data }) => { 102 | this.state.fetchingAnalysis = false 103 | this.state.analysis = data.choices[0].message.content 104 | }) 105 | .catch((error) => { 106 | this.state.fetchingAnalysis = false 107 | console.error(error) 108 | }) 109 | }, 110 | } 111 | -------------------------------------------------------------------------------- /src/components/CommentItem.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 124 | 125 | 178 | -------------------------------------------------------------------------------- /src/components/MainArea.vue: -------------------------------------------------------------------------------- 1 | 89 | 90 | 177 | 178 | 179 | --------------------------------------------------------------------------------