diff --git a/apps/tools.js b/apps/tools.js index 0421607..5d46c3e 100644 --- a/apps/tools.js +++ b/apps/tools.js @@ -12,7 +12,7 @@ import { downloadBFile, getBiliAudio, getDownloadUrl, - getDynamic, + getDynamic, getScanCodeData, getVideoInfo, m4sToMp3, mergeFileToMp4 @@ -50,10 +50,8 @@ import { TIKTOK_INFO, TWITTER_TWEET_INFO, XHS_REQ_LINK, - GENERAL_REQ_LINK, WEIBO_SINGLE_INFO, WEISHI_VIDEO_INFO + GENERAL_REQ_LINK, WEIBO_SINGLE_INFO, WEISHI_VIDEO_INFO, BILI_SCAN_CODE_GENERATE } from "../constants/tools.js"; -import child_process from 'node:child_process' -import { getAudio, getVideo } from "../utils/y2b.js"; import { processTikTokUrl } from "../utils/tiktok.js"; import { getDS } from "../utils/mihoyo.js"; import GeneralLinkAdapter from "../utils/general-link-adapter.js"; @@ -92,6 +90,11 @@ export class tools extends plugin { reg: "(www.tiktok.com)|(vt.tiktok.com)|(vm.tiktok.com)", fnc: "tiktok", }, + { + reg: "^#R插件B站扫码$", + fnc: "biliScan", + permission: 'master', + }, { reg: "(bilibili.com|b23.tv|t.bilibili.com)", fnc: "bili", @@ -339,6 +342,24 @@ export class tools extends plugin { return true; } + async biliScan(e) { + e.reply('R插件开源免责声明:\n您将通过扫码完成获取哔哩哔哩refresh_token以及ck。\n本Bot将不会保存您的登录状态。\n我方仅提供视频解析及相关B站内容服务,若您的账号封禁、被盗等处罚与我方无关。\n害怕风险请勿扫码 ~', { recallMsg: 180 }); + // 图片发送钩子 + const imgSendHook = function (e, path) { + e.reply([segment.image(path), segment.at(e.user_id), '请扫码以完成获取'], { recallMsg: 180 }) + }; + // 发送请求 + const saveCodePath = `${ this.defaultPath }qrcode.png`; + + const { SESSDATA, refresh_token } = await getScanCodeData(saveCodePath, 8, () => imgSendHook(e, saveCodePath)) + + // 更新到配置文件 + config.updateField("tools", "biliSessData", SESSDATA); + e.reply('登录成功!相关信息已保存至配置文件', true) + return true; + } + + // bilibi解析 async bili(e) { await this.limitUserUse(e, () => { diff --git a/config/help.yaml b/config/help.yaml index 1dd9eb8..abdc896 100644 --- a/config/help.yaml +++ b/config/help.yaml @@ -38,6 +38,9 @@ - icon: bilibili title: "bilibili/b23" desc: 哔哩哔哩分享实时下载 + - icon: bqrcode + title: "R插件B站扫码" + desc: R插件B站扫码 - icon: bilimusic title: "bili音乐+链接" desc: 哔哩哔哩音乐分享实时下载 diff --git a/config/version.yaml b/config/version.yaml index 431ea48..486d5a5 100644 --- a/config/version.yaml +++ b/config/version.yaml @@ -1,11 +1,10 @@ - { - version: 1.6.2, + version: 1.6.3, data: [ + 新增B站扫码功能, 新增即刻解析功能, 新增微视解析功能, - 新增小世界解析功能, - 新增贴吧解析功能, 支持锅巴插件,方便查看和修改配置, 添加#R帮助获取插件帮助, 添加#R版本获取插件版本, diff --git a/constants/tools.js b/constants/tools.js index 0bb034f..22bf60a 100644 --- a/constants/tools.js +++ b/constants/tools.js @@ -40,6 +40,20 @@ export const BILI_VIDEO_INFO = "http://api.bilibili.com/x/web-interface/view" */ export const BILI_NAV = "https://api.bilibili.com/x/web-interface/nav" +/** + * 扫码登录的二维码生成 + * https://github.com/SocialSisterYi/bilibili-API-collect/blob/master/docs/login/login_action/QR.md + * @type {string} + */ +export const BILI_SCAN_CODE_GENERATE = "https://passport.bilibili.com/x/passport-login/web/qrcode/generate" + +/** + * 扫码登录检测然后发送令牌数据 + * https://github.com/SocialSisterYi/bilibili-API-collect/blob/master/docs/login/login_action/QR.md + * @type {string} + */ +export const BILI_SCAN_CODE_DETECT = "https://passport.bilibili.com/x/passport-login/web/qrcode/poll?qrcode_key={}"; + /** * 米游社网页端获取文章 * https://github.com/UIGF-org/mihoyo-api-collect/blob/main/hoyolab/article/article.md#%E8%8E%B7%E5%8F%96%E5%AE%8C%E6%95%B4%E6%96%87%E7%AB%A0%E4%BF%A1%E6%81%AF diff --git a/model/index.js b/model/index.js index 32ef893..1c8a88b 100644 --- a/model/index.js +++ b/model/index.js @@ -28,6 +28,26 @@ class RConfig { return this.getYaml(name) } + // 获取指定配置的某个字段 + getField(name, field) { + const config = this.getConfig(name); + return config[field]; + } + + // 更新指定配置的某个字段 + updateField(name, field, value) { + let config = this.getConfig(name); + config[field] = value; // 更新字段值 + this.saveSet(name, config); // 保存更改 + } + + // 删除指定配置的某个字段 + deleteField(name, field) { + let config = this.getConfig(name); + delete config[field]; // 删除指定字段 + this.saveSet(name, config); // 保存更改 + } + /** * 获取配置yaml * @param name 名称 diff --git a/package.json b/package.json index 6758f3b..b34dd01 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,7 @@ "type": "module", "dependencies": { "axios": "^1.3.4", - "tunnel": "^0.0.6" + "tunnel": "^0.0.6", + "qrcode": "^1.5.3" } } diff --git a/resources/img/icon/bqrcode.png b/resources/img/icon/bqrcode.png new file mode 100644 index 0000000..e9078ed Binary files /dev/null and b/resources/img/icon/bqrcode.png differ diff --git a/utils/bilibili.js b/utils/bilibili.js index 4099c75..94944ef 100644 --- a/utils/bilibili.js +++ b/utils/bilibili.js @@ -2,8 +2,21 @@ import fs from "node:fs"; import axios from 'axios' import child_process from 'node:child_process' import util from "util"; -import { BILI_BVID_TO_CID, BILI_DYNAMIC, BILI_PLAY_STREAM, BILI_VIDEO_INFO } from "../constants/tools.js"; +import { + BILI_BVID_TO_CID, + BILI_DYNAMIC, + BILI_PLAY_STREAM, BILI_SCAN_CODE_DETECT, + BILI_SCAN_CODE_GENERATE, + BILI_VIDEO_INFO +} from "../constants/tools.js"; import { mkdirIfNotExists } from "./file.js"; +import qrcode from "qrcode" + +const biliHeaders = { + 'User-Agent': + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36', + referer: 'https://www.bilibili.com', +} /** * 下载单个bili文件 @@ -17,9 +30,7 @@ export async function downloadBFile(url, fullFileName, progressCallback) { .get(url, { responseType: 'stream', headers: { - 'User-Agent': - 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36', - referer: 'https://www.bilibili.com', + ...biliHeaders }, }) .then(({ data, headers }) => { @@ -53,9 +64,7 @@ export async function getDownloadUrl(url) { return axios .get(url, { headers: { - 'User-Agent': - 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36', - referer: 'https://www.bilibili.com', + ...biliHeaders }, }) .then(({ data }) => { @@ -127,9 +136,7 @@ export async function m4sToMp3(m4sUrl, path) { .get(m4sUrl, { responseType: 'stream', headers: { - 'User-Agent': - 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36', - referer: 'https://www.bilibili.com', + ...biliHeaders }, }).then(async res => { // 如果没有目录就创建一个 @@ -231,9 +238,7 @@ export async function getDynamic(dynamicId, SESSDATA) { const dynamicApi = BILI_DYNAMIC.replace("{}", dynamicId); return axios.get(dynamicApi, { headers: { - 'User-Agent': - 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36', - 'referer': 'https://www.bilibili.com', + ...biliHeaders, Cookie: `SESSDATA=${ SESSDATA }` }, }).then(resp => { @@ -254,4 +259,60 @@ export async function getDynamic(dynamicId, SESSDATA) { dynamicDesc } }) +} + +/** + * 扫码 + * @param qrcodeSavePath 【必须】QR保存位置 + * @param detectTime 【可选】检测时间(默认10s检测一次) + * @param hook 【可选】钩子函数,目前只用来人机交互 + * @returns {Promise<{ + * SESSDATA, + * refresh_token + * }>} + */ +export async function getScanCodeData(qrcodeSavePath = 'qrcode.png', detectTime = 10, hook = () => {}) { + try { + const resp = await axios.get(BILI_SCAN_CODE_GENERATE, { ...biliHeaders }); + // 保存扫码的地址、扫码登录秘钥 + const { url: scanUrl, qrcode_key } = resp.data.data; + await qrcode.toFile(qrcodeSavePath, scanUrl); + + let code = 1; + + // 设置最大尝试次数 + let attemptCount = 0; + const maxAttempts = 3; + + let loginResp; + // 检测扫码情况默认 10s 检测一次,并且尝试3次,没扫就拜拜 + while (code !== 0 && attemptCount < maxAttempts) { + // 钩子函数,目前用于发送二维码给用户 + hook(); + loginResp = await axios.get(BILI_SCAN_CODE_DETECT.replace("{}", qrcode_key), { ...biliHeaders }); + code = loginResp.data.data.code; + await new Promise(resolve => setTimeout(resolve, detectTime * 1000)); // Wait for detectTime seconds + } + // 获取刷新令牌 + const { refresh_token } = loginResp.data.data; + + // 获取cookie + const cookies = loginResp.headers['set-cookie']; + const SESSDATA = cookies + .map(cookie => cookie.split(';').find(item => item.trim().startsWith('SESSDATA='))) + .find(item => item !== undefined) + ?.split('=')[1]; + + return { + SESSDATA, + refresh_token + }; + } catch (err) { + logger.error(err); + // 可能需要处理错误或返回一个默认值 + return { + SESSDATA: '', + refresh_token: '' + }; + } } \ No newline at end of file