├── .babelrc.json ├── .github ├── ISSUE_TEMPLATE │ └── bug_report.md └── workflows │ └── node.js.yml ├── .gitignore ├── LICENSE ├── README.md ├── build.js ├── httpserver.bat ├── img ├── AliPay.png └── WeChat.png ├── models └── treasurebox_captcha │ ├── group1-shard1of1.bin │ └── model.json ├── package-lock.json ├── package.json ├── src ├── html │ ├── eula.html │ └── notice.html ├── main.js ├── meta.js └── modules │ ├── avoiddetection.js │ ├── dailyreward.js │ ├── exchange.js │ ├── gift.js │ ├── heartbeat.js │ ├── sign.js │ ├── treasurebox.js │ └── treasurebox │ └── worker.js ├── update-log-old.md └── update-log.md /.babelrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "@babel/preset-env", 5 | { 6 | "targets": { 7 | "firefox": "70", 8 | "chrome": "78" 9 | }, 10 | "useBuiltIns": "usage", 11 | "corejs": 3 12 | } 13 | ] 14 | ] 15 | } -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | 5 | --- 6 | 7 | **描述bug** 8 | 简要描述所遇到的bug 9 | 10 | **重现bug** 11 | 说明您在进行了怎样的操作后出现了bug 12 | 1. xxx 13 | 2. xxx 14 | 15 | **预期行为** 16 | 简要描述进行以上操作后预期的脚本行为 17 | 18 | **截图** 19 | 如果可以,请提供有关截图 20 | 21 | **使用环境:** 22 | - 浏览器: 如Chrome 23 | - 浏览器版本: 如Chromium 67.0.3396.99 24 | - 脚本的版本: 如1.0.0 25 | - 网络情况(网速): 好/一般/坏 26 | - 其他浏览器插件/脚本: 在此写上您认为可能会对本脚本产生影响的插件/脚本 27 | - bug出现时间: (精确到分钟) 28 | 29 | **其他** 30 | 若您有其他想要补充的内容,请在此说明 31 | -------------------------------------------------------------------------------- /.github/workflows/node.js.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions 3 | 4 | name: Auto Build 5 | 6 | on: 7 | push: 8 | branches: [ master ] 9 | 10 | jobs: 11 | build: 12 | 13 | runs-on: ubuntu-latest 14 | 15 | strategy: 16 | matrix: 17 | node-version: [12.x] 18 | 19 | steps: 20 | - uses: actions/checkout@v2 21 | with: 22 | ref: ${{ github.head_ref }} 23 | 24 | - name: Use Node.js ${{ matrix.node-version }} 25 | uses: actions/setup-node@v1 26 | with: 27 | node-version: ${{ matrix.node-version }} 28 | 29 | - name: Setup 30 | run: npm ci 31 | 32 | - name: Run build.js 33 | run: npm run build --if-present 34 | 35 | - name: Git Auto Commit 36 | run: | 37 | cd dist 38 | git init 39 | git config user.name "GitHub Actions" 40 | git config user.email "actions@github.com" 41 | git remote add origin "https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/${GITHUB_REPOSITORY}.git" 42 | git checkout --orphan dist 43 | git add --all 44 | git commit --allow-empty -m "Github Actions Auto Commit" --no-verify --author="${GITHUB_ACTOR} <${GITHUB_ACTOR}@users.noreply.github.com>" 45 | git push origin dist --force 46 | 47 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vs* 2 | !.circleci 3 | node_modules 4 | captacha 5 | old* 6 | dist/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018-2020 SeaLoong 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Bilibili直播间挂机助手3 2 | 3 | + [Github项目地址](https://github.com/SeaLoong/BLRHH) 4 | 5 | ![Tampermonkey4.10](https://img.shields.io/badge/Tampermonkey4.10-pass-green.svg?longCache=true) ![Chromium80](https://img.shields.io/badge/Chromium80-pass-green.svg?longCache=true) ![Firefox72](https://img.shields.io/badge/Firefox72-pass-green.svg?longCache=true) [![Issues](https://img.shields.io/github/issues/SeaLoong/BLRHH.svg)](https://github.com/SeaLoong/BLRHH/issues) 6 | 7 | + 该脚本为 Tampermonkey 脚本,只在该环境下测试通过,使用其它脚本插件来加载此脚本的,不保证能正常运行。 8 | + 由于使用了比较新的语言特性,内核版本为 Chromium80 或 Firefox72 以下的版本无法使用。 9 | + 在 Tampermonkey 脚本设置中需要将此脚本的设置 “仅在顶层页面(框架)运行” 设置为否(默认为否)才使脚本在特殊直播间运行。 10 | 11 | ---------------------------------- 12 | 13 | ## 如何使用 14 | 15 | 1. 安装 **油猴插件**(Tampermonkey/Greasemonkey/Violentmonkey等),可以参考 [GreasyFork](https://greasyfork.org/zh-CN) 首页说明。 16 | 2. 安装插件,可从以下两种方式选择安装: 17 | + 点击 [安装脚本(github)](https://raw.githubusercontent.com/SeaLoong/BLRHH/dist/installer.github.user.js) 或 [安装脚本(jsdelivr)](https://cdn.jsdelivr.net/gh/SeaLoong/BLRHH@dist/installer.jsdelivr.user.js) ,脚本管理器会弹出安装脚本页面,即可从Github更新脚本。 18 | + 如果没有出现脚本管理器的安装页面,则需要将代码复制下来。然后从脚本管理器中新建一个脚本,将自动生成的内容删除,粘贴复制的代码并保存。 19 | + 进入 [Bilibili直播间挂机助手](https://greasyfork.org/zh-CN/scripts/37095-bilibili%E7%9B%B4%E6%92%AD%E9%97%B4%E6%8C%82%E6%9C%BA%E5%8A%A9%E6%89%8B) 页面,点击 **安装此脚本** 即可从 GreasyFork 更新脚本。 20 | 3. 进入直播间页面,右上方多出如 "弹幕" "日志" "设置" 等选项,选择自己需要的功能,保存即可。 21 | 22 | ---------------------------------- 23 | 24 | ## 功能 25 | 26 | > 其余功能正在重新实现中 27 | 28 | + 签到 29 | + 直播签到 30 | + 应援团签到 31 | + 宝箱 32 | + 银瓜子宝箱领取(现已无效) 33 | + 金宝箱抽奖(即实物抽奖)参加 34 | + 心跳 35 | + 移动端 36 | + 兑换 37 | + 银瓜子兑换硬币 38 | + 硬币兑换银瓜子 39 | + 每日奖励(主站) 40 | + 签到 41 | + 观看 42 | + 投币 43 | + 分享 44 | + 避免挂机检测 45 | 46 | ---------------------------------- 47 | 48 | ## 已知问题 49 | 50 | + 部分功能与别的浏览器插件/脚本不兼容,如 51 | + pakku – 哔哩哔哩弹幕过滤器\[Chrome、Firefox 扩展\] 与 银瓜子宝箱领取(现已无效) 不兼容 52 | 53 | ---------------------------------- 54 | 55 | ## 捐赠作者 56 | 57 | + 支付宝 => ![支付宝二维码](https://cdn.jsdelivr.net/gh/SeaLoong/BLRHH/img/AliPay.png) 微信 => ![微信二维码](https://cdn.jsdelivr.net/gh/SeaLoong/BLRHH/img/WeChat.png) 58 | 59 | ---------------------------------- 60 | 61 | ## 开源许可证 62 | 63 | [![MIT License](https://img.shields.io/badge/License-MIT-green.svg?longCache=true)](https://github.com/SeaLoong/BLRHH/blob/master/LICENSE) 64 | 65 | + 本项目中使用到的库 66 | + [BLUL](https://github.com/SeaLoong/BLUL) - [![MIT License](https://img.shields.io/badge/License-MIT-green.svg?longCache=true)](https://github.com/SeaLoong/BLUL/blob/master/LICENSE) 67 | + [tfjs](https://github.com/tensorflow/tfjs) - [![Apache-2.0 License](https://img.shields.io/badge/License-Apache--2.0-blue.svg?longCache=true)](https://github.com/tensorflow/tfjs/blob/master/LICENSE) 68 | 69 | ---------------------------------- 70 | 71 | ## 更新日志 72 | 73 | + 3.1.11 (2020-01-17) 74 | + 修复给满级勋章对应的应援团签到时总跳出“应援失败”的问题。 75 | 76 | [完整日志](https://cdn.jsdelivr.net/gh/SeaLoong/BLRHH/update-log.md) 77 | 78 | ---------------------------------- 79 | 80 | ## 鸣谢 81 | 82 | + [Alexander Xia](https://github.com/xfl03) 83 | + [Jokin](https://github.com/jokin1999) 84 | + [Misha](https://github.com/Mishasama) 85 | 86 | 以及所有提出过建议和意见的用户 87 | -------------------------------------------------------------------------------- /build.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const http = require('http'); 3 | const https = require('https'); 4 | 5 | const fileCache = new Map(); 6 | function readFile (path) { 7 | if (fileCache.has(path)) return fileCache.get(path); 8 | return new Promise((resolve, reject) => { 9 | if (path.startsWith('http')) { 10 | let data = ''; 11 | const callback = res => { 12 | res.on('data', chunk => { 13 | data += chunk; 14 | }); 15 | res.on('end', () => { 16 | data = data.toString(); 17 | fileCache.set(path, data); 18 | if (res.complete) resolve(data); 19 | else reject(data); 20 | }); 21 | }; 22 | if (path.startsWith('https')) https.get(path, callback); 23 | else http.get(path, callback); 24 | } else { 25 | try { 26 | const data = fs.readFileSync('./src/' + path).toString(); 27 | fileCache.set(path, data); 28 | resolve(data); 29 | } catch (e) { 30 | reject(e); 31 | } 32 | } 33 | }); 34 | } 35 | 36 | function resolvePath (path, dir) { 37 | if (path.startsWith('http://') || path.startsWith('https://') || path.startsWith('file://')) return path; 38 | if (path.startsWith('/')) path = dir + path; 39 | else path = dir + '/' + path; 40 | return new URL(path).toString(); 41 | } 42 | 43 | function prettyMeta (metas) { 44 | const list = []; 45 | let maxLen = 0; 46 | for (const line of metas) { 47 | const its = line.split(' '); 48 | if (its.length < 2) continue; 49 | list.push(its); 50 | maxLen = Math.max(maxLen, its[1].length + 1); 51 | } 52 | const ret = []; 53 | for (const its of list) { 54 | let s = its[0] + ' ' + its[1]; 55 | if (its.length > 2) { 56 | s += ' '.repeat(maxLen - its[1].length); 57 | for (let i = 2; i < its.length; i++) { 58 | s += its[i]; 59 | if (i + 1 === its.length) break; 60 | s += ' '; 61 | } 62 | } 63 | ret.push(s); 64 | } 65 | return ret; 66 | } 67 | 68 | function wrap (metas, notMeta) { 69 | return '// ==UserScript==\n' + prettyMeta(metas).join('\n') + '\n// ==/UserScript==\n\n' + notMeta; 70 | } 71 | 72 | const multipleKeySet = new Set(['@match', '@exclude-match', '@include', '@exclude', '@require', '@resource', '@connect', '@grant', '@compatible', '@incompatible']); 73 | 74 | function mergeMeta (metasArr) { 75 | const keyMap = new Map(); 76 | for (let i = metasArr.length - 1; i >= 0; i--) { 77 | for (const line of metasArr[i]) { 78 | const its = line.split(' '); 79 | if (its.length < 2) continue; 80 | const tag = its[1]; 81 | if (multipleKeySet.has(tag)) { 82 | if (!keyMap.has(tag)) keyMap.set(tag, new Set()); 83 | keyMap.get(tag).add(line); 84 | } else if (!keyMap.has(tag)) { 85 | keyMap.set(tag, line); 86 | } 87 | } 88 | } 89 | const ret = []; 90 | for (const v of keyMap.values()) { 91 | if (v instanceof Set) { 92 | for (const it of v) { 93 | ret.push(it); 94 | } 95 | } else { 96 | ret.push(v); 97 | } 98 | } 99 | return ret; 100 | } 101 | 102 | const userScriptRegExp = /\/\/\s*==UserScript==\s*([\s\S]*?)\/\/\s*==\/UserScript==/; 103 | const processingMap = new Map(); 104 | async function processMeta (path, replaceUrlFn, onlyMeta = false) { 105 | console.log('processMeta', path, onlyMeta); 106 | if (!processingMap.has(replaceUrlFn)) processingMap.set(replaceUrlFn, new Set()); 107 | const pathSet = processingMap.get(replaceUrlFn); 108 | if (pathSet.has(path)) return [[], '']; 109 | pathSet.add(path); 110 | const data = await readFile(replaceUrlFn instanceof Function ? replaceUrlFn(path, false) : path); 111 | const r = userScriptRegExp.exec(data); 112 | if (!r) return [[], data]; 113 | const notMeta = data.slice(r.index + r[0].length); 114 | const meta = r[1].replace(/[\r\n]+/g, '\n').replace(/ +/g, ' '); 115 | const overrideMetas = []; 116 | const metas = []; 117 | const notMetas = []; 118 | for (let line of meta.split('\n')) { 119 | const its = line.split(' '); 120 | if (its.length < 2) continue; 121 | const tag = its[1]; 122 | switch (tag) { 123 | case '@require': 124 | { 125 | if (its.length < 3) continue; 126 | const url = its[2]; 127 | const m = await processMeta(url, replaceUrlFn, onlyMeta); 128 | overrideMetas.push(m[0]); 129 | if (onlyMeta) { 130 | if (replaceUrlFn instanceof Function) { 131 | line = line.replace(url, replaceUrlFn(url)); 132 | } 133 | } else { 134 | notMetas.push(m[1]); 135 | continue; 136 | } 137 | break; 138 | } 139 | case '@resource': 140 | { 141 | if (its.length < 4) continue; 142 | const url = its[3]; 143 | if (replaceUrlFn instanceof Function) { 144 | line = line.replace(url, replaceUrlFn(url)); 145 | } 146 | break; 147 | } 148 | default: 149 | if (!tag.endsWith('URL')) break; 150 | // eslint-disable-line no-fallthrough 151 | case '@homepage': 152 | case '@website': 153 | case '@source': 154 | case '@icon': 155 | case '@defaulticon': 156 | case '@icon64': 157 | { 158 | if (its.length < 3) continue; 159 | const url = its[2]; 160 | if (replaceUrlFn instanceof Function) { 161 | line = line.replace(url, replaceUrlFn(url)); 162 | } 163 | break; 164 | } 165 | } 166 | if (line) metas.push(line); 167 | } 168 | overrideMetas.push(metas); 169 | notMetas.push(notMeta); 170 | pathSet.delete(path); 171 | return [mergeMeta(overrideMetas), notMetas.join('\n')]; 172 | } 173 | 174 | function createReplaceFn (dir, name) { 175 | return (url, res = true) => { 176 | url = url.replace('{replace}', name); 177 | if (res) return resolvePath(url, dir); 178 | return url; 179 | }; 180 | } 181 | 182 | function copy (src, dst) { 183 | console.log('Copy from', src, 'to', dst); 184 | if (fs.statSync(src).isDirectory()) { 185 | if (!fs.existsSync(dst))fs.mkdirSync(dst); 186 | fs.readdirSync(src).forEach(path => copy(src + '/' + path, dst + '/' + path)); 187 | } else { 188 | fs.copyFileSync(src, dst); 189 | } 190 | } 191 | 192 | (async function () { 193 | copy('./src', './dist'); 194 | 195 | const replaceGithub = createReplaceFn('https://raw.githubusercontent.com/SeaLoong/BLRHH/dist', 'github'); 196 | let dist = await processMeta('./meta.js', replaceGithub); 197 | fs.writeFileSync('./dist/installer.github.user.js', wrap(dist[0], dist[1])); 198 | 199 | dist = await processMeta('./meta.js', replaceGithub, true); 200 | fs.writeFileSync('./dist/meta.github.js', wrap(dist[0], dist[1])); 201 | 202 | const replaceJsdelivr = createReplaceFn('https://cdn.jsdelivr.net/gh/SeaLoong/BLRHH@dist', 'jsdelivr'); 203 | dist = await processMeta('./meta.js', replaceJsdelivr); 204 | fs.writeFileSync('./dist/installer.jsdelivr.user.js', wrap(dist[0], dist[1])); 205 | 206 | dist = await processMeta('./meta.js', replaceJsdelivr, true); 207 | fs.writeFileSync('./dist/meta.jsdelivr.js', wrap(dist[0], dist[1])); 208 | })(); 209 | -------------------------------------------------------------------------------- /httpserver.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | hs --cors 3 | pause 4 | -------------------------------------------------------------------------------- /img/AliPay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SeaLoong/BLRHH/eafd2e3163c30ad68cdddd6854778042bf4f9dd4/img/AliPay.png -------------------------------------------------------------------------------- /img/WeChat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SeaLoong/BLRHH/eafd2e3163c30ad68cdddd6854778042bf4f9dd4/img/WeChat.png -------------------------------------------------------------------------------- /models/treasurebox_captcha/group1-shard1of1.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SeaLoong/BLRHH/eafd2e3163c30ad68cdddd6854778042bf4f9dd4/models/treasurebox_captcha/group1-shard1of1.bin -------------------------------------------------------------------------------- /models/treasurebox_captcha/model.json: -------------------------------------------------------------------------------- 1 | {"format": "layers-model", "generatedBy": "keras v2.3.0-tf", "convertedBy": "TensorFlow.js Converter v2.0.1.post1", "modelTopology": {"keras_version": "2.3.0-tf", "backend": "tensorflow", "model_config": {"class_name": "Sequential", "config": {"name": "sequential", "layers": [{"class_name": "Conv2D", "config": {"name": "conv2d", "trainable": true, "batch_input_shape": [null, 40, 120, 1], "dtype": "float32", "filters": 64, "kernel_size": [3, 3], "strides": [1, 1], "padding": "same", "data_format": "channels_last", "dilation_rate": [1, 1], "activation": "relu", "use_bias": true, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}}, {"class_name": "MaxPooling2D", "config": {"name": "max_pooling2d", "trainable": true, "dtype": "float32", "pool_size": [2, 2], "padding": "valid", "strides": [2, 2], "data_format": "channels_last"}}, {"class_name": "Conv2D", "config": {"name": "conv2d_1", "trainable": true, "dtype": "float32", "filters": 64, "kernel_size": [3, 3], "strides": [1, 1], "padding": "same", "data_format": "channels_last", "dilation_rate": [1, 1], "activation": "relu", "use_bias": true, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}}, {"class_name": "MaxPooling2D", "config": {"name": "max_pooling2d_1", "trainable": true, "dtype": "float32", "pool_size": [2, 2], "padding": "valid", "strides": [2, 2], "data_format": "channels_last"}}, {"class_name": "Conv2D", "config": {"name": "conv2d_2", "trainable": true, "dtype": "float32", "filters": 64, "kernel_size": [3, 3], "strides": [1, 1], "padding": "same", "data_format": "channels_last", "dilation_rate": [1, 1], "activation": "relu", "use_bias": true, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}}, {"class_name": "MaxPooling2D", "config": {"name": "max_pooling2d_2", "trainable": true, "dtype": "float32", "pool_size": [2, 2], "padding": "valid", "strides": [2, 2], "data_format": "channels_last"}}, {"class_name": "Flatten", "config": {"name": "flatten", "trainable": true, "dtype": "float32", "data_format": "channels_last"}}, {"class_name": "Dense", "config": {"name": "dense", "trainable": true, "dtype": "float32", "units": 256, "activation": "relu", "use_bias": true, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}}, {"class_name": "Dropout", "config": {"name": "dropout", "trainable": true, "dtype": "float32", "rate": 0.8, "noise_shape": null, "seed": null}}, {"class_name": "Dense", "config": {"name": "dense_1", "trainable": true, "dtype": "float32", "units": 512, "activation": "relu", "use_bias": true, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}}, {"class_name": "Dropout", "config": {"name": "dropout_1", "trainable": true, "dtype": "float32", "rate": 0.8, "noise_shape": null, "seed": null}}, {"class_name": "Dense", "config": {"name": "dense_2", "trainable": true, "dtype": "float32", "units": 48, "activation": "linear", "use_bias": true, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}}], "build_input_shape": [null, 40, 120, 1]}}, "training_config": {"loss": {"class_name": "BinaryCrossentropy", "config": {"reduction": "auto", "name": "binary_crossentropy", "from_logits": true, "label_smoothing": 0}}, "metrics": [{"class_name": "BinaryCrossentropy", "config": {"name": "binary_crossentropy", "dtype": "float32", "from_logits": true, "label_smoothing": 0}}], "loss_weights": null, "sample_weight_mode": null, "weighted_metrics": null, "optimizer_config": {"class_name": "Adam", "config": {"name": "Adam", "learning_rate": 0.0010000000474974513, "decay": 0.0, "beta_1": 0.8999999761581421, "beta_2": 0.9990000128746033, "epsilon": 1e-07, "amsgrad": false}}}}, "weightsManifest": [{"paths": ["group1-shard1of1.bin"], "weights": [{"name": "conv2d/kernel", "shape": [3, 3, 1, 64], "dtype": "float32", "quantization": {"dtype": "float16", "original_dtype": "float32"}}, {"name": "conv2d/bias", "shape": [64], "dtype": "float32", "quantization": {"dtype": "float16", "original_dtype": "float32"}}, {"name": "conv2d_1/kernel", "shape": [3, 3, 64, 64], "dtype": "float32", "quantization": {"dtype": "float16", "original_dtype": "float32"}}, {"name": "conv2d_1/bias", "shape": [64], "dtype": "float32", "quantization": {"dtype": "float16", "original_dtype": "float32"}}, {"name": "conv2d_2/kernel", "shape": [3, 3, 64, 64], "dtype": "float32", "quantization": {"dtype": "float16", "original_dtype": "float32"}}, {"name": "conv2d_2/bias", "shape": [64], "dtype": "float32", "quantization": {"dtype": "float16", "original_dtype": "float32"}}, {"name": "dense/kernel", "shape": [4800, 256], "dtype": "float32", "quantization": {"dtype": "float16", "original_dtype": "float32"}}, {"name": "dense/bias", "shape": [256], "dtype": "float32", "quantization": {"dtype": "float16", "original_dtype": "float32"}}, {"name": "dense_1/kernel", "shape": [256, 512], "dtype": "float32", "quantization": {"dtype": "float16", "original_dtype": "float32"}}, {"name": "dense_1/bias", "shape": [512], "dtype": "float32", "quantization": {"dtype": "float16", "original_dtype": "float32"}}, {"name": "dense_2/kernel", "shape": [512, 48], "dtype": "float32", "quantization": {"dtype": "float16", "original_dtype": "float32"}}, {"name": "dense_2/bias", "shape": [48], "dtype": "float32", "quantization": {"dtype": "float16", "original_dtype": "float32"}}]}]} -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "blrhh", 3 | "scripts": { 4 | "upload": "node upload.js", 5 | "babelBuild": "npx babel src --out-dir dist", 6 | "build": "node build.js" 7 | }, 8 | "dependencies": { 9 | "qcloud-cos-upload": "^1.3.0", 10 | "request": "^2.88.0" 11 | }, 12 | "devDependencies": { 13 | "@babel/cli": "^7.8.4", 14 | "@babel/core": "^7.9.0", 15 | "@babel/preset-env": "^7.9.0", 16 | "@tensorflow/tfjs": "^1.5.2", 17 | "@tensorflow/tfjs-node": "^1.5.2", 18 | "@tensorflow/tfjs-vis": "^1.4.3", 19 | "babel-eslint": "^10.1.0", 20 | "canvas": "^2.6.1", 21 | "eslint": "^6.8.0", 22 | "semistandard": "^14.2.0" 23 | }, 24 | "semistandard": { 25 | "parser": "babel-eslint", 26 | "env": { 27 | "browser": true, 28 | "es2020": true, 29 | "worker": true, 30 | "jquery": true, 31 | "greasemonkey": true 32 | }, 33 | "ignore": [ 34 | "/dist/*" 35 | ], 36 | "global": [ 37 | "$" 38 | ] 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/html/eula.html: -------------------------------------------------------------------------------- 1 | [v1] 2020/07/05 2 |
3 |

请仔细阅读本最终用户许可协议。

4 |

为了保护您的信息和本开源项目的维护热情,项目开发者(以下简称“我们”)拟定了本最终用户许可协议(“EULA”)用于规范您对本程序的使用。本 EULA 包含用户使用条款和我们的隐私政策。

5 |

如果您使用本程序,即表示您同意遵守本 EULA 。如果您不愿或不能同意这些规定,则不得使用本程序。本程序中可能含有部分功能需要第三方提供支持,我们和第三方提供者有权停用您的有关功能。我们保留随时变更、修改、新增或删除本 EULA 条款的权利。

6 |

由于本项目为开源项目,您可以在遵守 MIT 开源协议的情况下对本程序的源代码进行修改或在其它项目中引用。若您对本程序的源代码进行了修改,本 EULA 将自行终止生效,并且我们不对本程序作任何保证。

7 |
8 |

与本程序相关的其他公告和通知:

9 |
    10 |
  1. 关于违规使用软件在直播间抢辣条的处罚公告
  2. 11 |
12 |
13 |

用户使用条款

14 |

您应当遵守下列条款:

15 |
    16 |
  1. 使用本程序而导致B站对您进行处罚的责任由您承担,我们对此不负任何责任。

  2. 17 |
  3. 仔细阅读使用说明和帮助提示,在已有的说明和提示不能解决问题时再寻求我们。

  4. 18 |
  5. 不得出售本程序以及程序中的任何源代码,或以其他方式使他人付费使用本程序。

  6. 19 |
  7. 不得滥用需要第三方提供支持的功能,不得对第三方提供的服务进行盗用和攻击。

  8. 20 |
  9. 不得在本项目中贡献任何形式的恶意代码、未经允许的推广代码、未经允许的统计代码、无效代码和其他违反法律法规的代码。

  10. 21 |
22 |
23 |

我们的隐私政策

24 |

在程序使用过程中,我们可能会收集和使用您的用户信息。

25 |

我们收集的信息包含:

26 |
    27 |
  1. 浏览器标识和运行环境信息。

  2. 28 |
  3. 本程序运行时所在页面信息。

  4. 29 |
  5. 使用本程序时用户自行填写的信息。

  6. 30 |
31 |

我们怎样使用您的信息:

32 |
    33 |
  1. 用于对B站API的请求。

  2. 34 |
  3. 用于使用需要第三方提供支持的功能。

  4. 35 |
  5. 用于数据统计。

  6. 36 |
37 | 38 | 39 | -------------------------------------------------------------------------------- /src/html/notice.html: -------------------------------------------------------------------------------- 1 |

版本更新说明

2 | 10 |

其他说明

11 |
    12 |
  1. 银瓜子宝箱下线公告
  2. 13 |
-------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | /* global BLUL */ 2 | 'use strict'; 3 | 4 | (async () => { 5 | const EULA = await GM.getResourceText('EULA'); 6 | const NOTICE = await GM.getResourceText('NOTICE'); 7 | BLUL.NAME = 'BLRHH'; 8 | const result = await BLUL.run({ debug: await GM.getValue('debug'), slient: false, unique: true, login: true, EULA: EULA, EULA_VERSION: EULA.match(/\[v(.+?)\]/)[1], NOTICE: NOTICE }); 9 | switch (result) { 10 | case 0: 11 | break; 12 | case 1: 13 | break; 14 | case 2: 15 | break; 16 | case 3: 17 | (BLUL.Logger ?? console).warn('脚本运行需要登录,当前未登录'); 18 | break; 19 | case 4: 20 | (BLUL.Logger ?? console).warn('未同意EULA,脚本将不会运行'); 21 | break; 22 | } 23 | if (result !== 0) { 24 | BLUL.recover(); 25 | return; 26 | } 27 | BLUL.Logger.info('脚本信息', `运行环境: ${BLUL.ENVIRONMENT} ${BLUL.ENVIRONMENT_VERSION}`, `版本: ${BLUL.VERSION}`); 28 | const { importModule } = BLUL; 29 | try { 30 | const r = await BLUL.Request.fetch('https://api.live.bilibili.com/xlive/web-room/v1/index/getInfoByUser?room_id=' + BLUL.INFO.ROOMID); 31 | const obj = await r.json(); 32 | BLUL.INFO.InfoByUser = obj.data; 33 | if (obj.code !== 0) { 34 | BLUL.Logger.warn(obj.message); 35 | } 36 | /* eslint-disable camelcase */ 37 | const color = Number(BLUL.INFO.InfoByUser?.property?.danmu?.color).toString(16).toUpperCase(); 38 | BLUL.Logger.info('用户信息', `uid: ${BLUL.INFO.UID} 用户名: ${BLUL.INFO.InfoByUser?.info?.uname}`, 39 | `手机绑定: ${BLUL.INFO.InfoByUser?.info?.mobile_verify ? '是' : '否'} 实名认证: ${BLUL.INFO.InfoByUser?.info?.identification ? '是' : '否'}`, 40 | `UL: ${BLUL.INFO.InfoByUser?.user_level?.level} 金瓜子: ${BLUL.INFO.InfoByUser?.wallet?.gold} 银瓜子: ${BLUL.INFO.InfoByUser?.wallet?.silver}`, 41 | `弹幕模式: ${BLUL.INFO.InfoByUser?.property?.danmu?.mode} 弹幕颜色: ${color} 弹幕长度: ${BLUL.INFO.InfoByUser?.property?.danmu?.length}`, 42 | `房间id: ${BLUL.INFO.ROOMID} 短id: ${BLUL.INFO.SHORT_ROOMID} 主播uid: ${BLUL.INFO.ANCHOR_UID}`); 43 | /* eslint-enable camelcase */ 44 | } catch (error) { 45 | BLUL.Logger.error('初始化用户信息失败', error); 46 | } 47 | BLUL.setBase('https://cdn.jsdelivr.net/gh/SeaLoong/BLRHH@dist'); 48 | await importModule('Sign'); 49 | await importModule('Exchange'); 50 | await importModule('TreasureBox'); 51 | await importModule('Heartbeat'); 52 | await importModule('DailyReward'); 53 | await importModule('AvoidDetection'); 54 | })(); 55 | -------------------------------------------------------------------------------- /src/meta.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name Bilibili直播间挂机助手3 3 | // @namespace SeaLoong 4 | // @version 3.1.11 5 | // @description B站直播间挂机用: 签到,领瓜子,移动端心跳,瓜子换硬币等 6 | // @author SeaLoong 7 | // @homepageURL https://github.com/SeaLoong/BLRHH 8 | // @supportURL https://github.com/SeaLoong/BLRHH/issues 9 | // @updateURL ./installer.{replace}.user.js 10 | // @include /^https?:\/\/live\.bilibili\.com\/(blanc\/)?\d+.*$/ 11 | // @license MIT License 12 | // @require https://raw.githubusercontent.com/SeaLoong/BLUL/dist/meta.{replace}.js 13 | // @require ./main.js 14 | // @resource EULA ./html/eula.html 15 | // @resource NOTICE ./html/notice.html 16 | // @resource tfjs https://cdn.jsdelivr.net/npm/@tensorflow/tfjs@2.3.0/dist/tf.min.js 17 | // @resource Sign ./modules/sign.js 18 | // @resource Exchange ./modules/exchange.js 19 | // @resource TreasureBox ./modules/treasurebox.js 20 | // @resource TreasureBox/worker ./modules/treasurebox/worker.js 21 | // @resource Heartbeat ./modules/heartbeat.js 22 | // @resource DailyReward ./modules/dailyreward.js 23 | // @resource AvoidDetection ./modules/avoiddetection.js 24 | // ==/UserScript== 25 | -------------------------------------------------------------------------------- /src/modules/avoiddetection.js: -------------------------------------------------------------------------------- 1 | const NAME = '避免检测'; 2 | const config = { 3 | avoidDetection: true, 4 | interval: 5 5 | }; 6 | export default async function (importModule, BLUL, GM) { 7 | function mouseMove () { 8 | if (!config.avoidDetection) return; 9 | BLUL.debug('AvoidDetection.mouseMove'); 10 | document.dispatchEvent(new MouseEvent('mousemove', { 11 | screenX: Math.floor(Math.random() * screen.availWidth), 12 | screenY: Math.floor(Math.random() * screen.availHeight), 13 | clientX: Math.floor(Math.random() * window.innerWidth), 14 | clientY: Math.floor(Math.random() * window.innerHeight), 15 | ctrlKey: Math.random() > 0.8, 16 | shiftKey: Math.random() > 0.8, 17 | altKey: Math.random() > 0.9, 18 | metaKey: false, 19 | button: 0, 20 | buttons: 0, 21 | relatedTarget: null, 22 | region: null, 23 | detail: 0, 24 | view: window, 25 | sourceCapabilities: window.InputDeviceCapabilities ? new window.InputDeviceCapabilities({ fireTouchEvents: false }) : null, 26 | bubbles: true, 27 | cancelable: true, 28 | composed: true 29 | })); 30 | setTimeout(mouseMove, config.interval * 60e3); 31 | } 32 | function run () { 33 | if (!config.avoidDetection) return; 34 | BLUL.debug('AvoidDetection.run'); 35 | BLUL.removeAllListener('visibilitychange'); 36 | mouseMove(); 37 | } 38 | 39 | BLUL.oninit(() => { 40 | BLUL.Config.addItem('avoidDetection', NAME, config.avoidDetection, { tag: 'input', help: '定时触发网页鼠标事件,以避免挂机检测', attribute: { type: 'checkbox' } }); 41 | BLUL.Config.addItem('avoidDetection.interval', '触发间隔', config.interval, { tag: 'input', attribute: { type: 'number', placeholder: '默认为5', min: 1, max: 20 } }); 42 | 43 | BLUL.Config.onload(() => { 44 | config.avoidDetection = BLUL.Config.get('avoidDetection'); 45 | config.interval = BLUL.Config.get('avoidDetection.interval'); 46 | }); 47 | }); 48 | BLUL.onrun(run); 49 | 50 | BLUL.AvoidDetection = { 51 | run 52 | }; 53 | 54 | BLUL.debug('Module Loaded: AvoidDetection', BLUL.AvoidDetection); 55 | } 56 | -------------------------------------------------------------------------------- /src/modules/dailyreward.js: -------------------------------------------------------------------------------- 1 | const NAME = '每日奖励'; 2 | const config = { 3 | dailyReward: true, 4 | login: true, 5 | watch: false, 6 | coin: false, 7 | coinNumber: 5, 8 | share: false 9 | }; 10 | let cards; 11 | export default async function (importModule, BLUL, GM) { 12 | const Util = BLUL.Util; 13 | 14 | const NAME_DYNAMIC = NAME + '-获取动态视频'; 15 | async function dynamic () { 16 | if (!config.dailyReward) return Util.cancelRetry(dynamic); 17 | BLUL.debug('DailyReward.dynamic'); 18 | try { 19 | const r = await BLUL.Request.fetch({ 20 | url: 'https://api.vc.bilibili.com/dynamic_svr/v1/dynamic_svr/dynamic_new', 21 | search: { 22 | uid: BLUL.INFO.UID, 23 | type_list: 8 24 | } 25 | }); 26 | const obj = await r.json(); 27 | if (obj.code === 0 && obj?.data?.cards) { 28 | cards = obj?.data?.cards; 29 | for (const c of cards) { 30 | c.card = JSON.parse(c.card); 31 | } 32 | } else { 33 | BLUL.Logger.warn(NAME_DYNAMIC, obj.message); 34 | } 35 | return Util.cancelRetry(dynamic); 36 | } catch (error) { 37 | BLUL.Logger.error(NAME_DYNAMIC, error); 38 | } 39 | return Util.retry(dynamic); 40 | } 41 | 42 | const NAME_LOGIN = NAME + '-登录'; 43 | async function login () { 44 | if (!config.dailyReward || !config.login) return Util.cancelRetry(login); 45 | BLUL.debug('DailyReward.login'); 46 | try { 47 | const r = await BLUL.Request.fetch({ 48 | url: 'https://api.bilibili.com/x/report/click/now', 49 | search: { 50 | jsonp: 'jsonp' 51 | } 52 | }); 53 | const obj = await r.json(); 54 | if (obj.code === 0) { 55 | BLUL.Logger.success(NAME_LOGIN, '完成'); 56 | } else { 57 | BLUL.Logger.warn(NAME_LOGIN, obj.message); 58 | } 59 | return Util.cancelRetry(login); 60 | } catch (error) { 61 | BLUL.Logger.error(NAME_LOGIN, error); 62 | } 63 | return Util.retry(login); 64 | } 65 | 66 | const NAME_WATCH = NAME + '-观看'; 67 | async function watch () { 68 | if (!config.dailyReward || !config.watch) return Util.cancelRetry(watch); 69 | BLUL.debug('DailyReward.watch'); 70 | if (!cards?.length) { 71 | Util.cancelRetry(watch); 72 | BLUL.Logger.warn(NAME_WATCH, '没有可用的视频动态,10分钟后将重试'); 73 | await Util.sleep(600e3); 74 | return watch(); 75 | } 76 | try { 77 | const { aid, cid } = cards[0].card; 78 | const { bvid } = cards[0].desc; 79 | const r = await BLUL.Request.fetch({ 80 | method: 'POST', 81 | url: 'https://api.bilibili.com/x/report/web/heartbeat', 82 | data: { 83 | aid: aid, 84 | cid: cid, 85 | bvid: bvid, 86 | mid: BLUL.INFO.UID, 87 | start_ts: Math.floor(Date.now() / 1e3), 88 | played_time: 0, 89 | realtime: 0, 90 | type: 3, 91 | play_type: 1, // 1:播放开始,2:播放中 92 | dt: 2, 93 | csrf: BLUL.INFO.CSRF 94 | } 95 | }); 96 | const obj = await r.json(); 97 | if (obj.code === 0) { 98 | BLUL.Logger.success(NAME_WATCH, `完成(av=${aid})`); 99 | } else { 100 | BLUL.Logger.warn(NAME_WATCH, obj.message); 101 | } 102 | return Util.cancelRetry(watch); 103 | } catch (error) { 104 | BLUL.Logger.error(NAME_WATCH, error); 105 | } 106 | return Util.retry(watch); 107 | } 108 | 109 | const NAME_COIN = NAME + '-投币'; 110 | async function coin () { 111 | if (!config.dailyReward || !config.coin) return Util.cancelRetry(coin); 112 | try { 113 | BLUL.debug('DailyReward.coin'); 114 | if (!cards?.length) { 115 | Util.cancelRetry(coin); 116 | BLUL.Logger.warn(NAME_COIN, '没有可用的视频动态,10分钟后将重试'); 117 | await Util.sleep(600e3); 118 | return coin(); 119 | } 120 | const r = await BLUL.Request.fetch({ 121 | url: 'https://www.bilibili.com/plus/account/exp.php' 122 | }); 123 | const obj = await r.json(); 124 | if (!('number' in obj)) { 125 | Util.cancelRetry(coin); 126 | BLUL.Logger.error(NAME_COIN, '获取今日已投币经验失败,10分钟后将重试'); 127 | await Util.sleep(600e3); 128 | return coin(); 129 | } 130 | let count = obj.number / 10; 131 | let stop = false; 132 | for (const { card } of cards) { 133 | if (config.coinNumber <= count || stop) return; 134 | const { aid } = card; 135 | let one = false; 136 | await (async function tryCoin () { 137 | if (!config.dailyReward || !config.coin || config.coinNumber <= count) return Util.cancelRetry(tryCoin); 138 | BLUL.debug('DailyReward.coin.tryCoin'); 139 | try { 140 | const multiply = one ? 1 : Math.min(2, (config.coinNumber - count)); 141 | const r = await BLUL.Request.monkey({ 142 | method: 'POST', 143 | url: 'https://app.bilibili.com/x/v2/view/coin/add', 144 | headers: BLUL.AppToken.headers, 145 | data: BLUL.AppToken.sign({ 146 | access_key: await BLUL.AppToken.getAccessToken(), 147 | aid: aid, 148 | avtype: 1, 149 | c_locale: 'zh_CN', 150 | multiply: multiply, 151 | select_like: 0 152 | }) 153 | }); 154 | const obj = await r.json(); 155 | if (obj.code === 0) { 156 | count += multiply; 157 | BLUL.Logger.success(NAME_COIN, `投币成功(av=${aid},num=${multiply})`); 158 | } else if (obj.code === -110) { 159 | stop = true; 160 | BLUL.Logger.warn(NAME_COIN, '未绑定手机,不能投币'); 161 | } else if (obj.code === 34003) { 162 | // 非法的投币数量 163 | if (!one) { 164 | one = true; 165 | return tryCoin(); 166 | } 167 | } else if (obj.code === 34005) { 168 | // 塞满啦!先看看库存吧~ 169 | } else { 170 | BLUL.Logger.warn(NAME_COIN, obj.message); 171 | } 172 | return Util.cancelRetry(tryCoin); 173 | } catch (error) { 174 | BLUL.Logger.error(NAME_COIN, error); 175 | } 176 | return Util.retry(tryCoin); 177 | })(); 178 | } 179 | Util.cancelRetry(coin); 180 | if (!stop && config.coinNumber > count) { 181 | BLUL.Logger.warn(NAME_COIN, '可投币的视频动态不足,10分钟后将重试'); 182 | await Util.sleep(600e3); 183 | return coin(); 184 | } 185 | return; 186 | } catch (error) { 187 | BLUL.Logger.error(NAME_COIN, error); 188 | } 189 | return Util.retry(coin); 190 | } 191 | 192 | const NAME_SHARE = NAME + '-分享'; 193 | async function share () { 194 | if (!config.dailyReward || !config.share) return Util.cancelRetry(share); 195 | BLUL.debug('DailyReward.share'); 196 | if (!cards?.length) { 197 | Util.cancelRetry(share); 198 | BLUL.Logger.warn(NAME_SHARE, '没有可用的视频动态,10分钟后将重试'); 199 | await Util.sleep(600e3); 200 | return share(); 201 | } 202 | try { 203 | const { aid } = cards[0].card; 204 | const r = await BLUL.Request.fetch({ 205 | method: 'POST', 206 | url: 'https://api.bilibili.com/x/web-interface/share/add', 207 | data: { 208 | aid: aid, 209 | csrf: BLUL.INFO.CSRF 210 | } 211 | }); 212 | const obj = await r.json(); 213 | if (obj.code === 0) { 214 | BLUL.Logger.success(NAME_SHARE, `完成(av=${aid})`); 215 | } else if (obj.code === 71000) { 216 | } else { 217 | BLUL.Logger.warn(NAME_SHARE, obj.message); 218 | } 219 | return Util.cancelRetry(share); 220 | } catch (error) { 221 | BLUL.Logger.error(NAME_SHARE, error); 222 | } 223 | return Util.retry(share); 224 | } 225 | 226 | const TIMESTAMP_NAME_LOGIN = 'timestampDailyReward-login'; 227 | const TIMESTAMP_NAME_WATCH = 'timestampDailyReward-watch'; 228 | const TIMESTAMP_NAME_COIN = 'timestampDailyReward-coin'; 229 | const TIMESTAMP_NAME_SHARE = 'timestampDailyReward-share'; 230 | 231 | async function run () { 232 | if (!config.dailyReward) return; 233 | BLUL.debug('DailyReward.run'); 234 | (async function runLogin () { 235 | if (!config.dailyReward || !config.login) return; 236 | if (Util.isAtTime(await GM.getValue(TIMESTAMP_NAME_LOGIN) ?? 0)) { 237 | await login(); 238 | await GM.setValue(TIMESTAMP_NAME_LOGIN, Date.now()); 239 | } 240 | BLUL.Logger.info(NAME_LOGIN, '今日已完成'); 241 | Util.callAtTime(runLogin); 242 | })(); 243 | 244 | (async function runWatch () { 245 | if (!config.dailyReward || !config.watch) return; 246 | if (Util.isAtTime(await GM.getValue(TIMESTAMP_NAME_WATCH) ?? 0)) { 247 | if (!cards) await dynamic(); 248 | await watch(); 249 | await GM.setValue(TIMESTAMP_NAME_WATCH, Date.now()); 250 | } 251 | BLUL.Logger.info(NAME_WATCH, '今日已完成'); 252 | Util.callAtTime(runWatch); 253 | })(); 254 | 255 | (async function runCoin () { 256 | if (!config.dailyReward || !config.coin) return; 257 | if (Util.isAtTime(await GM.getValue(TIMESTAMP_NAME_COIN) ?? 0)) { 258 | if (!BLUL.INFO?.InfoByUser?.info || BLUL.INFO.InfoByUser.info.mobile_verify) { 259 | if (!cards) await dynamic(); 260 | await coin(); 261 | await GM.setValue(TIMESTAMP_NAME_COIN, Date.now()); 262 | BLUL.Logger.info(NAME_COIN, '今日已完成'); 263 | } else { 264 | BLUL.Logger.warn(NAME_COIN, '未绑定手机,不能投币'); 265 | } 266 | } 267 | Util.callAtTime(runCoin); 268 | })(); 269 | 270 | (async function runShare () { 271 | if (!config.dailyReward || !config.share) return; 272 | if (Util.isAtTime(await GM.getValue(TIMESTAMP_NAME_SHARE) ?? 0)) { 273 | if (!cards) await dynamic(); 274 | await share(); 275 | await GM.setValue(TIMESTAMP_NAME_SHARE, Date.now()); 276 | } 277 | BLUL.Logger.info(NAME_SHARE, '今日已完成'); 278 | Util.callAtTime(runShare); 279 | })(); 280 | } 281 | 282 | BLUL.onupgrade(() => { 283 | GM.deleteValue(TIMESTAMP_NAME_LOGIN); 284 | GM.deleteValue(TIMESTAMP_NAME_WATCH); 285 | GM.deleteValue(TIMESTAMP_NAME_COIN); 286 | GM.deleteValue(TIMESTAMP_NAME_SHARE); 287 | }); 288 | 289 | BLUL.oninit(() => { 290 | BLUL.Config.addItem('dailyReward', NAME, config.dailyReward, { tag: 'input', help: '自动完成主站的每日任务', attribute: { type: 'checkbox' } }); 291 | BLUL.Config.addItem('dailyReward.login', '登录', config.login, { tag: 'input', attribute: { type: 'checkbox' } }); 292 | BLUL.Config.addItem('dailyReward.watch', '观看', config.watch, { tag: 'input', attribute: { type: 'checkbox' } }); 293 | BLUL.Config.addItem('dailyReward.coin', '投币', config.coin, { tag: 'input', attribute: { type: 'checkbox' } }); 294 | BLUL.Config.addItem('dailyReward.coin.number', '数量', config.coinNumber, { tag: 'input', attribute: { type: 'number', placeholder: '默认为5', min: 1, max: 5 } }); 295 | BLUL.Config.addItem('dailyReward.share', '分享', config.share, { tag: 'input', attribute: { type: 'checkbox' } }); 296 | 297 | BLUL.Config.onload(() => { 298 | config.dailyReward = BLUL.Config.get('dailyReward'); 299 | config.login = BLUL.Config.get('dailyReward.login'); 300 | config.watch = BLUL.Config.get('dailyReward.watch'); 301 | config.coin = BLUL.Config.get('dailyReward.coin'); 302 | config.coinNumber = BLUL.Config.get('dailyReward.coin.number'); 303 | config.share = BLUL.Config.get('dailyReward.share'); 304 | }); 305 | }); 306 | BLUL.onrun(run); 307 | 308 | BLUL.DailyReward = { 309 | run, 310 | login, 311 | watch, 312 | coin, 313 | share 314 | }; 315 | 316 | BLUL.debug('Module Loaded: DailyReward', BLUL.DailyReward); 317 | 318 | return BLUL.DailyReward; 319 | } 320 | -------------------------------------------------------------------------------- /src/modules/exchange.js: -------------------------------------------------------------------------------- 1 | const NAME = '兑换'; 2 | const config = { 3 | exchange: false, 4 | silver2coin: false, 5 | coin2silver: false, 6 | quantity: 1 7 | }; 8 | export default async function (importModule, BLUL, GM) { 9 | const Util = BLUL.Util; 10 | 11 | const NAME_SILVER2COIN = NAME + '-银瓜子兑换硬币'; 12 | async function silver2coin () { 13 | BLUL.debug('Exchange.silver2coin'); 14 | try { 15 | const response = await BLUL.Request.fetch({ 16 | method: 'POST', 17 | url: 'https://api.live.bilibili.com/pay/v1/Exchange/silver2coin', 18 | data: { 19 | platform: 'pc', 20 | csrf: BLUL.INFO.CSRF, 21 | csrf_token: BLUL.INFO.CSRF, 22 | visit_id: BLUL.INFO.VISIT_ID 23 | } 24 | }); 25 | const obj = await response.json(); 26 | if (obj.code === 0) { 27 | BLUL.Logger.success(NAME_SILVER2COIN, obj.message); 28 | return Util.cancelRetry(silver2coin); 29 | } else if (obj.message.includes('最多') || obj.message.includes('余额不足')) { 30 | BLUL.Logger.info(NAME_SILVER2COIN, obj.message); 31 | return Util.cancelRetry(silver2coin); 32 | } 33 | BLUL.Logger.warn(NAME_SILVER2COIN, obj.message); 34 | } catch (error) { 35 | BLUL.Logger.error(NAME_SILVER2COIN, error); 36 | } 37 | return Util.retry(silver2coin); 38 | } 39 | 40 | const NAME_COIN2SILVER = NAME + '-硬币兑换银瓜子'; 41 | async function coin2silver () { 42 | BLUL.debug('Exchange.coin2silver'); 43 | try { 44 | const response = await BLUL.Request.fetch({ 45 | method: 'POST', 46 | url: 'https://api.live.bilibili.com/pay/v1/Exchange/coin2silver', 47 | data: { 48 | num: config.quantity, 49 | platform: 'pc', 50 | csrf: BLUL.INFO.CSRF, 51 | csrf_token: BLUL.INFO.CSRF, 52 | visit_id: BLUL.INFO.VISIT_ID 53 | } 54 | }); 55 | const obj = await response.json(); 56 | if (obj.code === 0) { 57 | BLUL.Logger.success(NAME_COIN2SILVER, obj.message); 58 | return Util.cancelRetry(coin2silver); 59 | } else if (obj.message.includes('最多') || obj.message.includes('余额不足')) { 60 | BLUL.Logger.info(NAME_COIN2SILVER, obj.message); 61 | return Util.cancelRetry(coin2silver); 62 | } 63 | BLUL.Logger.warn(NAME_COIN2SILVER, obj.message); 64 | } catch (error) { 65 | BLUL.Logger.error(NAME_COIN2SILVER, error); 66 | } 67 | return Util.retry(coin2silver); 68 | } 69 | 70 | const TIMESTAMP_NAME_SILVER2COIN = 'timestampExchange-silver2coin'; 71 | const TIMESTAMP_NAME_COIN2SILVER = 'timestampExchange-coin2silver'; 72 | 73 | async function run () { 74 | if (!config.exchange) return; 75 | BLUL.debug('Exchange.run'); 76 | (async function runSilver2coin () { 77 | if (!config.exchange || !config.silver2coin) return; 78 | if (Util.isAtTime(await GM.getValue(TIMESTAMP_NAME_SILVER2COIN) ?? 0)) { 79 | await silver2coin(); 80 | await GM.setValue(TIMESTAMP_NAME_SILVER2COIN, Date.now()); 81 | } 82 | BLUL.Logger.info(NAME_SILVER2COIN, '今日已进行过兑换,等待下次兑换'); 83 | Util.callAtTime(runSilver2coin); 84 | })(); 85 | (async function runCoin2silver () { 86 | if (!config.exchange || !config.coin2silver) return; 87 | if (Util.isAtTime(await GM.getValue(TIMESTAMP_NAME_COIN2SILVER) ?? 0)) { 88 | await coin2silver(); 89 | await GM.setValue(TIMESTAMP_NAME_COIN2SILVER, Date.now()); 90 | } 91 | BLUL.Logger.info(NAME_COIN2SILVER, '今日已进行过兑换,等待下次兑换'); 92 | Util.callAtTime(runCoin2silver); 93 | })(); 94 | } 95 | 96 | BLUL.onupgrade(() => { 97 | GM.deleteValue(TIMESTAMP_NAME_SILVER2COIN); 98 | GM.deleteValue(TIMESTAMP_NAME_COIN2SILVER); 99 | }); 100 | 101 | BLUL.oninit(() => { 102 | BLUL.Config.addItem('exchange', NAME, config.exchange, { tag: 'input', attribute: { type: 'checkbox' } }); 103 | BLUL.Config.addItem('exchange.silver2coin', '银瓜子兑换硬币', config.silver2coin, { tag: 'input', help: '700银瓜子=1硬币,每天最多兑换1次', attribute: { type: 'checkbox' } }); 104 | BLUL.Config.addItem('exchange.coin2silver', '硬币兑换银瓜子', config.coin2silver, { tag: 'input', help: '1硬币=450银瓜子(老爷或大会员500银瓜子)普通用户每天兑换上限25硬币;老爷或大会员每天兑换上限50硬币。', attribute: { type: 'checkbox' } }); 105 | BLUL.Config.addItem('exchange.coin2silver.quantity', '兑换数量', config.quantity, { tag: 'input', corrector: v => v > 1 ? v : 1, attribute: { type: 'number', placeholder: '默认为1', min: 1, max: 50 } }); 106 | BLUL.Config.onload(() => { 107 | config.exchange = BLUL.Config.get('exchange'); 108 | config.silver2coin = BLUL.Config.get('exchange.silver2coin'); 109 | config.coin2silver = BLUL.Config.get('exchange.coin2silver'); 110 | config.quantity = BLUL.Config.get('exchange.coin2silver.quantity'); 111 | }); 112 | }); 113 | BLUL.onrun(run); 114 | 115 | BLUL.Exchange = { 116 | run, 117 | silver2coin, 118 | coin2silver 119 | }; 120 | 121 | BLUL.debug('Module Loaded: Exchange', BLUL.Exchange); 122 | 123 | return BLUL.Exchange; 124 | } 125 | -------------------------------------------------------------------------------- /src/modules/gift.js: -------------------------------------------------------------------------------- 1 | const NAME = '礼物'; 2 | const config = { 3 | gift: true, 4 | fastReceiveSmallHeartHeart: true, 5 | send: false, 6 | lightMedal: false 7 | }; 8 | export default async function (importModule, BLUL, GM) { 9 | const Util = BLUL.Util; 10 | 11 | const NAME_SENDGIFT = NAME + '-送礼'; 12 | const roomidSet = new Set(); 13 | function mobile (roomid = BLUL.INFO.ROOMID) { 14 | BLUL.debug('Heartbeat.mobile'); 15 | if (roomidSet.has(roomid)) return; 16 | roomidSet.add(roomid); 17 | const heartbeat = async () => { 18 | if (!roomidSet.has(roomid)) return; 19 | try { 20 | const response = await BLUL.Request.monkey({ 21 | method: 'POST', 22 | url: 'https://api.live.bilibili.com/heartbeat/v1/OnLine/mobileOnline', 23 | headers: BLUL.AppToken.headers, 24 | search: BLUL.AppToken.sign({ access_key: await BLUL.AppToken.getAccessToken() }), 25 | data: { 26 | roomid: roomid, 27 | scale: 'xxhdpi' 28 | } 29 | }); 30 | const obj = await response.json(); 31 | if (obj.code !== 0) { 32 | BLUL.Logger.warn(NAME_MOBILE, roomid, obj.message); 33 | } 34 | setTimeout(heartbeat, config.mobileInterval * 1e3); 35 | return Util.cancelRetry(heartbeat); 36 | } catch (error) { 37 | BLUL.Logger.error(NAME_MOBILE, roomid, error); 38 | } 39 | return Util.retry(heartbeat); 40 | }; 41 | heartbeat(); 42 | } 43 | 44 | function stopMobile (roomid = BLUL.INFO.ROOMID) { 45 | BLUL.debug('Heartbeat.stopMobile'); 46 | roomidSet.delete(roomid); 47 | } 48 | 49 | async function run () { 50 | if (!config.heartbeat) return; 51 | BLUL.debug('Heartbeat.run'); 52 | mobile(); 53 | } 54 | 55 | BLUL.oninit(() => { 56 | BLUL.Config.addItem('gift', NAME, config.gift, { tag: 'input', attribute: { type: 'checkbox' } }); 57 | BLUL.Config.addItem('gift.fastReceiveSmallHeartHeart', '快速领取小心心', config.fastReceiveSmallHeartHeart, { tag: 'input', attribute: { type: 'checkbox' } }); 58 | BLUL.Config.addItem('gift.send', '送礼', config.send, { tag: 'input', help: '送礼原则:
1.只送包裹中的银瓜子礼物
2.尽量保证不浪费
3.优先送出快到期的礼物
4.优先送出高价值的礼物
以下送礼功能会以满足这几个原则为基础来实现', attribute: { type: 'checkbox' } }); 59 | BLUL.Config.addItem('gift.', '心跳间隔', config.mobileInterval, { tag: 'input', corrector: v => v > 1 ? v : 1, attribute: { type: 'number', placeholder: '单位为秒,默认为300', min: 1, max: 3600 } }); 60 | 61 | BLUL.Config.onload(() => { 62 | config.gift = BLUL.Config.get('gift'); 63 | config.send = BLUL.Config.get('gift.send'); 64 | config.fastReceiveSmallHeartHeart = BLUL.Config.get('gift.fastReceiveSmallHeartHeart'); 65 | }); 66 | }); 67 | BLUL.onrun(run); 68 | 69 | BLUL.Gift = { 70 | run, 71 | mobile, 72 | stopMobile 73 | }; 74 | 75 | BLUL.debug('Module Loaded: Gift', BLUL.Gift); 76 | 77 | return BLUL.Gift; 78 | } 79 | -------------------------------------------------------------------------------- /src/modules/heartbeat.js: -------------------------------------------------------------------------------- 1 | const NAME = '心跳'; 2 | const config = { 3 | heartbeat: true, 4 | mobile: true, 5 | mobileInterval: 300 6 | }; 7 | export default async function (importModule, BLUL, GM) { 8 | const Util = BLUL.Util; 9 | 10 | const NAME_MOBILE = NAME + '-移动端'; 11 | const roomidSet = new Set(); 12 | async function mobile (roomid = BLUL.INFO.ROOMID) { 13 | if (!config.heartbeat || !config.mobile || roomidSet.has(roomid)) return; 14 | BLUL.debug('Heartbeat.mobile'); 15 | roomidSet.add(roomid); 16 | const heartbeat = async () => { 17 | BLUL.debug('Heartbeat.mobile: heartbeat'); 18 | try { 19 | const r = await BLUL.Request.monkey({ 20 | method: 'POST', 21 | url: 'https://api.live.bilibili.com/heartbeat/v1/OnLine/mobileOnline', 22 | headers: BLUL.AppToken.headers, 23 | search: BLUL.AppToken.sign({ access_key: await BLUL.AppToken.getAccessToken() }), 24 | data: { 25 | roomid: roomid, 26 | scale: 'xxhdpi' 27 | } 28 | }); 29 | const obj = await r.json(); 30 | if (obj.code !== 0) { 31 | BLUL.Logger.warn(NAME_MOBILE, `roomid=${roomid}`, obj.message); 32 | } 33 | return Util.cancelRetry(heartbeat); 34 | } catch (error) { 35 | BLUL.Logger.error(NAME_MOBILE, `roomid=${roomid}`, error); 36 | } 37 | return Util.retry(heartbeat); 38 | }; 39 | while (config.heartbeat && config.mobile && roomidSet.has(roomid)) { 40 | await heartbeat(); 41 | await Util.sleep(config.mobileInterval * 1e3); 42 | } 43 | } 44 | 45 | function stopMobile (roomid = BLUL.INFO.ROOMID) { 46 | BLUL.debug('Heartbeat.stopMobile'); 47 | roomidSet.delete(roomid); 48 | } 49 | 50 | async function run () { 51 | if (!config.heartbeat) return; 52 | BLUL.debug('Heartbeat.run'); 53 | await mobile(); 54 | } 55 | 56 | BLUL.oninit(() => { 57 | BLUL.Config.addItem('heartbeat', NAME, config.heartbeat, { tag: 'input', attribute: { type: 'checkbox' } }); 58 | BLUL.Config.addItem('heartbeat.mobile', '移动端', config.mobile, { tag: 'input', attribute: { type: 'checkbox' } }); 59 | BLUL.Config.addItem('heartbeat.mobile.interval', '心跳间隔', config.mobileInterval, { tag: 'input', corrector: v => v > 1 ? v : 1, attribute: { type: 'number', placeholder: '单位为秒,默认为300', min: 1, max: 3600 } }); 60 | BLUL.Config.onload(() => { 61 | config.heartbeat = BLUL.Config.get('heartbeat'); 62 | config.mobile = BLUL.Config.get('heartbeat.mobile'); 63 | config.mobileInterval = BLUL.Config.get('heartbeat.mobile.interval'); 64 | }); 65 | }); 66 | BLUL.onrun(run); 67 | 68 | BLUL.Heartbeat = { 69 | run, 70 | mobile, 71 | stopMobile 72 | }; 73 | 74 | BLUL.debug('Module Loaded: Heartbeat', BLUL.Heartbeat); 75 | 76 | return BLUL.Heartbeat; 77 | } 78 | -------------------------------------------------------------------------------- /src/modules/sign.js: -------------------------------------------------------------------------------- 1 | const NAME = '签到'; 2 | const config = { 3 | sign: true, 4 | live: true, 5 | linkGroup: true 6 | }; 7 | export default async function (importModule, BLUL, GM) { 8 | const Util = BLUL.Util; 9 | 10 | const NAME_LIVE = NAME + '-直播'; 11 | async function live () { 12 | BLUL.debug('Sign.live'); 13 | try { 14 | const response = await BLUL.Request.fetch('https://api.live.bilibili.com/xlive/web-ucenter/v1/sign/DoSign'); 15 | const obj = await response.json(); 16 | if (obj.code === 0) { 17 | BLUL.Logger.success(NAME_LIVE, obj.data.text); 18 | return Util.cancelRetry(live); 19 | } else if (obj.code === 1011040 || obj.message.includes('已签到')) { 20 | BLUL.Logger.info(NAME_LIVE, obj.message); 21 | return Util.cancelRetry(live); 22 | } else if (obj.code === 1001) { 23 | BLUL.Logger.warn(NAME_LIVE, '未绑定手机,不能签到'); 24 | return Util.cancelRetry(live); 25 | } 26 | BLUL.Logger.warn(NAME_LIVE, obj.message); 27 | } catch (error) { 28 | BLUL.Logger.error(NAME_LIVE, error); 29 | } 30 | return Util.retry(live); 31 | } 32 | 33 | const NAME_LINKGROUP = NAME + '-应援团'; 34 | async function getMedalInfo () { 35 | BLUL.debug('Sign.getMedalInfo'); 36 | if (BLUL.INFO.fansMedalList) return; 37 | let totalpages = 1; 38 | let page = 1; 39 | let fansMedalList = []; 40 | while (page <= totalpages) { 41 | const response = await BLUL.Request.fetch(`http://api.live.bilibili.com/fans_medal/v5/live_fans_medal/iApiMedal?page=${page}&pageSize=100`); 42 | const obj = await response.json(); 43 | if (obj.code === 0 && obj.data) { 44 | fansMedalList = fansMedalList.concat(obj.data.fansMedalList); 45 | totalpages = obj.data.pageinfo.totalpages; 46 | } else { 47 | BLUL.Logger.warn(NAME_LINKGROUP, obj.message); 48 | } 49 | page++; 50 | } 51 | BLUL.INFO.fansMedalList = fansMedalList; 52 | } 53 | 54 | async function linkGroup () { 55 | BLUL.debug('Sign.linkGroup'); 56 | try { 57 | await getMedalInfo(); 58 | const response = await BLUL.Request.fetch('https://api.vc.bilibili.com/link_group/v1/member/my_groups'); 59 | const obj = await response.json(); 60 | if (obj.code === 0 && obj.data) { 61 | /* eslint-disable camelcase */ 62 | const fullLevelMedalTargetIds = new Set(); 63 | for (const { level, target_id } of BLUL.INFO.fansMedalList) { 64 | if (level === 20 || level === 40) { 65 | fullLevelMedalTargetIds.add(target_id); 66 | } 67 | } 68 | const promises = []; 69 | for (const { owner_uid, group_id } of obj.data.list) { 70 | if (owner_uid === BLUL.INFO.UID) continue; // 自己不能给自己的应援团应援 71 | if (fullLevelMedalTargetIds.has(owner_uid)) continue; // 跳过满级牌子对应的应援团 72 | const signOneLinkGroup = async () => { 73 | try { 74 | const msg = `应援团(group_id=${group_id},owner_uid=${owner_uid})`; 75 | const response = await BLUL.Request.fetch({ 76 | url: 'https://api.vc.bilibili.com/link_setting/v1/link_setting/sign_in', 77 | search: { 78 | group_id, 79 | owner_id: owner_uid 80 | } 81 | }); 82 | const obj = await response.json(); 83 | if (obj.code === 0) { 84 | if (obj.data.status === 0) { 85 | BLUL.Logger.success(NAME_LINKGROUP, msg, `签到成功,对应勋章亲密度+${obj.data.add_num}`); 86 | return Util.cancelRetry(signOneLinkGroup); 87 | } else if (obj.data.status === 1) { 88 | BLUL.Logger.info(NAME_LINKGROUP, msg, '今日已签到过'); 89 | return Util.cancelRetry(signOneLinkGroup); 90 | } 91 | } 92 | BLUL.Logger.warn(NAME_LINKGROUP, msg, obj.message); 93 | } catch (error) { 94 | BLUL.Logger.error(NAME_LINKGROUP, error); 95 | } 96 | return Util.retry(signOneLinkGroup); 97 | }; 98 | promises.push(signOneLinkGroup()); 99 | } 100 | return Promise.all(promises); 101 | } 102 | BLUL.Logger.warn(NAME_LINKGROUP, obj.message); 103 | } catch (error) { 104 | BLUL.Logger.error(NAME_LINKGROUP, error); 105 | } 106 | return Util.retry(linkGroup); 107 | } 108 | 109 | const TIMESTAMP_NAME_LIVE = 'timestampSign-live'; 110 | const TIMESTAMP_NAME_LINKGROUP = 'timestampSign-linkGroup'; 111 | 112 | async function run () { 113 | if (!config.sign) return; 114 | BLUL.debug('Sign.run'); 115 | (async function runLive () { 116 | if (!config.sign || !config.live) return; 117 | if (!BLUL.INFO?.InfoByUser?.info || BLUL.INFO.InfoByUser.info.mobile_verify) { 118 | if (Util.isAtTime(await GM.getValue(TIMESTAMP_NAME_LIVE) ?? 0)) { 119 | await live(); 120 | await GM.setValue(TIMESTAMP_NAME_LIVE, Date.now()); 121 | } 122 | BLUL.Logger.info(NAME_LIVE, '今日已进行过签到,等待下次签到'); 123 | } else { 124 | BLUL.Logger.warn(NAME_LIVE, '未绑定手机,不能签到'); 125 | } 126 | Util.callAtTime(runLive); 127 | })(); 128 | const hourRunLinkGroup = 9; 129 | (async function runLinkGroup () { 130 | if (!config.sign || !config.linkGroup) return; 131 | if (Util.isAtTime(await GM.getValue(TIMESTAMP_NAME_LINKGROUP) ?? 0, hourRunLinkGroup)) { 132 | await linkGroup(); 133 | await GM.setValue(TIMESTAMP_NAME_LINKGROUP, Date.now()); 134 | } 135 | BLUL.Logger.info(NAME_LINKGROUP, '今日已进行过签到,等待下次签到'); 136 | Util.callAtTime(runLinkGroup, hourRunLinkGroup); 137 | })(); 138 | } 139 | 140 | BLUL.onupgrade(() => { 141 | GM.deleteValue(TIMESTAMP_NAME_LIVE); 142 | GM.deleteValue(TIMESTAMP_NAME_LINKGROUP); 143 | }); 144 | 145 | BLUL.oninit(() => { 146 | BLUL.Config.addItem('sign', NAME, config.sign, { tag: 'input', attribute: { type: 'checkbox' } }); 147 | BLUL.Config.addItem('sign.live', '直播', config.live, { tag: 'input', attribute: { type: 'checkbox' } }); 148 | BLUL.Config.addItem('sign.linkGroup', '应援团', config.linkGroup, { tag: 'input', attribute: { type: 'checkbox' } }); 149 | BLUL.Config.onload(() => { 150 | config.sign = BLUL.Config.get('sign'); 151 | config.live = BLUL.Config.get('sign.live'); 152 | config.linkGroup = BLUL.Config.get('sign.linkGroup'); 153 | }); 154 | }); 155 | 156 | BLUL.onrun(run); 157 | 158 | BLUL.Sign = { 159 | run, 160 | live, 161 | linkGroup 162 | }; 163 | 164 | BLUL.debug('Module Loaded: Sign', BLUL.Sign); 165 | 166 | return BLUL.Sign; 167 | } 168 | -------------------------------------------------------------------------------- /src/modules/treasurebox.js: -------------------------------------------------------------------------------- 1 | const NAME = '宝箱'; 2 | const config = { 3 | treasureBox: false, 4 | silverBox: false, 5 | goldBox: false, 6 | aid: 683, 7 | cache: {}, 8 | interval: 60, 9 | ignoreKeywords: ['test', 'encrypt', '测试', '钓鱼', '加密', '炸鱼'] 10 | }; 11 | export default async function (importModule, BLUL, GM) { 12 | const Util = BLUL.Util; 13 | 14 | let worker; 15 | 16 | let tipElement; 17 | let timerElement; 18 | let canvas; 19 | let ctx; 20 | 21 | let loadImageResolveFn; 22 | const image = new Image(120, 40); 23 | image.onload = () => { 24 | if (loadImageResolveFn) { 25 | loadImageResolveFn(); 26 | loadImageResolveFn = null; 27 | } 28 | }; 29 | 30 | function loadImage (url) { 31 | const promise = new Promise(resolve => (loadImageResolveFn = resolve)); 32 | image.src = url; 33 | return promise; 34 | } 35 | 36 | function setTip (html) { 37 | return tipElement && tipElement.html(html); 38 | } 39 | 40 | function timing (time) { 41 | if (!timerElement) return; 42 | if (timerElement.interval) { 43 | clearInterval(timerElement.interval); 44 | timerElement.interval = null; 45 | } 46 | time = Math.ceil(time); 47 | if (time <= 0) return; 48 | return new Promise(resolve => { 49 | timerElement.html(time); 50 | timerElement.show(); 51 | timerElement.interval = setInterval(() => { 52 | if (--time <= 0) { 53 | clearInterval(timerElement.interval); 54 | timerElement.interval = null; 55 | timerElement.hide(); 56 | resolve(); 57 | } else { 58 | timerElement.html(time); 59 | } 60 | }, 1e3); 61 | }); 62 | } 63 | 64 | let silverBoxData; 65 | 66 | const NAME_SILVER_BOX = NAME + '-银瓜子宝箱'; 67 | async function silverBox () { 68 | BLUL.debug('TreasureBox.silverBox'); 69 | try { 70 | const response = await BLUL.Request.fetch('https://api.live.bilibili.com/lottery/v1/SilverBox/getCurrentTask'); 71 | const obj = await response.json(); 72 | if (obj.code === 0) { 73 | silverBoxData = obj.data; 74 | setTip(`次数
${silverBoxData.times}/${silverBoxData.max_times}
银瓜子
${silverBoxData.silver}`); 75 | BLUL.Logger.info(NAME_SILVER_BOX, `任务:${silverBoxData.minute} 分钟, ${silverBoxData.silver} 银瓜子, 次数 ${silverBoxData.times}/${silverBoxData.max_times}`); 76 | await timing(silverBoxData.time_end - Date.now() / 1000 + 1); 77 | return silverBoxAward(); 78 | } else if (obj.code === -10017) { 79 | // 今天所有的宝箱已经领完! 80 | setTip('今日
已领完'); 81 | BLUL.Logger.info(NAME_SILVER_BOX, obj.message); 82 | return Util.cancelRetry(silverBox); 83 | } else if (obj.code === -500) { 84 | // 请先登录! 85 | setTip('请先
登录'); 86 | BLUL.Logger.warn(NAME_SILVER_BOX, obj.message); 87 | return Util.cancelRetry(silverBox); 88 | } 89 | BLUL.Logger.warn(NAME_SILVER_BOX, obj.message); 90 | } catch (error) { 91 | BLUL.Logger.error(NAME_SILVER_BOX, error); 92 | } 93 | return Util.retry(silverBox); 94 | } 95 | 96 | async function silverBoxAward () { 97 | BLUL.debug('TreasureBox.silverBoxAward'); 98 | try { 99 | let response = await BLUL.Request.fetch('https://api.live.bilibili.com/lottery/v1/SilverBox/getCaptcha?ts=' + Date.now()); 100 | let obj = await response.json(); 101 | await loadImage(obj.data.img); 102 | ctx.drawImage(image, 0, 0); 103 | const captcha = await worker.predict(ctx.getImageData(0, 0, image.width, image.height)); 104 | const result = (0, eval)(captcha); // eslint-disable-line no-eval 105 | BLUL.debug('验证码识别结果: ', `${captcha}=${result}`); 106 | response = await BLUL.Request.fetch({ 107 | url: 'https://api.live.bilibili.com/lottery/v1/SilverBox/getAward', 108 | search: { 109 | time_start: silverBoxData.time_start, 110 | end_time: silverBoxData.time_end, 111 | captcha: result 112 | } 113 | }); 114 | obj = await response.json(); 115 | switch (obj.code) { 116 | case 0: 117 | BLUL.Logger.success(NAME_SILVER_BOX, `领取了 ${obj.data.awardSilver} 银瓜子`); 118 | Util.cancelRetry(silverBoxAward); 119 | return silverBox(); 120 | case -903: // -903: 已经领取过这个宝箱 121 | case -500: // -500:领取时间未到, 请稍后再试 122 | Util.cancelRetry(silverBoxAward); 123 | return silverBox(); 124 | case -800: // -800:未绑定手机 125 | case 1001: 126 | setTip('未绑定
手机'); 127 | BLUL.Logger.warn(NAME_SILVER_BOX, '未绑定手机,不能领取银瓜子'); 128 | return Util.cancelRetry(silverBoxAward); 129 | case 400: // 400: 访问被拒绝 130 | setTip('访问
被拒绝'); 131 | BLUL.Logger.error(NAME_SILVER_BOX, obj.message); 132 | return Util.cancelRetry(silverBoxAward); 133 | case -902: // -902: 验证码错误 134 | case -901: // -901: 验证码过期 135 | BLUL.Logger.info(NAME_SILVER_BOX, obj.message); 136 | Util.cancelRetry(silverBoxAward); 137 | break; 138 | default: 139 | BLUL.Logger.warn(NAME_SILVER_BOX, obj.message); 140 | } 141 | } catch (error) { 142 | BLUL.Logger.error(NAME_SILVER_BOX, error); 143 | } 144 | return Util.retry(silverBoxAward); 145 | } 146 | 147 | const NAME_GOLD_BOX = NAME + '-金宝箱'; 148 | const joinedSet = new Set(); 149 | const aidStatusMap = new Map(); 150 | async function goldBox () { 151 | BLUL.debug('TreasureBox.goldBox'); 152 | BLUL.Logger.info(NAME_GOLD_BOX, '正在检查可参加的宝箱抽奖'); 153 | let joinTime = 0; 154 | for (const k in config.cache) { 155 | for (const o of config.cache[k].typeB) { 156 | joinTime = Math.max(joinTime, o.join_end_time); 157 | } 158 | if (joinTime * 1e3 <= Date.now()) { 159 | delete config.cache[k]; 160 | aidStatusMap.set(parseInt(k, 10), 1); 161 | } 162 | } 163 | let aid = config.aid; 164 | let cnt = 0; 165 | const startedList = []; 166 | const waitList = []; 167 | const maxCnt = 2 + Math.round(Math.random() * 6); 168 | while (cnt < maxCnt) { 169 | switch (await joinActivity(aid)) { 170 | case 2: 171 | cnt++; 172 | break; 173 | case 1: 174 | cnt--; 175 | startedList.push(aid); 176 | break; 177 | case 0: 178 | cnt--; 179 | waitList.push(aid); 180 | break; 181 | } 182 | aid++; 183 | } 184 | let lastAid = waitList.reduce((m, v) => Math.min(m, v), Number.MAX_SAFE_INTEGER); 185 | if (lastAid === Number.MAX_SAFE_INTEGER) lastAid = startedList.reduce((m, v) => Math.max(m, v), 0) + 1; 186 | config.aid = Math.max(config.aid, lastAid); 187 | await BLUL.Config.set('treasureBox.goldBox.aid', config.aid); 188 | await BLUL.Config.set('treasureBox.goldBox.cache', JSON.stringify(config.cache)); 189 | aidStatusMap.clear(); 190 | } 191 | 192 | function joinActivity (aid) { 193 | return (async function tryJoin () { 194 | if (aidStatusMap.has(aid)) return aidStatusMap.get(aid); 195 | BLUL.debug('TreasureBox.joinActivity'); 196 | try { 197 | let data; 198 | if (aid in config.cache) { 199 | data = config.cache[aid]; 200 | } else { 201 | const r = await BLUL.Request.fetch({ 202 | url: 'https://api.live.bilibili.com/xlive/lottery-interface/v2/Box/getStatus?aid=' + aid, 203 | referrer: 'https://live.bilibili.com/p/html/live-room-treasurebox/index.html?aid=' + aid 204 | }); 205 | const obj = await r.json(); 206 | if (obj.code !== 0) { 207 | BLUL.Logger.warn(NAME_GOLD_BOX, obj.message); 208 | aidStatusMap.set(aid, 0); 209 | Util.cancelRetry(tryJoin); 210 | return 0; 211 | } 212 | data = obj.data; 213 | if (!data) { 214 | aidStatusMap.set(aid, 2); 215 | Util.cancelRetry(tryJoin); 216 | return 2; 217 | } 218 | } 219 | let joinTime = 0; 220 | const title = data.title; 221 | const ignore = config.ignoreKeywords.some(v => title.includes(v)); 222 | if (ignore) { 223 | BLUL.Logger.info(NAME_GOLD_BOX, `忽略抽奖: ${title}(aid=${aid})`); 224 | } 225 | for (const o of data.typeB) { 226 | joinTime = Math.max(joinTime, o.join_end_time); 227 | if (!joinedSet.has(aid) && !ignore && (o.status === 0 || o.status === -1)) { 228 | const names = []; 229 | for (const g of o.list) { 230 | names.push(g.jp_name); 231 | } 232 | draw(aid, o.round_num, o.startTime, o.join_start_time, o.join_end_time, title, ...names); 233 | } 234 | } 235 | joinedSet.add(aid); 236 | const ret = joinTime * 1e3 <= Date.now() ? 1 : 0; 237 | if (ret === 0) { 238 | config.cache[aid] = data; 239 | } else { 240 | delete config.cache[aid]; 241 | } 242 | aidStatusMap.set(aid, ret); 243 | Util.cancelRetry(tryJoin); 244 | return ret; 245 | } catch (error) { 246 | BLUL.Logger.error(NAME_GOLD_BOX, `aid=${aid}`, error); 247 | } 248 | return Util.retry(tryJoin); 249 | })(); 250 | } 251 | 252 | /* eslint-disable camelcase */ 253 | const drawMap = new Map(); 254 | function draw (aid, number, startTime, join_start_time, join_end_time, title, ...names) { 255 | if (!drawMap.has(aid)) drawMap.set(aid, new Set()); 256 | const set = drawMap.get(aid); 257 | if (set.has(number)) return; 258 | set.add(number); 259 | async function timeoutDraw () { 260 | BLUL.debug('TreasureBox.draw.timeoutDraw'); 261 | try { 262 | const r = await BLUL.Request.fetch({ 263 | url: 'https://api.live.bilibili.com/xlive/lottery-interface/v2/Box/draw', 264 | search: { 265 | aid, 266 | number 267 | }, 268 | referrer: 'https://live.bilibili.com/p/html/live-room-treasurebox/index.html?aid=' + aid 269 | }); 270 | const obj = await r.json(); 271 | if (obj.code === 0) { 272 | BLUL.Logger.success(NAME_GOLD_BOX, '已参加抽奖: ' + title, '奖品:', ...names); 273 | setTimeout(timeoutEnd, (join_end_time + 5) * 1e3 - Date.now()); 274 | Util.cancelRetry(timeoutDraw); 275 | } else if (obj.message.includes('未开始')) { 276 | return Util.retry(timeoutDraw); 277 | } else { 278 | BLUL.Logger.warn(NAME_GOLD_BOX, obj.message); 279 | } 280 | } catch (error) { 281 | BLUL.Logger.error(NAME_GOLD_BOX, `aid=${aid},number=${number}`, error); 282 | return Util.retry(timeoutDraw); 283 | } 284 | } 285 | async function timeoutEnd () { 286 | BLUL.debug('TreasureBox.draw.timeoutEnd'); 287 | try { 288 | const r = await BLUL.Request.fetch({ 289 | url: 'https://api.live.bilibili.com/xlive/lottery-interface/v2/Box/getWinnerGroupInfo', 290 | search: { 291 | aid, 292 | number 293 | }, 294 | referrer: 'https://live.bilibili.com/p/html/live-room-treasurebox/index.html?aid=' + aid 295 | }); 296 | const obj = await r.json(); 297 | if (obj.code === 0) { 298 | if (!obj.data.groups) { 299 | return Util.retry(timeoutEnd); 300 | } 301 | for (const gift of obj.data.groups) { 302 | const arr = []; 303 | for (const u of gift.list) { 304 | arr.push(u.uid + ' ' + u.uname); 305 | } 306 | BLUL.Logger.info(NAME_GOLD_BOX, '奖品: ' + gift.giftTitle, '中奖人:', ...arr); 307 | } 308 | Util.cancelRetry(timeoutEnd); 309 | } else { 310 | BLUL.Logger.warn(NAME_GOLD_BOX, obj.message); 311 | } 312 | } catch (error) { 313 | BLUL.Logger.error(NAME_GOLD_BOX, `aid=${aid},number=${number}`, error); 314 | return Util.retry(timeoutEnd); 315 | } 316 | } 317 | const t = (join_start_time + 3) * 1e3 - Date.now(); 318 | if (t > 0) { 319 | BLUL.Logger.info(NAME_GOLD_BOX, '等待参加: ' + title, '开始时间: ' + startTime, '奖品: ', ...names); 320 | } 321 | setTimeout(timeoutDraw, t); 322 | } 323 | /* eslint-enable camelcase */ 324 | 325 | async function run () { 326 | if (!config.treasureBox) return; 327 | BLUL.debug('TreasureBox.run'); 328 | (async function runSilverBox () { 329 | if (!config.treasureBox || !config.silverBox) return; 330 | if (!tipElement && !timerElement && !$('.draw-box.gift-left-part').length) { 331 | await BLUL.addResource('tfjs', ['https://cdn.jsdelivr.net/npm/@tensorflow/tfjs@2.0.1/dist/tf.min.js']); 332 | await BLUL.addResource('TreasureBox_Model', ['https://cdn.jsdelivr.net/gh/SeaLoong/BLRHH/models/treasurebox_captcha/model.json', 'https://raw.githubusercontent.com/SeaLoong/BLRHH/dev/models/treasurebox_captcha/model.json']); 333 | 334 | const box = $('#gift-control-vm div.treasure-box.p-relative').first(); 335 | box.attr('id', 'old_treasure_box'); 336 | box.hide(); 337 | const cssTreasureBox = `${BLUL.NAME}-treasure-box`; 338 | const cssTreasureBoxText = `${BLUL.NAME}-treasure-box-text`; 339 | await GM.addStyle(` 340 | .${cssTreasureBox} { position: relative; min-width: 46px; display: inline-block; float: left; padding: 22px 0 0 15px; } 341 | .${cssTreasureBoxText} { text-align: center; user-select: none; max-width: 40px; padding: 2px 4px; margin-top: 3px; font-size: 12px; color: #fff; background-color: rgba(0,0,0,.5); border-radius: 10px; } 342 | `); 343 | const div = $(`
`); 344 | tipElement = $(`
自动
领取中
`); 345 | timerElement = $(`
`); 346 | timerElement.hide(); 347 | box.after(div); 348 | div.append(tipElement); 349 | tipElement.after(timerElement); 350 | worker = await BLUL.Worker.importModule('TreasureBox/worker'); 351 | canvas = $('')[0]; 352 | ctx = canvas.getContext('2d'); 353 | } 354 | /* eslint-disable camelcase */ 355 | if (!BLUL.INFO?.InfoByUser?.info || BLUL.INFO.InfoByUser.info.mobile_verify) { 356 | setTip('自动
领取中'); 357 | await worker.loadModel(await BLUL.getResourceUrl('TreasureBox_Model')); 358 | await silverBox(); 359 | } else { 360 | setTip('未绑定
手机'); 361 | BLUL.Logger.warn(NAME_SILVER_BOX, '未绑定手机,不能领取银瓜子'); 362 | } 363 | /* eslint-enable camelcase */ 364 | Util.callAtTime(runSilverBox); 365 | })(); 366 | (async function runGoldBox () { 367 | if (!config.treasureBox || !config.goldBox) return; 368 | /* eslint-disable camelcase */ 369 | if (!BLUL.INFO?.InfoByUser?.info || BLUL.INFO.InfoByUser.info.mobile_verify) { 370 | await goldBox(); 371 | } else { 372 | BLUL.Logger.warn(NAME_SILVER_BOX, '未绑定手机,不能参加宝箱抽奖'); 373 | } 374 | /* eslint-enable camelcase */ 375 | setTimeout(runGoldBox, config.interval * 60e3); 376 | })(); 377 | } 378 | 379 | BLUL.onupgrade(async () => { 380 | await BLUL.Config.set('treasureBox.goldBox.aid', config.aid); 381 | }); 382 | 383 | BLUL.oninit(() => { 384 | BLUL.Config.addItem('treasureBox', NAME, config.treasureBox, { tag: 'input', attribute: { type: 'checkbox' } }); 385 | BLUL.Config.addItem('treasureBox.silverBox', '银瓜子宝箱', config.silverBox, { tag: 'input', help: '领取银瓜子宝箱,需要绑定手机才能正常使用', attribute: { type: 'checkbox' } }); 386 | BLUL.Config.addItem('treasureBox.goldBox', '金宝箱', config.silverBox, { tag: 'input', help: '参加金宝箱抽奖(即实物抽奖),需要绑定手机才能正常使用', attribute: { type: 'checkbox' } }); 387 | BLUL.Config.addItem('treasureBox.goldBox.aid', 'aid', config.aid, { tag: 'input', attribute: { type: 'number' } }); 388 | BLUL.Config.addItem('treasureBox.goldBox.cache', 'cache', JSON.stringify(config.cache), { tag: 'input', help: '此项只用于存储和显示数据', attribute: { type: 'text', readonly: true } }); 389 | BLUL.Config.addItem('treasureBox.goldBox.interval', '检查间隔', config.interval, { tag: 'input', help: '设定多久检查一次宝箱抽奖
单位为分钟,默认为60', corrector: v => v > 1 ? v : 60, attribute: { type: 'number', placeholder: '单位为分钟,默认为60', min: 1, max: 1440 } }); 390 | BLUL.Config.addItem('treasureBox.goldBox.ignoreKeywords', '忽略关键字', config.ignoreKeywords.join(','), { tag: 'input', help: '忽略含有以下关键字的抽奖,用英文逗号隔开', attribute: { type: 'text' } }); 391 | 392 | BLUL.Config.onload(() => { 393 | config.treasureBox = BLUL.Config.get('treasureBox'); 394 | config.silverBox = BLUL.Config.get('treasureBox.silverBox'); 395 | config.goldBox = BLUL.Config.get('treasureBox.goldBox'); 396 | config.aid = BLUL.Config.get('treasureBox.goldBox.aid'); 397 | config.cache = JSON.parse(BLUL.Config.get('treasureBox.goldBox.cache')); 398 | config.interval = BLUL.Config.get('treasureBox.goldBox.interval'); 399 | config.ignoreKeywords = BLUL.Config.get('treasureBox.goldBox.ignoreKeywords').split(',').map(v => v.trim()); 400 | }); 401 | }); 402 | BLUL.onrun(run); 403 | 404 | BLUL.TreasureBox = { 405 | run, 406 | silverBox: { 407 | silverBox, 408 | setTip, 409 | timing 410 | }, 411 | goldBox: { 412 | goldBox, 413 | joinActivity, 414 | draw 415 | } 416 | }; 417 | 418 | BLUL.debug('Module Loaded: TreasureBox', BLUL.TreasureBox); 419 | 420 | return BLUL.TreasureBox; 421 | } 422 | -------------------------------------------------------------------------------- /src/modules/treasurebox/worker.js: -------------------------------------------------------------------------------- 1 | const NAME = 'TreasureBox-Worker'; 2 | export default async function (importModule, BLUL, GM) { 3 | await importModule('tfjs'); 4 | /* globals tf */ 5 | 6 | let model; 7 | 8 | function RGBA2Gray (imageData) { 9 | const { width, height, data } = imageData; 10 | const n = width * height; 11 | const grayScaleData = new Uint8ClampedArray(n); 12 | for (let i = 0; i < n; i++) { 13 | const r = data[4 * i]; 14 | const g = data[4 * i + 1]; 15 | const b = data[4 * i + 2]; 16 | const gray = 0.299 * r + 0.587 * g + 0.114 * b; 17 | grayScaleData[i] = gray; 18 | } 19 | return { width, height, data: grayScaleData }; 20 | } 21 | 22 | function binarization (grayScaleImageData, threshold = 200) { 23 | const { width, height, data } = grayScaleImageData; 24 | const n = data.length; 25 | const binarizationData = new Uint8ClampedArray(n); 26 | for (let i = 0; i < n; i++) { 27 | binarizationData[i] = data[i] > threshold ? 255 : 0; 28 | } 29 | return { width, height, data: binarizationData }; 30 | } 31 | 32 | function filter (binarizationImageData) { 33 | const { width, height, data } = binarizationImageData; 34 | const n = data.length; 35 | const filteredData = new Uint8ClampedArray(n); 36 | const maxFilter3x3 = (i) => { 37 | let m = data[i]; 38 | for (let x = -1; x <= 1; x++) { 39 | for (let y = -1; y <= 1; y++) { 40 | const j = i + x + y * width; 41 | if (j < 0 || j >= n) continue; 42 | m = Math.max(m, data[j]); 43 | } 44 | } 45 | return m; 46 | }; 47 | for (let i = 0; i < n; i++) { 48 | filteredData[i] = maxFilter3x3(i); 49 | } 50 | return { width, height, data: filteredData }; 51 | } 52 | 53 | function normalization (array) { 54 | let n = array.length; 55 | const arr = new Float32Array(n); 56 | while (n--) { 57 | arr[n] = array[n] / 255.0; 58 | } 59 | return arr; 60 | } 61 | 62 | const TEXT_LENGTH = 4; 63 | const CHAR_SET_LEN = 12; 64 | 65 | function array2Str (arr) { 66 | while (arr.length === 1) arr = arr[0]; 67 | let str = ''; 68 | const n = TEXT_LENGTH * CHAR_SET_LEN; 69 | for (let i = 0; i < n; i += CHAR_SET_LEN) { 70 | let m = arr[i]; 71 | let mk = 0; 72 | for (let k = 0; k < CHAR_SET_LEN; k++) { 73 | if (arr[i + k] > m) { 74 | m = arr[i + k]; 75 | mk = k; 76 | } 77 | } 78 | if (mk < 10) str += String.fromCharCode(mk + 48); 79 | else if (mk === 10) str += '+'; 80 | else if (mk === 11) str += '-'; 81 | } 82 | return str; 83 | } 84 | 85 | let modelUrl; 86 | 87 | async function loadModel (url) { 88 | if (modelUrl === url) return; 89 | console.info(`[${NAME}] loading model`, url); 90 | model = await tf.loadLayersModel(url); 91 | tf.tidy(() => { 92 | model.predict(tf.zeros([1, 40, 120, 1])); 93 | }); 94 | console.info(`[${NAME}] model loaded.`); 95 | modelUrl = url; 96 | } 97 | 98 | function predict (imageData) { 99 | const arr = normalization(filter(binarization(RGBA2Gray(imageData))).data); 100 | return tf.tidy(() => { 101 | return array2Str(model.predict(tf.tensor(arr, [1, 40, 120, 1])).arraySync()); 102 | }); 103 | } 104 | 105 | console.info(`[${NAME}] ready.`); 106 | 107 | return { 108 | NAME, 109 | loadModel, 110 | predict 111 | }; 112 | } 113 | -------------------------------------------------------------------------------- /update-log-old.md: -------------------------------------------------------------------------------- 1 | # 旧版更新日志 2 | 3 | > ### 2020-04-15 (Version 2.4.11) 4 | > 不会再到自己的应援团里签到导致死循环无限出错了 5 | > ### 2020-02-20 (Version 2.4.10) 6 | > 更新了舰队领奖接口 7 | > ### 2019-11-19 (Version 2.4.9) 8 | > 修复了签到功能异常的问题 9 | > ### 2019-11-02 (Version 2.4.8) 10 | > 修改了拦截弹幕服务器连接的方式 11 | > 修改了检测子脚本的方式 12 | > ### 2019-11-02 (Version 2.4.7) 13 | > 修复了直播页中的部分子页面无法正常显示的问题 14 | > ### 2019-10-24 (Version 2.4.6) 15 | > 修复了直播页排行榜无法正常显示的问题 16 | > ### 2019-10-12 (Version 2.4.5) 17 | > ### 2019-09-27 (Version 2.4.4) 18 | > 修复了无限重试连接弹幕服务器的问题 19 | > ### 2019-08-22 (Version 2.4.3) 20 | > 更新了抽奖接口 21 | > 修改了抽奖方式 22 | > ### 2019-08-16 (Version 2.4.2) 23 | > 修复了不能正确识别小黑屋的问题 24 | > ### 2019-07-29 (Version 2.4.1) 25 | > 更新了抽奖接口 26 | > ### 2019-07-02 (Version 2.4.0) 27 | > 修复了在未启用舰队领奖时仍然会领取奖励的问题 28 | > 增加了自动更换监听房间的功能 29 | > ### 2019-05-25 (Version 2.3.18) 30 | > ### 2019-05-25 (Version 2.3.17) 31 | > 修复了主动断开弹幕服务器连接后仍自动重连的问题 32 | > ### 2019-05-22 (Version 2.3.16) 33 | > 修复了自动完成任务、自动送礼、自动实物抽奖计时不正确的问题 34 | > ### 2019-05-03 (Version 2.3.15) 35 | > 修复了移动端心跳失效的问题 36 | > ### 2019-03-26 (Version 2.3.14) 37 | > 增加了自动分流连接弹幕服务器的功能 38 | > 修复了有时候会出现访问过快的问题 39 | > ### 2019-03-23 (Version 2.3.13) 40 | > 优化了访问API的方式 41 | > 修复了自动抽奖出现漏抽、漏领的问题 42 | > ### 2019-03-18 (Version 2.3.12) 43 | > ### 2019-03-18 (Version 2.3.11) 44 | > 修复了在网络环境差的情况下部分功能有时不能正常运行的问题 45 | > ### 2019-03-15 (Version 2.3.10) 46 | > 修复了舰队领奖、礼物抽奖在没有新的广播时不会参加已有抽奖的问题 47 | > 增加了隐藏抽奖提示框的功能 48 | > ### 2019-03-15 (Version 2.3.9) 49 | > 修复了舰队领奖、礼物抽奖中错误识别为小黑屋状态的问题 50 | > ### 2019-03-05 (Version 2.3.8) 51 | > 优化了领瓜子代码逻辑 52 | > 优化了脚本多开检测代码逻辑 53 | > 增加了清除脚本缓存的功能 54 | > 移除了重置为默认设置的功能 55 | > ### 2019-02-28 (Version 2.3.7) 56 | > 修复了一处实物抽奖中的逻辑问题 57 | > 修复了由时差引起的脚本执行不正常的问题 58 | > ### 2019-02-14 (Version 2.3.6) 59 | > 修复了一处因Cookie过期导致无限重试的问题 60 | > 优化了实物抽奖穷举算法 61 | > ### 2019-02-13 (Version 2.3.5) 62 | > 增加了新的分区支持 63 | > ### 2018-12-15 (Version 2.3.4) 64 | > ### 2018-12-04 (Version 2.3.3) 65 | > ### 2018-12-01 (Version 2.3.2) 66 | > ### 2018-11-27 (Version 2.3.1) 67 | > 修复了一些逻辑问题 68 | > ### 2018-11-26 (Version 2.3.0) 69 | > 修复了应援团签到不能正常使用的问题 70 | > 增加了自动完成每日奖励任务的功能 71 | > ### 2018-11-10 (Version 2.2.6) 72 | > ### 2018-11-10 (Version 2.2.5) 73 | > 增加了自动抽奖的内置延迟 74 | > 增加了对自动送礼功能的限制 75 | > ### 2018-11-07 (Version 2.2.4) 76 | > ### 2018-11-05 (Version 2.2.3) 77 | > 修复了有时出现抽奖参数不正确的问题 78 | > ### 2018-11-04 (Version 2.2.2) 79 | > 优化了代码逻辑 80 | > 修复了重复抽奖的问题 81 | > ### 2018-11-03 (Version 2.2.1) 82 | > 修复了舰队领奖不能正常工作的问题 83 | > 增加了自定义监听房间数的功能 84 | > 优化了代码逻辑 85 | > ### 2018-11-03 (Version 2.2.0) 86 | > 修改了自动抽奖->礼物抽奖的参与方式 87 | > 修改了自动抽奖->舰队领奖的参与方式 88 | > 移除了部分设置项及其功能 89 | > ### 2018-11-01 (Version 2.1.3) 90 | > 修复了不能礼物抽奖的问题 91 | > ### 2018-11-01 (Version 2.1.2) 92 | > 修复了舰队领奖有时候会疯狂重试的问题 93 | > ### 2018-10-31 (Version 2.1.1) 94 | > 修复了脚本可以在多个直播间页面运行的问题 95 | > 修复了舰长奖励无法领取的问题 96 | > ### 2018-10-30 (Version 2.1.0) 97 | > 优化了重试机制 98 | > 增加了对四大分区都监听的功能 99 | > (可能)修复了一些奇怪的bug 100 | > ### 2018-09-28 (Version 2.0.12) 101 | > 修改和移除了部分设置项 102 | > 提高了使用稳定性 103 | > ### 2018-08-26 (Version 2.0.11) 104 | > 修复了在有实物抽奖的直播间不能运行脚本的问题 105 | > ### 2018-08-21 (Version 2.0.10) 106 | > 增加了对超时情况的处理 107 | > 调整了部分代码逻辑以保证功能的持续运行 108 | > ### 2018-08-17 (Version 2.0.9) 109 | > 调整了实物抽奖代码逻辑 110 | > ### 2018-08-13 (Version 2.0.8) 111 | > 调整了一小部分代码 112 | > ### 2018-08-09 (Version 2.0.7) 113 | > 增加了自动刷新页面的功能 114 | > 增加了只允许单个直播间页面的脚本运行的功能 115 | > ### 2018-08-08 (Version 2.0.6) 116 | > 修复了自动领取瓜子有时候会停止不领取的逻辑问题 117 | > 修复了自动抽奖->实物抽奖有时候会疯狂访问接口的问题 118 | > 移除了银瓜子换硬币的旧接口及其设置项 119 | > ### 2018-08-05 (Version 2.0.5) 120 | > 修复了"防止自动完成任务、自动送礼在同日/同时间段多次运行的功能"无效的问题 121 | > 修复了在当日银瓜子领取完后,再次运行脚本后,自动领取银瓜子界面会显示"自动领取中"的问题 122 | > ### 2018-08-05 (Version 2.0.4) 123 | > 增加了防止自动签到、自动应援团签到、银瓜子换硬币、自动完成任务、自动送礼在同日/同时间段多次运行的功能 124 | > ### 2018-08-04 (Version 2.0.3) 125 | > 修复了自动抽奖部分设置项无效的问题 126 | > ### 2018-08-03 (Version 2.0.2) 127 | > 修复了自动抽奖->礼物抽奖在运行时会出现其他直播间声音的问题 128 | > 优化了自动抽奖->礼物抽奖的参与方式 129 | > ### 2018-08-03 (Version 2.0.1) 130 | > 优化了自动抽奖->礼物抽奖的参与方式 131 | > 增加了移动端心跳5分钟后自动进行一次完成任务的功能 132 | > 修复了自动送礼不能持续运行的问题 133 | > 增加了保存设置时会自动修正设置的功能 134 | > ### 2018-08-02 (Version 2.0.0) 135 | > 增加了自动应援团签到的功能 136 | > 增加了移动端心跳的功能(可配合自动完成任务来完成双端观看) 137 | > 增加了银瓜子换硬币的旧API兑换功能 138 | > 增加了自动抽奖->总督领奖的功能 139 | > 增加了自动抽奖->实物抽奖的功能 140 | > 重新命名了自动抽奖功能,现为自动抽奖->礼物抽奖功能 141 | > 增加了自动抽奖->礼物抽奖的自定义设置 142 | > 修改了自动抽奖->礼物抽奖的检测方式 143 | > 修改了自动抽奖->礼物抽奖的参与方式 144 | > 降低了自动抽奖->礼物抽奖功能的内存占用 145 | > 调整了浮动提示的位置 146 | > 优化了设置界面的生成方式 147 | > 移除了未实现的节奏风暴代码部分 148 | > 在GreasyFork上恢复脚本 149 | > ### 2018-05-19 150 | > 更新API,优化代码,第二次尝试规避封号 151 | > ### 2018-05-12 152 | > 更新API相关,优化逻辑,尝试规避封号 153 | > ### 2018-04-30 154 | > 更新验证码识别算法,修复无法正常领取瓜子的问题 155 | > 增加新的设置项 156 | > ### 2018-03-31 157 | > 更新总督API相关(缺少数据,功能未实装) 158 | > ### 2018-03-10 159 | > 在Github上创建脚本仓库 160 | > 更新领取银瓜子的功能 161 | > ### 2018-02-15 162 | > 修正了节奏风暴有关功能的实现逻辑 163 | > 在GreasyFork上删除脚本 164 | > ### 2018-02-12 165 | > 解决了不能在任意直播间参加抽奖的问题(实验性)(需在设置中勾选) 166 | > ### 2018-02-10 167 | > 修复了不能领取银瓜子的问题 168 | > ### 2018-02-04 169 | > 增加了银瓜子兑换硬币的功能 170 | > 修改了默认设置 171 | > 调整了设置界面的位置 172 | > ### 2018-01-28 173 | > 调整了设置界面 174 | > 修改了送礼逻辑 175 | > ### 2018-01-22 176 | > 增加了可视设置界面和保存设置的功能 177 | > 增加了新的设置项 178 | > 移除了使用银瓜子送礼的功能 179 | > 修复了会自动送出永久期限礼物的bug(未测试) 180 | > 优化了领取瓜子信息的界面设计 181 | > 优化了抽奖计时逻辑 182 | > ### 2018-01-20 183 | > 优化了抽奖功能,调整了浮动提示 184 | > ### 2018-01-18 185 | > 修复了不能正常参加新春抽奖的问题 186 | > ### 2018-01-13 187 | > 修复了领取宝箱的有关bug 188 | > ### 2018-01-12 189 | > 增加了自定义送礼等功能,优化了脚本逻辑 190 | > ### 2018-01-09 191 | > 修复了脚本不能运行的问题 192 | > ### 2018-01-06 193 | > 在GreasyFork上创建脚本 194 | -------------------------------------------------------------------------------- /update-log.md: -------------------------------------------------------------------------------- 1 | # 更新日志 2 | 3 | + 3.1.11 (2020-01-17) 4 | + 修复给满级勋章对应的应援团签到时总跳出“应援失败”的问题。 5 | + 3.1.10 (2020-12-17) 6 | + 修复在检测到重复运行后会导致直播间页面异常的问题。 7 | + 3.1.9 (2020-12-15) 8 | + 修复避免挂机检测功能无效的问题。 9 | + 优化金宝箱参加算法。 10 | + 3.1.8 (2020-11-19) 11 | + 增加避免挂机检测功能。 12 | + 3.1.7 (2020-11-06) 13 | + 增加每日奖励-投币功能。 14 | + 3.1.6 (2020-11-01) 15 | + 优化金宝箱参加算法。 16 | + 3.1.5 (2020-09-07) 17 | + 修复时区自适应错误的问题。 18 | + 修复脚本在特殊直播间运行时出现的问题。 19 | + 优化脚本加载逻辑。 20 | + 3.1.4 (2020-08-24) 21 | + 修复脚本不能在特殊直播间运行的问题。 22 | + 3.1.3 (2020-08-21) 23 | + 修复金宝箱抽奖重复参加的问题。 24 | + 3.1.2 (2020-08-19) 25 | + 优化提示。 26 | + 支持在Firefox中使用原先不能使用的功能。 27 | + 3.1.1 (2020-08-19) 28 | + 3.1.0 (2020-08-19) 29 | + 修复金宝箱抽奖算法问题。 30 | + 增加部分主站每日奖励功能。 31 | + 3.0.0 (2020-07-26) 32 | + 脚本完全重写 33 | --------------------------------------------------------------------------------