├── .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 |    [](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 | + 支付宝 =>  微信 => 
58 |
59 | ----------------------------------
60 |
61 | ## 开源许可证
62 |
63 | [](https://github.com/SeaLoong/BLRHH/blob/master/LICENSE)
64 |
65 | + 本项目中使用到的库
66 | + [BLUL](https://github.com/SeaLoong/BLUL) - [](https://github.com/SeaLoong/BLUL/blob/master/LICENSE)
67 | + [tfjs](https://github.com/tensorflow/tfjs) - [](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 | - 关于违规使用软件在直播间抢辣条的处罚公告
11 |
12 |
13 | 用户使用条款
14 | 您应当遵守下列条款:
15 |
16 | 使用本程序而导致B站对您进行处罚的责任由您承担,我们对此不负任何责任。
17 | 仔细阅读使用说明和帮助提示,在已有的说明和提示不能解决问题时再寻求我们。
18 | 不得出售本程序以及程序中的任何源代码,或以其他方式使他人付费使用本程序。
19 | 不得滥用需要第三方提供支持的功能,不得对第三方提供的服务进行盗用和攻击。
20 | 不得在本项目中贡献任何形式的恶意代码、未经允许的推广代码、未经允许的统计代码、无效代码和其他违反法律法规的代码。
21 |
22 |
23 | 我们的隐私政策
24 | 在程序使用过程中,我们可能会收集和使用您的用户信息。
25 | 我们收集的信息包含:
26 |
27 | 浏览器标识和运行环境信息。
28 | 本程序运行时所在页面信息。
29 | 使用本程序时用户自行填写的信息。
30 |
31 | 我们怎样使用您的信息:
32 |
33 | 用于对B站API的请求。
34 | 用于使用需要第三方提供支持的功能。
35 | 用于数据统计。
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/src/html/notice.html:
--------------------------------------------------------------------------------
1 | 版本更新说明
2 |
3 | -
4 |
3.1.11 (2020-01-17)
5 |
6 | - 修复给满级勋章对应的应援团签到时总跳出“应援失败”的问题。
7 |
8 |
9 |
10 | 其他说明
11 |
12 | - 银瓜子宝箱下线公告
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 |
--------------------------------------------------------------------------------