Skip to content

Commit 12b3f7d

Browse files
committed
feat: FlareSolverr
close #493
1 parent 1d5e0b9 commit 12b3f7d

File tree

11 files changed

+259
-26
lines changed

11 files changed

+259
-26
lines changed

CHANGELOG.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,17 @@
22

33
## 2025
44

5+
### 10-01 v3.21.0
6+
7+
- 方舟公招计职业 tag 没有显示干员两个字
8+
- 增大九宫图合并图片长宽上限
9+
- 支持使用 [FlareSolverr](https://github.com/FlareSolverr/FlareSolverr) 解决 ascii2d 或 nHentai 的 cf challenge [#493](../../issues/493)
10+
- 移除 `bot.cfTLSVersion` 配置及相关能力
11+
- 配置项变更
12+
- A `flaresolverr`
13+
- M `bot.getDoujinDetailFromNhentaiMirrorSite` 默认值 `"https://nhentai.xxx"` -> `""`
14+
- D `bot.cfTLSVersion`
15+
516
### 06-08 v3.20.2
617

718
- 修复方舟公招计算漏掉了职业 tag 的问题

config.default.jsonc

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -74,13 +74,17 @@
7474
"useAscii2dWhenFailed": false,
7575
// 是否优先从本地上传图片到 ascii2d 搜索(否时使用 ascii2d 的 URL API)
7676
"ascii2dLocalUpload": false,
77-
// 是否使用 Puppeteer 请求 ascii2d 以绕过 cf js challenge,详情请看“wiki-配置文件说明-使用 Puppeteer 绕过 cf js challenge”(启用时 ascii2dLocalUpload 配置将不生效)
77+
// 是否使用 Puppeteer 请求 ascii2d 以绕过 cf js challenge,启用时 `ascii2dLocalUpload` 不生效,详情请看“wiki-配置文件说明-使用 Puppeteer 绕过 cf js challenge”
78+
// 不再建议使用,建议使用 FlareSolverr,见设置项 `flaresolverr`
7879
"ascii2dUsePuppeteer": false,
7980
// saucenao 搜到本子时是否进一步去 nHentai 搜索
8081
"getDoujinDetailFromNhentai": true,
81-
// nHentai 主站有 cf js challenge 无法直接搜索,可以配置使用镜像站搜索,但需注意镜像站收录本子的进度通常落后于主站,设置空字符串则去主站搜索
82-
"getDoujinDetailFromNhentaiMirrorSite": "https://nhentai.xxx",
82+
// 若 nHentai 主站有 cf js challenge 无法直接搜索,可以配置使用镜像站搜索,如 https://nhentai.xxx
83+
// 但需注意镜像站收录本子的进度通常落后于主站,设置空字符串则去主站搜索
84+
// 不再建议使用,建议使用 FlareSolverr,见设置项 `flaresolverr`
85+
"getDoujinDetailFromNhentaiMirrorSite": "",
8386
// 是否使用 Puppeteer 请求 nHentai 以绕过 cf js challenge,试验阶段,目前可能不解决问题,使用 getDoujinDetailFromNhentaiMirrorSite 时该配置不生效,详情请看“wiki-配置文件说明-使用 Puppeteer 绕过 cf js challenge”
87+
// 不再建议使用,建议使用 FlareSolverr,见设置项 `flaresolverr`
8488
"nHentaiUsePuppeteer": false,
8589
// 是否开启搜图反馈,成功接收图片会回复 bot.replys.searchFeedback,可用于判断图片是否被屏蔽
8690
"searchFeedback": false,
@@ -394,6 +398,19 @@
394398
"whiteGroup": []
395399
}
396400
},
401+
// https://github.com/FlareSolverr/FlareSolverr
402+
"flaresolverr": {
403+
// API 地址
404+
"url": "http://127.0.0.1:8191",
405+
// 会话名,用于持久化,为空则不使用,不建议为空
406+
"session": "cqps",
407+
// 代理,见 FlareSolverr 文档;注意:请求缩略图会使用 `bot.proxy` 代理而不是这个代理
408+
"proxy": null,
409+
// 为 ascii2d 请求使用,启用时 `ascii2dLocalUpload` 不生效
410+
"enableForAscii2d": false,
411+
// 为 nHentai 请求使用
412+
"enableForNHentai": false
413+
},
397414
// saucenao 自定义 host,格式:[protocol://]host[:port],不写明协议默认为 https
398415
"saucenaoHost": "saucenao.com",
399416
// saucenao APIKEY,必填,否则无法使用 saucenao 搜图

