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.
Files changed (2) hide show
  1. package/lib/index.js +292 -41
  2. 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 > config.sessionTimeout * 1e3) {
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 (config.watchGroups.length > 0 && !config.watchGroups.includes(groupId)) {
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 < config.sessionTimeout * 1e3) {
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, config, internal, groupId, session.userId, userSession, index, logger);
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 < config.sessionTimeout * 1e3) {
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, config.sessionTimeout, logger);
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, config.sessionTimeout, logger);
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, config, keyword, logger, false);
216
+ let results = await searchResources(ctx, fullConfig, keyword, logger, false);
205
217
  let usedBackupApi = false;
206
- if (!results.length && config.enableBackupApi && config.backupApiUrl) {
218
+ if (!results.length && fullConfig.enableBackupApi && fullConfig.backupApiUrl) {
207
219
  logger.info(`主API无结果,尝试备用API...`);
208
220
  try {
209
- results = await searchResources(ctx, config, keyword, logger, true);
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, config.sessionTimeout, logger);
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 > maxTitleLength) {
364
- logger.debug(`标题过长被过滤: "${title}" (${title.length} > ${maxTitleLength})`);
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(searchResources, "searchResources");
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
- searchSessions.delete(sessionKey);
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("转存失败:", 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,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "koishi-plugin-quark-search",
3
3
  "description": "电影电视剧夸克网盘资源搜索插件",
4
- "version": "1.1.1",
4
+ "version": "1.2.1",
5
5
  "main": "lib/index.js",
6
6
  "typings": "lib/index.d.ts",
7
7
  "files": [