koishi-plugin-quark-search 1.1.1 → 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/index.js +292 -41
- package/package.json +1 -1
package/lib/index.js
CHANGED
|
@@ -27,25 +27,35 @@ __export(src_exports, {
|
|
|
27
27
|
module.exports = __toCommonJS(src_exports);
|
|
28
28
|
var import_koishi = require("koishi");
|
|
29
29
|
var name = "quark-search";
|
|
30
|
+
var INTERNAL_CONFIG = {
|
|
31
|
+
// 搜索 API 配置
|
|
32
|
+
searchApiUrl: "https://wzapi.com/api/jhsj",
|
|
33
|
+
searchApiMethod: "GET",
|
|
34
|
+
searchApiParams: '{"kw": "{keyword}", "cloud_types": "quark"}',
|
|
35
|
+
searchApiHeaders: '{"User-Agent": "Mozilla/5.0"}',
|
|
36
|
+
searchResultPath: "data.merged_by_type.quark",
|
|
37
|
+
searchTitleField: "note",
|
|
38
|
+
searchUrlField: "url",
|
|
39
|
+
// 备用搜索 API 配置
|
|
40
|
+
enableBackupApi: true,
|
|
41
|
+
backupApiUrl: "https://api.iyuns.com/api/wpysso",
|
|
42
|
+
backupApiMethod: "GET",
|
|
43
|
+
backupApiParams: '{"kw": "{keyword}"}',
|
|
44
|
+
backupApiHeaders: '{"User-Agent": "Mozilla/5.0"}',
|
|
45
|
+
backupResultPath: "data",
|
|
46
|
+
backupTitleField: "title",
|
|
47
|
+
backupUrlField: "url",
|
|
48
|
+
// 夸克网盘配置
|
|
49
|
+
quarkCookie: "ctoken=Ji1ntkR_JnxaFREUHI3i_bWN; b-user-id=f67e37a1-88c8-1032-9ade-36ba59c3a3d6; grey-id=f8b75ed1-cca3-3e76-e5b6-d3738d297b1a; grey-id.sig=GQxvnT8Dswjp_zDD6aThHQ2wzmMg6WeoQH1C1DAYwLI; isQuark=true; isQuark.sig=hUgqObykqFom5Y09bll94T1sS9abT1X-4Df_lzgl8nM; __wpkreporterwid_=d8a622dd-0e24-4bbe-0ef0-0322a85820a8; _UP_A4A_11_=wb9d01842d77494ea029a3143597a7cf; _UP_D_=pc; _UP_F7E_8D_=iN3APNXwvEW2o0BxtSubcksem8D%2BGJzLUdRO7YsGUI%2BibipNZ4wWoKA3nkVDHeUjK77U6xYgqMbXDik4ZZxmO%2B6jibaMeh%2F15GPrrCl5M87ivs0EP%2FjAQTQimMgEdat62Byd22%2BZGM6asBHyZ16ADgtZHstF5aenvOTidzNw8s%2FWtKAIxWbnCzZn4%2FJMBUubVJq284hh5AUHlq0SG81EVeFtarJkjW0Zf72DnVYx1vjdoxii5ItgZhU4wR0Pq7NklczEGdRq2nIAcu7v22Uw2o%2FxMY0xBdeC9Korm5%2FNHnxl6K%2Bd6FXSoT9a3XIMQO359auZPiZWzrNlZe%2BqnOahXcx7KAhQIRqSOapSmL4ygJor4r5isJhRuDoXy7vJAVuH%2FRDtEJJ8rZTq0BdC23Bz%2B0MrsdgbK%2BiW; __pus=eb7475d573854af9ec600760777c9efeAATRlIxMqw+qjGbQXvEkKjzqJypS2raEXMgceVWvtp5ch5HuLp877gI7eGfLImjwPSUBlAengL6KkRH602elss3K; __kp=5384a860-dbe2-11f0-8a99-9d332385f208; __kps=AAQhKYwaLxGO5KwkGMeTx0c4; __ktd=LBymOZO2+WkiWIOIoQLJUA==; __uid=AAQhKYwaLxGO5KwkGMeTx0c4; web-grey-id=f26686a9-8b74-6856-e499-1fd0e830cda1; web-grey-id.sig=INvEejvSFdDbVCmV7SlpvOYVogu_fXiQzj0uqNazVcg; __puus=a48229be9a6c92724dd4a2feb419dc08AASLA6iAGC1+Y2mGjf7DY/IeWnvZUhESv1x+96Da7rOuy9skRlpjgX/lnNQrj8zmVdoelVwiDtR41ebIZA37lHY88e1mkO8eftgXuY8FjgtnGomkoHEurMi4bt4HsP6WJ7ZRFs9iy6KXmzxmJqwW7BNTjdL9dEvyyvrVjZLivPu7/KVWt6fQXmrsokrJ1dxH1bYlp5LiQhKUI464VFnCtgG3",
|
|
50
|
+
saveFolderId: "eb5cf05a4205413ab29071df8fa1834a",
|
|
51
|
+
expiredType: 1,
|
|
52
|
+
// 广告过滤配置
|
|
53
|
+
enableAdFilter: true,
|
|
54
|
+
deleteKeywords: ["必看", "资源", "添加", "扫一扫", "扩容", "免费", "更多", "热门", "电影"],
|
|
55
|
+
replaceRules: {},
|
|
56
|
+
scanDepth: 3
|
|
57
|
+
};
|
|
30
58
|
var Config = import_koishi.Schema.object({
|
|
31
|
-
searchApiUrl: import_koishi.Schema.string().required().description("搜索 API 地址,例如:https://wzapi.com/api/jhsj"),
|
|
32
|
-
searchApiMethod: import_koishi.Schema.union(["GET", "POST"]).default("GET").description("请求方式"),
|
|
33
|
-
searchApiParams: import_koishi.Schema.string().default('{"kw": "{keyword}"}').description("请求参数,JSON格式,{keyword}会被替换为搜索关键词"),
|
|
34
|
-
searchApiHeaders: import_koishi.Schema.string().default('{"User-Agent": "Mozilla/5.0"}').description("请求头,JSON格式"),
|
|
35
|
-
searchResultPath: import_koishi.Schema.string().default("data").description("搜索结果在响应中的路径,用点分隔,如 data.list"),
|
|
36
|
-
searchTitleField: import_koishi.Schema.string().default("title").description("标题字段名"),
|
|
37
|
-
searchUrlField: import_koishi.Schema.string().default("url").description("链接字段名"),
|
|
38
|
-
enableBackupApi: import_koishi.Schema.boolean().default(false).description("启用备用搜索API(当主API失败或无结果时自动切换)"),
|
|
39
|
-
backupApiUrl: import_koishi.Schema.string().description("备用搜索 API 地址"),
|
|
40
|
-
backupApiMethod: import_koishi.Schema.union(["GET", "POST"]).default("GET").description("备用API请求方式"),
|
|
41
|
-
backupApiParams: import_koishi.Schema.string().default('{"kw": "{keyword}"}').description("备用API请求参数,JSON格式,{keyword}会被替换为搜索关键词"),
|
|
42
|
-
backupApiHeaders: import_koishi.Schema.string().default('{"User-Agent": "Mozilla/5.0"}').description("备用API请求头,JSON格式"),
|
|
43
|
-
backupResultPath: import_koishi.Schema.string().default("data").description("备用API搜索结果在响应中的路径,用点分隔"),
|
|
44
|
-
backupTitleField: import_koishi.Schema.string().default("title").description("备用API标题字段名"),
|
|
45
|
-
backupUrlField: import_koishi.Schema.string().default("url").description("备用API链接字段名"),
|
|
46
|
-
quarkCookie: import_koishi.Schema.string().required().role("textarea").description("夸克网盘 Cookie(必填,从浏览器获取)"),
|
|
47
|
-
saveFolderId: import_koishi.Schema.string().default("0").description("转存目标文件夹ID,0表示根目录"),
|
|
48
|
-
expiredType: import_koishi.Schema.number().default(1).description("分享有效期:1=永久,2=7天,3=1天"),
|
|
49
59
|
watchGroups: import_koishi.Schema.array(String).default([]).description("监控的群号列表,留空则监控所有群"),
|
|
50
60
|
limit: import_koishi.Schema.number().default(10).description("每次搜索返回的最大结果数"),
|
|
51
61
|
sessionTimeout: import_koishi.Schema.number().default(120).description("搜索会话超时时间(秒)"),
|
|
@@ -120,8 +130,9 @@ async function filterValidLinks(ctx, items, logger) {
|
|
|
120
130
|
if (valid) {
|
|
121
131
|
item.valid = true;
|
|
122
132
|
validItems.push(item);
|
|
133
|
+
logger.debug(`链接有效: ${item.title} - ${item.url}`);
|
|
123
134
|
} else {
|
|
124
|
-
logger.debug(`链接失效: ${item.title} - ${message}`);
|
|
135
|
+
logger.debug(`链接失效: ${item.title} - ${item.url} - ${message}`);
|
|
125
136
|
}
|
|
126
137
|
}
|
|
127
138
|
return validItems;
|
|
@@ -129,10 +140,11 @@ async function filterValidLinks(ctx, items, logger) {
|
|
|
129
140
|
__name(filterValidLinks, "filterValidLinks");
|
|
130
141
|
function apply(ctx, config) {
|
|
131
142
|
const logger = ctx.logger("quark-search");
|
|
143
|
+
const fullConfig = { ...INTERNAL_CONFIG, ...config };
|
|
132
144
|
const cleanupInterval = setInterval(() => {
|
|
133
145
|
const now = Date.now();
|
|
134
146
|
for (const [key, session] of searchSessions) {
|
|
135
|
-
if (now - session.timestamp >
|
|
147
|
+
if (now - session.timestamp > fullConfig.sessionTimeout * 1e3) {
|
|
136
148
|
searchSessions.delete(key);
|
|
137
149
|
}
|
|
138
150
|
}
|
|
@@ -143,7 +155,7 @@ function apply(ctx, config) {
|
|
|
143
155
|
});
|
|
144
156
|
ctx.guild().on("message", async (session) => {
|
|
145
157
|
const groupId = session.guildId || session.channelId;
|
|
146
|
-
if (
|
|
158
|
+
if (fullConfig.watchGroups.length > 0 && !fullConfig.watchGroups.includes(groupId)) {
|
|
147
159
|
return;
|
|
148
160
|
}
|
|
149
161
|
const content = session.content?.trim() || "";
|
|
@@ -152,23 +164,23 @@ function apply(ctx, config) {
|
|
|
152
164
|
const internal = bot.internal;
|
|
153
165
|
if (/^\d+$/.test(content)) {
|
|
154
166
|
const userSession = searchSessions.get(sessionKey);
|
|
155
|
-
if (userSession && Date.now() - userSession.timestamp <
|
|
167
|
+
if (userSession && Date.now() - userSession.timestamp < fullConfig.sessionTimeout * 1e3) {
|
|
156
168
|
const index = parseInt(content, 10);
|
|
157
169
|
if (index >= 1 && index <= userSession.validResults.length) {
|
|
158
|
-
await handleResourceSelection(ctx,
|
|
170
|
+
await handleResourceSelection(ctx, fullConfig, internal, groupId, session.userId, userSession, index, logger);
|
|
159
171
|
return;
|
|
160
172
|
}
|
|
161
173
|
}
|
|
162
174
|
}
|
|
163
175
|
if (content === "下一页" || content === "上一页") {
|
|
164
176
|
const userSession = searchSessions.get(sessionKey);
|
|
165
|
-
if (userSession && Date.now() - userSession.timestamp <
|
|
177
|
+
if (userSession && Date.now() - userSession.timestamp < fullConfig.sessionTimeout * 1e3) {
|
|
166
178
|
const totalPages = Math.ceil(userSession.validResults.length / PAGE_SIZE);
|
|
167
179
|
if (content === "下一页") {
|
|
168
180
|
if (userSession.currentPage < totalPages) {
|
|
169
181
|
userSession.currentPage++;
|
|
170
182
|
userSession.timestamp = Date.now();
|
|
171
|
-
await sendPageResults(ctx, internal, groupId, session.userId, userSession,
|
|
183
|
+
await sendPageResults(ctx, internal, groupId, session.userId, userSession, fullConfig.sessionTimeout, logger);
|
|
172
184
|
} else {
|
|
173
185
|
await internal.sendGroupMsg(groupId, [
|
|
174
186
|
{ type: "at", data: { qq: session.userId } },
|
|
@@ -179,7 +191,7 @@ function apply(ctx, config) {
|
|
|
179
191
|
if (userSession.currentPage > 1) {
|
|
180
192
|
userSession.currentPage--;
|
|
181
193
|
userSession.timestamp = Date.now();
|
|
182
|
-
await sendPageResults(ctx, internal, groupId, session.userId, userSession,
|
|
194
|
+
await sendPageResults(ctx, internal, groupId, session.userId, userSession, fullConfig.sessionTimeout, logger);
|
|
183
195
|
} else {
|
|
184
196
|
await internal.sendGroupMsg(groupId, [
|
|
185
197
|
{ type: "at", data: { qq: session.userId } },
|
|
@@ -201,12 +213,12 @@ function apply(ctx, config) {
|
|
|
201
213
|
}
|
|
202
214
|
logger.info(`收到搜索请求: ${keyword}, 群: ${groupId}, 用户: ${session.userId}`);
|
|
203
215
|
try {
|
|
204
|
-
let results = await searchResources(ctx,
|
|
216
|
+
let results = await searchResources(ctx, fullConfig, keyword, logger, false);
|
|
205
217
|
let usedBackupApi = false;
|
|
206
|
-
if (!results.length &&
|
|
218
|
+
if (!results.length && fullConfig.enableBackupApi && fullConfig.backupApiUrl) {
|
|
207
219
|
logger.info(`主API无结果,尝试备用API...`);
|
|
208
220
|
try {
|
|
209
|
-
results = await searchResources(ctx,
|
|
221
|
+
results = await searchResources(ctx, fullConfig, keyword, logger, true);
|
|
210
222
|
usedBackupApi = true;
|
|
211
223
|
if (results.length > 0) {
|
|
212
224
|
logger.info(`备用API搜索成功,找到 ${results.length} 个资源`);
|
|
@@ -224,6 +236,9 @@ function apply(ctx, config) {
|
|
|
224
236
|
}
|
|
225
237
|
const apiSource = usedBackupApi ? "(备用API)" : "";
|
|
226
238
|
logger.info(`搜索到 ${results.length} 个${apiSource},验证中...`);
|
|
239
|
+
results.forEach((item, index) => {
|
|
240
|
+
logger.debug(`[${index + 1}] ${item.title} - ${item.url}`);
|
|
241
|
+
});
|
|
227
242
|
const firstBatch = results.slice(0, PAGE_SIZE);
|
|
228
243
|
const validFirstBatch = await filterValidLinks(ctx, firstBatch, logger);
|
|
229
244
|
if (validFirstBatch.length === 0) {
|
|
@@ -250,12 +265,62 @@ function apply(ctx, config) {
|
|
|
250
265
|
validating: false
|
|
251
266
|
};
|
|
252
267
|
searchSessions.set(sessionKey, userSession);
|
|
253
|
-
await sendPageResults(ctx, internal, groupId, session.userId, userSession,
|
|
268
|
+
await sendPageResults(ctx, internal, groupId, session.userId, userSession, fullConfig.sessionTimeout, logger);
|
|
254
269
|
} catch (error) {
|
|
255
270
|
logger.error("搜索失败:", error);
|
|
271
|
+
if (fullConfig.enableBackupApi && fullConfig.backupApiUrl) {
|
|
272
|
+
logger.info("主API失败,尝试备用API...");
|
|
273
|
+
try {
|
|
274
|
+
const results = await searchResources(ctx, fullConfig, keyword, logger, true);
|
|
275
|
+
if (results.length > 0) {
|
|
276
|
+
logger.info(`备用API搜索成功,找到 ${results.length} 个资源`);
|
|
277
|
+
const apiSource = "(备用API)";
|
|
278
|
+
logger.info(`搜索到 ${results.length} 个${apiSource},验证中...`);
|
|
279
|
+
results.forEach((item, index) => {
|
|
280
|
+
logger.debug(`[${index + 1}] ${item.title} - ${item.url}`);
|
|
281
|
+
});
|
|
282
|
+
const firstBatch = results.slice(0, PAGE_SIZE);
|
|
283
|
+
const validFirstBatch = await filterValidLinks(ctx, firstBatch, logger);
|
|
284
|
+
if (validFirstBatch.length === 0) {
|
|
285
|
+
const moreBatch = results.slice(PAGE_SIZE, PAGE_SIZE * 3);
|
|
286
|
+
const validMoreBatch = await filterValidLinks(ctx, moreBatch, logger);
|
|
287
|
+
if (validMoreBatch.length === 0) {
|
|
288
|
+
await internal.sendGroupMsg(groupId, [
|
|
289
|
+
{ type: "at", data: { qq: session.userId } },
|
|
290
|
+
{ type: "text", data: { text: ` 「${keyword}」的资源链接均已失效,换个关键词试试吧~` } }
|
|
291
|
+
]);
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
validFirstBatch.push(...validMoreBatch);
|
|
295
|
+
}
|
|
296
|
+
logger.info(`验证完成,有效: ${validFirstBatch.length} 个`);
|
|
297
|
+
const userSession = {
|
|
298
|
+
results,
|
|
299
|
+
validResults: validFirstBatch,
|
|
300
|
+
timestamp: Date.now(),
|
|
301
|
+
keyword,
|
|
302
|
+
currentPage: 1,
|
|
303
|
+
validating: false
|
|
304
|
+
};
|
|
305
|
+
searchSessions.set(sessionKey, userSession);
|
|
306
|
+
await sendPageResults(ctx, internal, groupId, session.userId, userSession, fullConfig.sessionTimeout, logger);
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
} catch (backupError) {
|
|
310
|
+
logger.error("备用API也失败:", backupError);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
let errorMsg = "搜索服务暂时不可用,请稍后再试";
|
|
314
|
+
if (error?.code === "ECONNRESET" || error?.cause?.code === "ECONNRESET") {
|
|
315
|
+
errorMsg = "搜索服务连接中断,请稍后再试";
|
|
316
|
+
} else if (error?.code === "ETIMEDOUT" || error?.message?.includes("timeout")) {
|
|
317
|
+
errorMsg = "搜索请求超时,请稍后再试";
|
|
318
|
+
} else if (error?.code === "ENOTFOUND") {
|
|
319
|
+
errorMsg = "无法连接到搜索服务,请检查网络";
|
|
320
|
+
}
|
|
256
321
|
await internal.sendGroupMsg(groupId, [
|
|
257
322
|
{ type: "at", data: { qq: session.userId } },
|
|
258
|
-
{ type: "text", data: { text:
|
|
323
|
+
{ type: "text", data: { text: ` ${errorMsg}` } }
|
|
259
324
|
]);
|
|
260
325
|
}
|
|
261
326
|
});
|
|
@@ -313,6 +378,31 @@ async function sendPageResults(ctx, internal, groupId, userId, userSession, sess
|
|
|
313
378
|
}
|
|
314
379
|
__name(sendPageResults, "sendPageResults");
|
|
315
380
|
async function searchResources(ctx, config, keyword, logger, useBackup = false) {
|
|
381
|
+
const maxRetries = 2;
|
|
382
|
+
let lastError = null;
|
|
383
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
384
|
+
try {
|
|
385
|
+
if (attempt > 0) {
|
|
386
|
+
logger.info(`第 ${attempt + 1} 次尝试搜索...`);
|
|
387
|
+
await sleep(1e3 * attempt);
|
|
388
|
+
}
|
|
389
|
+
return await performSearch(ctx, config, keyword, logger, useBackup);
|
|
390
|
+
} catch (error) {
|
|
391
|
+
lastError = error;
|
|
392
|
+
const errorCode = error?.code || error?.cause?.code;
|
|
393
|
+
logger.warn(`搜索尝试 ${attempt + 1} 失败: ${errorCode || error?.message}`);
|
|
394
|
+
if (errorCode === "ECONNRESET" || errorCode === "ETIMEDOUT" || error?.message?.includes("timeout")) {
|
|
395
|
+
if (attempt < maxRetries) {
|
|
396
|
+
continue;
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
throw error;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
throw lastError;
|
|
403
|
+
}
|
|
404
|
+
__name(searchResources, "searchResources");
|
|
405
|
+
async function performSearch(ctx, config, keyword, logger, useBackup = false) {
|
|
316
406
|
try {
|
|
317
407
|
const apiUrl = useBackup ? config.backupApiUrl : config.searchApiUrl;
|
|
318
408
|
const apiMethod = useBackup ? config.backupApiMethod : config.searchApiMethod;
|
|
@@ -321,7 +411,6 @@ async function searchResources(ctx, config, keyword, logger, useBackup = false)
|
|
|
321
411
|
const resultPath = useBackup ? config.backupResultPath : config.searchResultPath;
|
|
322
412
|
const titleField = useBackup ? config.backupTitleField : config.searchTitleField;
|
|
323
413
|
const urlField = useBackup ? config.backupUrlField : config.searchUrlField;
|
|
324
|
-
const maxTitleLength = keyword.length + 10;
|
|
325
414
|
let params = {};
|
|
326
415
|
try {
|
|
327
416
|
params = JSON.parse(apiParamsStr);
|
|
@@ -360,8 +449,8 @@ async function searchResources(ctx, config, keyword, logger, useBackup = false)
|
|
|
360
449
|
const quarkPattern = /https:\/\/pan\.quark\.cn\/s\/[a-zA-Z0-9]+/g;
|
|
361
450
|
const isValidTitle = /* @__PURE__ */ __name((title) => {
|
|
362
451
|
if (!title) return false;
|
|
363
|
-
if (title.length >
|
|
364
|
-
logger.debug(`标题过长被过滤: "${title}" (${title.length} >
|
|
452
|
+
if (title.length > 200) {
|
|
453
|
+
logger.debug(`标题过长被过滤: "${title}" (${title.length} > 200)`);
|
|
365
454
|
return false;
|
|
366
455
|
}
|
|
367
456
|
return true;
|
|
@@ -466,17 +555,23 @@ async function searchResources(ctx, config, keyword, logger, useBackup = false)
|
|
|
466
555
|
}
|
|
467
556
|
}
|
|
468
557
|
logger.info(`搜索到 ${results.length} 个资源`);
|
|
558
|
+
if (results.length > 0) {
|
|
559
|
+
logger.debug("搜索结果详情:");
|
|
560
|
+
results.forEach((item, index) => {
|
|
561
|
+
logger.debug(` [${index + 1}] ${item.title} - ${item.url}`);
|
|
562
|
+
});
|
|
563
|
+
}
|
|
469
564
|
return results;
|
|
470
565
|
} catch (error) {
|
|
471
566
|
logger.error("搜索请求失败:", error);
|
|
472
567
|
throw error;
|
|
473
568
|
}
|
|
474
569
|
}
|
|
475
|
-
__name(
|
|
570
|
+
__name(performSearch, "performSearch");
|
|
476
571
|
async function handleResourceSelection(ctx, config, internal, groupId, userId, userSession, index, logger) {
|
|
477
572
|
const sessionKey = `${groupId}:${userId}`;
|
|
478
573
|
const selectedItem = userSession.validResults[index - 1];
|
|
479
|
-
logger.info(`用户选择: ${selectedItem.title}`);
|
|
574
|
+
logger.info(`用户选择: ${selectedItem.title} - ${selectedItem.url}`);
|
|
480
575
|
try {
|
|
481
576
|
await internal.sendGroupMsg(groupId, [
|
|
482
577
|
{ type: "at", data: { qq: userId } },
|
|
@@ -484,26 +579,43 @@ async function handleResourceSelection(ctx, config, internal, groupId, userId, u
|
|
|
484
579
|
]);
|
|
485
580
|
const result = await transferAndShare(ctx, config, selectedItem.url, selectedItem.title, logger);
|
|
486
581
|
if (result.success) {
|
|
582
|
+
logger.info(`转存成功: ${selectedItem.title} - 原链接: ${selectedItem.url} - 新链接: ${result.shareUrl}`);
|
|
583
|
+
userSession.timestamp = Date.now();
|
|
584
|
+
const remainingTime = Math.floor(config.sessionTimeout - (Date.now() - userSession.timestamp) / 1e3);
|
|
487
585
|
const lines = [];
|
|
488
586
|
lines.push(`✅ 转存成功!`);
|
|
489
587
|
lines.push("");
|
|
490
588
|
lines.push(`📺 ${selectedItem.title}`);
|
|
491
589
|
lines.push(`🔗 ${result.shareUrl}`);
|
|
590
|
+
lines.push("");
|
|
591
|
+
lines.push(`💡 可继续回复编号选择其他资源`);
|
|
592
|
+
lines.push(`⏰ 会话剩余 ${config.sessionTimeout} 秒`);
|
|
492
593
|
await internal.sendGroupMsg(groupId, [
|
|
493
594
|
{ type: "at", data: { qq: userId } },
|
|
494
595
|
{ type: "text", data: { text: "\n" + lines.join("\n") } }
|
|
495
596
|
]);
|
|
496
|
-
|
|
597
|
+
if (config.enableAdFilter && result.savedFids && result.savedFids.length > 0) {
|
|
598
|
+
logger.info(`开始广告过滤扫描: ${selectedItem.title}`);
|
|
599
|
+
scanAndCleanAds(ctx, config, result.savedFids, selectedItem.title, logger).catch((err) => {
|
|
600
|
+
logger.error(`广告过滤失败: ${selectedItem.title}`, err);
|
|
601
|
+
});
|
|
602
|
+
}
|
|
497
603
|
} else {
|
|
604
|
+
logger.warn(`转存失败: ${selectedItem.title} - ${selectedItem.url} - ${result.message}`);
|
|
605
|
+
userSession.timestamp = Date.now();
|
|
498
606
|
const lines = [];
|
|
499
|
-
lines.push(`⚠️ ${result.message}
|
|
607
|
+
lines.push(`⚠️ ${result.message}`);
|
|
608
|
+
lines.push("");
|
|
609
|
+
lines.push(`💡 请重新选择其他资源编号`);
|
|
610
|
+
lines.push(`⏰ 会话剩余 ${config.sessionTimeout} 秒`);
|
|
500
611
|
await internal.sendGroupMsg(groupId, [
|
|
501
612
|
{ type: "at", data: { qq: userId } },
|
|
502
613
|
{ type: "text", data: { text: "\n" + lines.join("\n") } }
|
|
503
614
|
]);
|
|
504
615
|
}
|
|
505
616
|
} catch (error) {
|
|
506
|
-
logger.error(
|
|
617
|
+
logger.error(`转存异常: ${selectedItem.title} - ${selectedItem.url}`, error);
|
|
618
|
+
userSession.timestamp = Date.now();
|
|
507
619
|
let errorMessage = "转存服务异常";
|
|
508
620
|
if (error?.response?.data?.message) {
|
|
509
621
|
errorMessage = error.response.data.message;
|
|
@@ -513,7 +625,10 @@ async function handleResourceSelection(ctx, config, internal, groupId, userId, u
|
|
|
513
625
|
errorMessage = error.message;
|
|
514
626
|
}
|
|
515
627
|
const lines = [];
|
|
516
|
-
lines.push(`⚠️ ${errorMessage}
|
|
628
|
+
lines.push(`⚠️ ${errorMessage}`);
|
|
629
|
+
lines.push("");
|
|
630
|
+
lines.push(`💡 请重新选择其他资源编号`);
|
|
631
|
+
lines.push(`⏰ 会话剩余 ${config.sessionTimeout} 秒`);
|
|
517
632
|
await internal.sendGroupMsg(groupId, [
|
|
518
633
|
{ type: "at", data: { qq: userId } },
|
|
519
634
|
{ type: "text", data: { text: "\n" + lines.join("\n") } }
|
|
@@ -711,7 +826,7 @@ async function transferAndShare(ctx, config, shareUrl, title, logger) {
|
|
|
711
826
|
return { success: false, message: passwordRes.message || "获取分享链接失败" };
|
|
712
827
|
}
|
|
713
828
|
const finalShareUrl = passwordRes.data.share_url;
|
|
714
|
-
return { success: true, shareUrl: finalShareUrl };
|
|
829
|
+
return { success: true, shareUrl: finalShareUrl, savedFids };
|
|
715
830
|
} catch (error) {
|
|
716
831
|
logger.error("转存出错:", error);
|
|
717
832
|
return { success: false, message: error?.message || "转存过程出错" };
|
|
@@ -722,6 +837,142 @@ function sleep(ms) {
|
|
|
722
837
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
723
838
|
}
|
|
724
839
|
__name(sleep, "sleep");
|
|
840
|
+
async function getFolderFiles(ctx, cookie, pdirFid, logger) {
|
|
841
|
+
const headers = getQuarkHeaders(cookie);
|
|
842
|
+
const params = {
|
|
843
|
+
pr: "ucpro",
|
|
844
|
+
fr: "pc",
|
|
845
|
+
uc_param_str: "",
|
|
846
|
+
pdir_fid: pdirFid,
|
|
847
|
+
_page: 1,
|
|
848
|
+
_size: 200,
|
|
849
|
+
_fetch_total: 1,
|
|
850
|
+
_fetch_sub_dirs: 0,
|
|
851
|
+
_sort: "file_type:asc,updated_at:desc"
|
|
852
|
+
};
|
|
853
|
+
try {
|
|
854
|
+
const response = await ctx.http.get(
|
|
855
|
+
"https://drive-pc.quark.cn/1/clouddrive/file/sort",
|
|
856
|
+
{ headers, params, timeout: 3e4 }
|
|
857
|
+
);
|
|
858
|
+
if (response.status === 200) {
|
|
859
|
+
return response.data?.list || [];
|
|
860
|
+
} else {
|
|
861
|
+
logger.error(`获取文件夹内容失败: ${response.message}`);
|
|
862
|
+
return [];
|
|
863
|
+
}
|
|
864
|
+
} catch (error) {
|
|
865
|
+
logger.error(`获取文件夹内容异常:`, error);
|
|
866
|
+
return [];
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
__name(getFolderFiles, "getFolderFiles");
|
|
870
|
+
async function renameFile(ctx, cookie, fid, newName, logger) {
|
|
871
|
+
const headers = getQuarkHeaders(cookie);
|
|
872
|
+
const params = { pr: "ucpro", fr: "pc", uc_param_str: "" };
|
|
873
|
+
const data = { fid, file_name: newName };
|
|
874
|
+
try {
|
|
875
|
+
const response = await ctx.http.post(
|
|
876
|
+
"https://drive-pc.quark.cn/1/clouddrive/file/rename",
|
|
877
|
+
data,
|
|
878
|
+
{ headers, params, timeout: 3e4 }
|
|
879
|
+
);
|
|
880
|
+
return response.status === 200;
|
|
881
|
+
} catch (error) {
|
|
882
|
+
logger.error(`重命名异常:`, error);
|
|
883
|
+
return false;
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
__name(renameFile, "renameFile");
|
|
887
|
+
async function deleteFiles(ctx, cookie, fidList, logger) {
|
|
888
|
+
const headers = getQuarkHeaders(cookie);
|
|
889
|
+
const params = { pr: "ucpro", fr: "pc", uc_param_str: "" };
|
|
890
|
+
const data = {
|
|
891
|
+
action_type: 2,
|
|
892
|
+
exclude_fids: [],
|
|
893
|
+
filelist: fidList
|
|
894
|
+
};
|
|
895
|
+
try {
|
|
896
|
+
const response = await ctx.http.post(
|
|
897
|
+
"https://drive-pc.quark.cn/1/clouddrive/file/delete",
|
|
898
|
+
data,
|
|
899
|
+
{ headers, params, timeout: 3e4 }
|
|
900
|
+
);
|
|
901
|
+
return response.status === 200;
|
|
902
|
+
} catch (error) {
|
|
903
|
+
logger.error(`删除异常:`, error);
|
|
904
|
+
return false;
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
__name(deleteFiles, "deleteFiles");
|
|
908
|
+
function shouldDelete(filename, deleteKeywords) {
|
|
909
|
+
for (const keyword of deleteKeywords) {
|
|
910
|
+
if (keyword && filename.includes(keyword)) {
|
|
911
|
+
return true;
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
return false;
|
|
915
|
+
}
|
|
916
|
+
__name(shouldDelete, "shouldDelete");
|
|
917
|
+
function applyReplaceRules(filename, replaceRules) {
|
|
918
|
+
let newName = filename;
|
|
919
|
+
for (const [oldWord, newWord] of Object.entries(replaceRules)) {
|
|
920
|
+
if (oldWord && newName.includes(oldWord)) {
|
|
921
|
+
newName = newName.replace(new RegExp(oldWord, "g"), newWord);
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
return newName;
|
|
925
|
+
}
|
|
926
|
+
__name(applyReplaceRules, "applyReplaceRules");
|
|
927
|
+
async function scanFolder(ctx, config, pdirFid, currentDepth, resourceTitle, logger, stats) {
|
|
928
|
+
if (currentDepth > config.scanDepth) {
|
|
929
|
+
return;
|
|
930
|
+
}
|
|
931
|
+
const files = await getFolderFiles(ctx, config.quarkCookie, pdirFid, logger);
|
|
932
|
+
for (const file of files) {
|
|
933
|
+
stats.scanned++;
|
|
934
|
+
const fileType = file.dir ? "文件夹" : "文件";
|
|
935
|
+
if (shouldDelete(file.file_name, config.deleteKeywords)) {
|
|
936
|
+
const success = await deleteFiles(ctx, config.quarkCookie, [file.fid], logger);
|
|
937
|
+
if (success) {
|
|
938
|
+
stats.deleted++;
|
|
939
|
+
logger.info(`[广告过滤] [${resourceTitle}] [删除] ${fileType}: ${file.file_name}`);
|
|
940
|
+
} else {
|
|
941
|
+
logger.error(`[广告过滤] [${resourceTitle}] [删除失败] ${fileType}: ${file.file_name}`);
|
|
942
|
+
}
|
|
943
|
+
continue;
|
|
944
|
+
}
|
|
945
|
+
const newName = applyReplaceRules(file.file_name, config.replaceRules);
|
|
946
|
+
if (newName !== file.file_name) {
|
|
947
|
+
const success = await renameFile(ctx, config.quarkCookie, file.fid, newName, logger);
|
|
948
|
+
if (success) {
|
|
949
|
+
stats.renamed++;
|
|
950
|
+
logger.info(`[广告过滤] [${resourceTitle}] [重命名] ${fileType}: ${file.file_name} -> ${newName}`);
|
|
951
|
+
} else {
|
|
952
|
+
logger.error(`[广告过滤] [${resourceTitle}] [重命名失败] ${fileType}: ${file.file_name}`);
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
if (file.dir && currentDepth < config.scanDepth) {
|
|
956
|
+
await scanFolder(ctx, config, file.fid, currentDepth + 1, resourceTitle, logger, stats);
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
__name(scanFolder, "scanFolder");
|
|
961
|
+
async function scanAndCleanAds(ctx, config, savedFids, resourceTitle, logger) {
|
|
962
|
+
const stats = { deleted: 0, renamed: 0, scanned: 0 };
|
|
963
|
+
logger.info(`[广告过滤] 开始扫描: ${resourceTitle}, 文件数: ${savedFids.length}, 深度: ${config.scanDepth}`);
|
|
964
|
+
try {
|
|
965
|
+
for (const fid of savedFids) {
|
|
966
|
+
await scanFolder(ctx, config, fid, 1, resourceTitle, logger, stats);
|
|
967
|
+
}
|
|
968
|
+
logger.info(
|
|
969
|
+
`[广告过滤] 扫描完成: ${resourceTitle} | 扫描: ${stats.scanned} | 删除: ${stats.deleted} | 重命名: ${stats.renamed}`
|
|
970
|
+
);
|
|
971
|
+
} catch (error) {
|
|
972
|
+
logger.error(`[广告过滤] 扫描异常: ${resourceTitle}`, error);
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
__name(scanAndCleanAds, "scanAndCleanAds");
|
|
725
976
|
// Annotate the CommonJS export names for ESM import in node:
|
|
726
977
|
0 && (module.exports = {
|
|
727
978
|
Config,
|