package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
"deep-freeze": "^0.0.1",
4646
"dotenv": "^16.3.1",
4747
"escape-string-regexp": "^5.0.0",
48+
"flaresolverr-client": "^1.2.0",
4849
"form-data": "^3.0.1",
4950
"fs-extra": "^11.2.0",
5051
"get-stream": "^6.0.1",
@@ -71,12 +72,14 @@
7172
"random-seed": "^0.3.0",
7273
"remove-markdown": "^0.5.0",
7374
"socks-proxy-agent": "^7.0.0",
75+
"tough-cookie": "^6.0.0",
7476
"url-join": "^5.0.0"
7577
},
7678
"devDependencies": {
7779
"@tsuk1ko/postversion": "^1.0.2",
7880
"@types/fs-extra": "^11.0.4",
7981
"@types/lodash-es": "^4.17.12",
82+
"@types/node": "^22.18.8",
8083
"eslint": "^8.56.0",
8184
"eslint-config-prettier": "^9.1.0",
8285
"eslint-config-standard": "^17.1.0",

src/plugin/ascii2d.mjs

Lines changed: 34 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ import FormData from 'form-data';
44
import _ from 'lodash-es';
55
import Axios from '../utils/axiosProxy.mjs';
66
import CQ from '../utils/CQcode.mjs';
7+
import { flareSolverr } from '../utils/flaresolverr.mjs';
78
import { getCqImg64FromUrl, getAntiShieldedCqImg64FromUrl } from '../utils/image.mjs';
9+
import { imgAntiShieldingFromArrayBuffer } from '../utils/imgAntiShielding.mjs';
810
import logError from '../utils/logError.mjs';
911
import { retryAsync } from '../utils/retry.mjs';
1012
import { confuseURL } from '../utils/url.mjs';
@@ -21,10 +23,9 @@ async function doSearch(img, snLowAcc = false) {
2123
const hosts = global.config.ascii2dHost;
2224
let host = hosts[hostsI++ % hosts.length];
2325
if (!/^https?:\/\//.test(host)) host = `https://${host}`;
24-
const callApi = global.config.bot.ascii2dUsePuppeteer ? callAscii2dUrlApiWithPuppeteer : callAscii2dApi;
2526
const { colorURL, colorDetail } = await retryAsync(
2627
async () => {
27-
const ret = await callApi(host, img);
28+
const ret = await callAscii2dApi(host, img);
2829
const colorURL = ret.request.res.responseUrl;
2930
if (!colorURL.includes('/color/')) {
3031
const $ = Cheerio.load(ret.data, { decodeEntities: false });
@@ -41,9 +42,7 @@ async function doSearch(img, snLowAcc = false) {
4142
e => typeof e !== 'string' && String(_.get(e, 'response.data')).trim() === 'first byte timeout',
4243
);
4344
const bovwURL = colorURL.replace('/color/', '/bovw/');
44-
const bovwDetail = await (global.config.bot.ascii2dUsePuppeteer ? getAscii2dWithPuppeteer : Axios.get)(bovwURL).then(
45-
r => getDetail(r, host),
46-
);
45+
const bovwDetail = await requestGet(bovwURL).then(r => getDetail(r, host));
4746
const colorRet = await getResult(colorDetail, snLowAcc);
4847
const bovwRet = await getResult(bovwDetail, snLowAcc);
4948
return {
@@ -53,10 +52,26 @@ async function doSearch(img, snLowAcc = false) {
5352
};
5453
}
5554

55+
function throwDeviceImageError() {
56+
// eslint-disable-next-line no-throw-literal
57+
throw '部分图片无法获取,如为转发请尝试保存后再手动发送,或使用其他设备手动发送';
58+
}
59+
5660
/**
61+
* @param {string} host
5762
* @param {MsgImage} img
5863
*/
5964
async function callAscii2dApi(host, img) {
65+
if (global.config.flaresolverr.enableForAscii2d) {
66+
if (!img.isUrlValid) throwDeviceImageError();
67+
return flareSolverr.get(`${host}/search/url/${img.url}`);
68+
}
69+
70+
if (global.config.bot.ascii2dUsePuppeteer) {
71+
if (!img.isUrlValid) throwDeviceImageError();
72+
return getAscii2dWithPuppeteer(`${host}/search/url/${img.url}`);
73+
}
74+
6075
if (global.config.bot.ascii2dLocalUpload || !img.isUrlValid) {
6176
const path = await img.getPath();
6277
if (path) {
@@ -70,19 +85,17 @@ async function callAscii2dApi(host, img) {
7085
return Axios.get(`${host}/search/url/${img.url}`);
7186
}
7287

73-
// eslint-disable-next-line no-throw-literal
74-
throw '部分图片无法获取,如为转发请尝试保存后再手动发送,或使用其他设备手动发送';
88+
throwDeviceImageError();
7589
}
7690

77-
/**
78-
* @param {MsgImage} img
79-
*/
80-
function callAscii2dUrlApiWithPuppeteer(host, img) {
81-
if (!img.isUrlValid) {
82-
// eslint-disable-next-line no-throw-literal
83-
throw '部分图片无法获取,如为转发请尝试保存后再手动发送,或使用其他设备手动发送';
91+
function requestGet(url) {
92+
if (global.config.flaresolverr.enableForAscii2d) {
93+
return flareSolverr.get(url);
8494
}
85-
return getAscii2dWithPuppeteer(`${host}/search/url/${img.url}`);
95+
if (global.config.bot.ascii2dUsePuppeteer) {
96+
return getAscii2dWithPuppeteer(url);
97+
}
98+
return Axios.get(url);
8699
}
87100

88101
async function getAscii2dWithPuppeteer(url) {
@@ -140,8 +153,12 @@ async function getResult({ url, title, author, thumbnail, author_url }, snLowAcc
140153
const texts = [CQ.escape(author ? `「${title}」/「${author}」` : title)];
141154
if (thumbnail && !(global.config.bot.hideImg || (snLowAcc && global.config.bot.hideImgWhenLowAcc))) {
142155
const mode = global.config.bot.antiShielding;
143-
if (mode > 0) texts.push(await getAntiShieldedCqImg64FromUrl(thumbnail, mode));
144-
else texts.push(await getCqImg64FromUrl(thumbnail));
156+
if (global.config.flaresolverr.enableForAscii2d) {
157+
const img = await flareSolverr.getImage(thumbnail);
158+
texts.push(CQ.img64(mode > 0 ? await imgAntiShieldingFromArrayBuffer(img, mode) : img));
159+
} else {
160+
texts.push(mode > 0 ? await getAntiShieldedCqImg64FromUrl(thumbnail, mode) : await getCqImg64FromUrl(thumbnail));
161+
}
145162
}
146163
if (url) texts.push(CQ.escape(confuseURL(url)));
147164
if (author_url) texts.push(`Author: ${CQ.escape(confuseURL(author_url))}`);

src/plugin/nhentai.mjs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import * as Cheerio from 'cheerio';
22
import NHentaiApi from 'nhentai-api';
33
import Axios from '../utils/axiosProxy.mjs';
4+
import { flareSolverr } from '../utils/flaresolverr.mjs';
45

56
const exts = {
67
j: 'jpg',
@@ -29,7 +30,11 @@ async function getJsonWithPuppeteer(url) {
2930
}
3031

3132
async function getDetailFromNHentaiAPI(name) {
32-
const get = global.config.bot.nHentaiUsePuppeteer ? getJsonWithPuppeteer : Axios.get;
33+
const get = global.config.flaresolverr.enableForNHentai
34+
? flareSolverr.getJSON
35+
: global.config.bot.nHentaiUsePuppeteer
36+
? getJsonWithPuppeteer
37+
: Axios.get;
3338
let json = await get(getSearchURL(`${name} chinese`)).then(r => r.data);
3439
if (json.result.length === 0) {
3540
json = await get(getSearchURL(name)).then(r => r.data);
@@ -48,7 +53,9 @@ async function getDetailFromWebsite(origin, name) {
4853
}
4954

5055
async function _getDetailFromWebsite(origin, name) {
51-
const { data } = await Axios.get(`${origin}/search/?q=${encodeURIComponent(name)}`, { responseType: 'text' });
56+
const get = global.config.flaresolverr.enableForNHentai ? flareSolverr.get : Axios.get;
57+
58+
const { data } = await get(`${origin}/search/?q=${encodeURIComponent(name)}`, { responseType: 'text' });
5259
const $ = Cheerio.load(data);
5360

5461
const gallery = $('.gallery').get(0);

src/setup/config.mjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ const noCheckPaths = new Set([
4343
'bot.chatgpt.prependMessages',
4444
'bot.chatgpt.additionParams',
4545
'bot.chatgpt.overrides',
46+
'flaresolverr.proxy',
4647
]);
4748

4849
function recursiveCopy(c, dc, cc, dcc, parentPath = '') {

src/types/config.d.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ export declare interface Config {
22
autoUpdateConfig: boolean;
33
cqws: Cqw;
44
bot: Bot;
5+
flaresolverr: Flaresolverr;
56
saucenaoHost: string;
67
saucenaoApiKey: string;
78
whatanimeHost: string;
@@ -10,6 +11,14 @@ export declare interface Config {
1011
setuApiHost: string;
1112
}
1213

14+
declare interface Flaresolverr {
15+
url: string;
16+
session: string;
17+
proxy: object;
18+
enableForAscii2d: boolean;
19+
enableForNHentai: boolean;
20+
}
21+
1322
declare interface Bot {
1423
debug: boolean;
1524
admin: number;

src/utils/CQcode.mjs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,10 +166,13 @@ export default class CQ {
166166

167167
/**
168168
* CQ码 Base64 图片
169-
* @param {string} base64 图片 Base64
169+
* @param {string|ArrayBuffer} base64 图片 Base64
170170
* @param {'flash'|'show'} [type] 类型
171171
*/
172172
static img64(base64, type) {
173+
if (typeof base64 !== 'string') {
174+
base64 = Buffer.from(base64).toString('base64');
175+
}
173176
return new CQ('image', { file: `base64://${base64}`, type }).toString();
174177
}
175178

0 commit comments

Comments
 (0)