karin-plugin-qgroup-file2openlist 0.0.22 → 0.0.23
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/README.md +21 -0
- package/config/config.json +2 -1
- package/lib/apps/groupFiles.backup.js +125 -0
- package/lib/apps/groupFiles.js +326 -10
- package/lib/apps/groupSyncConfig.js +18 -411
- package/lib/apps/groupSyncScheduler.js +13 -181
- package/lib/apps/help.js +1 -1
- package/lib/apps/opCommands.js +364 -0
- package/lib/apps/ownerUi.js +1206 -0
- package/lib/apps/render.js +1 -1
- package/lib/apps/sendMsg.js +1 -1
- package/lib/chunk-2LLNWYKG.js +576 -0
- package/lib/chunk-47BWXSCO.js +1222 -0
- package/lib/chunk-PR4QXAZC.js +867 -0
- package/lib/chunk-QZNZIRBJ.js +46 -0
- package/package.json +1 -1
- package/resources/template/help.html +25 -3
- package/resources/template/myBackup.html +209 -0
- package/resources/template/ui/action-result.html +241 -0
- package/resources/template/ui/panel.html +355 -0
- package/lib/chunk-AWSGUQPA.js +0 -2282
- /package/lib/{chunk-5WVKHIPK.js → chunk-DA4U55JC.js} +0 -0
package/README.md
CHANGED
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
- [前言](#前言)
|
|
6
6
|
- [群文件导出](#群文件导出)
|
|
7
7
|
- [同步到 OpenList](#同步到-openlist)
|
|
8
|
+
- [主人管理(图片面板)](#主人管理图片面板)
|
|
9
|
+
- [OpenList → OpenList 转发](#openlist--openlist-转发)
|
|
8
10
|
- [快速开始](#快速开始)
|
|
9
11
|
- [详细开发流程](#详细开发流程)
|
|
10
12
|
- [常见问题与建议](#常见问题与建议)
|
|
@@ -66,4 +68,23 @@ TypeScript 插件开发流程现在更加简单,无需手动克隆模板仓库
|
|
|
66
68
|
- `#群同步配置 <群号> 计划 <cron>`:设置定时同步(例:`0 0 3 * * *`)
|
|
67
69
|
- `#群同步配置 <群号> 时段 00:00-06:00,23:00-23:59`:限制定时同步时间段(空=不限制)
|
|
68
70
|
|
|
71
|
+
## 主人管理(图片面板)
|
|
72
|
+
|
|
73
|
+
仅主人私聊可用:
|
|
74
|
+
|
|
75
|
+
- `#群文件面板`:输出管理总览面板(群绑定/监听/OP转发规则摘要/快捷命令)
|
|
76
|
+
- `#绑定备份群 <群号> [--to /目标目录] [--inc|--full] [--flat|--keep]`:写入群配置并默认开启群文件上传监听
|
|
77
|
+
- `#解绑备份群 <群号>`:删除该群配置
|
|
78
|
+
- `#开启群文件监听 <群号>` / `#关闭群文件监听 <群号>`:切换 uploadBackup 监听开关
|
|
79
|
+
|
|
80
|
+
## OpenList → OpenList 转发
|
|
81
|
+
|
|
82
|
+
用于“多源站 → 单目的端(固定为配置 openlistBaseUrl)”的转发规则(仅主人私聊可用):
|
|
83
|
+
|
|
84
|
+
- `#添加op转发 <源OpenListBaseUrl> [--src /] [--to /backup] [--name xxx] [--user u] [--pass p] [--full|--inc] [--auto|--api|--webdav]`
|
|
85
|
+
- `#op转发 列表`
|
|
86
|
+
- `#op转发 查看 <ruleId>`
|
|
87
|
+
- `#op转发 执行 <ruleId>`(默认全量)
|
|
88
|
+
- `#op转发 删除 <ruleId>`
|
|
89
|
+
|
|
69
90
|
## 🚀 快速开始
|
package/config/config.json
CHANGED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import {
|
|
2
|
+
handleGroupFileUploadedAutoBackup
|
|
3
|
+
} from "../chunk-PR4QXAZC.js";
|
|
4
|
+
import {
|
|
5
|
+
backupOpenListToOpenListCore,
|
|
6
|
+
formatErrorMessage
|
|
7
|
+
} from "../chunk-47BWXSCO.js";
|
|
8
|
+
import "../chunk-QZNZIRBJ.js";
|
|
9
|
+
import "../chunk-DA4U55JC.js";
|
|
10
|
+
import "../chunk-IZS467MR.js";
|
|
11
|
+
|
|
12
|
+
// src/apps/groupFiles.backup.ts
|
|
13
|
+
import { karin, logger } from "node-karin";
|
|
14
|
+
var parseBackupOpenListArgs = (text) => {
|
|
15
|
+
const raw = text.trim();
|
|
16
|
+
const tokens = raw ? raw.split(/\s+/).filter(Boolean) : [];
|
|
17
|
+
const help = /(^|\s)(--help|-h|help|\?)(\s|$)/i.test(raw);
|
|
18
|
+
const first = tokens[0];
|
|
19
|
+
const sourceBaseUrl = first && /^https?:\/\//i.test(first) ? first : void 0;
|
|
20
|
+
const restRaw = sourceBaseUrl ? raw.slice(first.length).trim() : raw;
|
|
21
|
+
const srcMatch = restRaw.match(/--src\s+(\S+)/i) ?? restRaw.match(/(^|\s)src=(\S+)/i);
|
|
22
|
+
const srcDir = srcMatch ? srcMatch[srcMatch.length - 1] : void 0;
|
|
23
|
+
const toMatch = restRaw.match(/--to\s+(\S+)/i) ?? restRaw.match(/(^|\s)to=(\S+)/i);
|
|
24
|
+
const toDir = toMatch ? toMatch[toMatch.length - 1] : void 0;
|
|
25
|
+
const maxMatch = restRaw.match(/--max\s+(\d+)/i) ?? restRaw.match(/(^|\s)max=(\d+)/i);
|
|
26
|
+
const maxFiles = maxMatch ? Number(maxMatch[maxMatch.length - 1]) : void 0;
|
|
27
|
+
const concurrencyMatch = restRaw.match(/--concurrency\s+(\d+)/i) ?? restRaw.match(/(^|\s)concurrency=(\d+)/i);
|
|
28
|
+
const concurrency = concurrencyMatch ? Number(concurrencyMatch[concurrencyMatch.length - 1]) : void 0;
|
|
29
|
+
const timeoutMatch = restRaw.match(/--timeout\s+(\d+)/i) ?? restRaw.match(/(^|\s)timeout=(\d+)/i);
|
|
30
|
+
const timeoutSec = timeoutMatch ? Number(timeoutMatch[timeoutMatch.length - 1]) : void 0;
|
|
31
|
+
const scanMatch = restRaw.match(/--scan(?:-concurrency)?\s+(\d+)/i) ?? restRaw.match(/(^|\s)scan=(\d+)/i) ?? restRaw.match(/(^|\s)scanConcurrency=(\d+)/i) ?? restRaw.match(/(^|\s)scan_concurrency=(\d+)/i);
|
|
32
|
+
const scanConcurrency = scanMatch ? Number(scanMatch[scanMatch.length - 1]) : void 0;
|
|
33
|
+
const perPageMatch = restRaw.match(/--per-page\s+(\d+)/i) ?? restRaw.match(/--perpage\s+(\d+)/i) ?? restRaw.match(/--page-size\s+(\d+)/i) ?? restRaw.match(/(^|\s)per[_-]?page=(\d+)/i) ?? restRaw.match(/(^|\s)pageSize=(\d+)/i) ?? restRaw.match(/(^|\s)per_page=(\d+)/i);
|
|
34
|
+
const perPage = perPageMatch ? Number(perPageMatch[perPageMatch.length - 1]) : void 0;
|
|
35
|
+
const modeFull = /(^|\s)(--full|full)(\s|$)/i.test(restRaw);
|
|
36
|
+
const modeInc = /(^|\s)(--inc|--incremental|inc|incremental)(\s|$)/i.test(restRaw);
|
|
37
|
+
const mode = modeFull ? "full" : modeInc ? "incremental" : void 0;
|
|
38
|
+
const transportApi = /(^|\s)(--api)(\s|$)/i.test(restRaw);
|
|
39
|
+
const transportWebDav = /(^|\s)(--webdav|--dav)(\s|$)/i.test(restRaw);
|
|
40
|
+
const transportAuto = /(^|\s)(--auto)(\s|$)/i.test(restRaw);
|
|
41
|
+
const transport = transportApi ? "api" : transportWebDav ? "webdav" : transportAuto ? "auto" : void 0;
|
|
42
|
+
return {
|
|
43
|
+
sourceBaseUrl,
|
|
44
|
+
srcDir,
|
|
45
|
+
toDir,
|
|
46
|
+
maxFiles,
|
|
47
|
+
concurrency,
|
|
48
|
+
timeoutSec,
|
|
49
|
+
scanConcurrency,
|
|
50
|
+
perPage,
|
|
51
|
+
mode,
|
|
52
|
+
transport,
|
|
53
|
+
help
|
|
54
|
+
};
|
|
55
|
+
};
|
|
56
|
+
var openListToOpenListHelpText = [
|
|
57
|
+
"OpenList -> OpenList \u5907\u4EFD\u7528\u6CD5\uFF1A",
|
|
58
|
+
"- \u79C1\u804A\uFF1A#\u5907\u4EFDoplist [\u6E90OpenList\u5730\u5740] [\u53C2\u6570]",
|
|
59
|
+
"- \u793A\u4F8B\uFF1A#\u5907\u4EFDoplist https://pan.example.com",
|
|
60
|
+
"- #\u5907\u4EFDoplist https://pan.example.com --src / --to /backup --inc",
|
|
61
|
+
"- #\u5907\u4EFDoplist https://pan.example.com --api",
|
|
62
|
+
"- #\u5907\u4EFDoplist https://pan.example.com --webdav",
|
|
63
|
+
"- #\u5907\u4EFDoplist https://pan.example.com --full --concurrency 3 --timeout 600",
|
|
64
|
+
"- #\u5907\u4EFDoplist https://pan.example.com --scan 30 --per-page 2000",
|
|
65
|
+
"\u63D0\u793A\uFF1A\u76EE\u6807\u7AEF\u4F7F\u7528 openlistBaseUrl/openlistUsername/openlistPassword\uFF08\u4E0E\u7FA4\u6587\u4EF6\u540C\u6B65\u5171\u7528\uFF09\u3002",
|
|
66
|
+
"\u63D0\u793A\uFF1A\u4F20\u8F93\u9ED8\u8BA4 auto\uFF08\u6E90\u7AEF\u4E0B\u8F7D\u504F\u5411 API\uFF0C\u76EE\u6807\u7AEF\u4E0A\u4F20\u504F\u5411 WebDAV\uFF1B\u5931\u8D25\u4F1A\u56DE\u9000\uFF09\u3002",
|
|
67
|
+
'\u8BF4\u660E\uFF1A\u4F1A\u5728\u76EE\u6807\u76EE\u5F55\u4E0B\u521B\u5EFA\u5B50\u76EE\u5F55\uFF08\u6E90 OpenList \u57DF\u540D\uFF0C"." \u66FF\u6362\u4E3A "_"\uFF09\u3002'
|
|
68
|
+
].join("\n");
|
|
69
|
+
var backupOpenListToOpenList = karin.command(/^#?备份oplist(.*)$/i, async (e) => {
|
|
70
|
+
if (!e.isPrivate) return false;
|
|
71
|
+
const argsText = e.msg.replace(/^#?备份oplist/i, "");
|
|
72
|
+
const {
|
|
73
|
+
sourceBaseUrl,
|
|
74
|
+
srcDir,
|
|
75
|
+
toDir,
|
|
76
|
+
maxFiles,
|
|
77
|
+
concurrency,
|
|
78
|
+
timeoutSec,
|
|
79
|
+
scanConcurrency,
|
|
80
|
+
perPage,
|
|
81
|
+
mode,
|
|
82
|
+
transport,
|
|
83
|
+
help
|
|
84
|
+
} = parseBackupOpenListArgs(argsText);
|
|
85
|
+
if (help || !sourceBaseUrl) {
|
|
86
|
+
await e.reply(openListToOpenListHelpText);
|
|
87
|
+
return true;
|
|
88
|
+
}
|
|
89
|
+
try {
|
|
90
|
+
await backupOpenListToOpenListCore({
|
|
91
|
+
sourceBaseUrl,
|
|
92
|
+
srcDir,
|
|
93
|
+
toDir,
|
|
94
|
+
maxFiles,
|
|
95
|
+
concurrency,
|
|
96
|
+
timeoutSec,
|
|
97
|
+
scanConcurrency,
|
|
98
|
+
perPage,
|
|
99
|
+
mode,
|
|
100
|
+
transport,
|
|
101
|
+
report: (msg) => e.reply(msg)
|
|
102
|
+
});
|
|
103
|
+
return true;
|
|
104
|
+
} catch (error) {
|
|
105
|
+
logger.error(error);
|
|
106
|
+
await e.reply(formatErrorMessage(error));
|
|
107
|
+
return true;
|
|
108
|
+
}
|
|
109
|
+
}, {
|
|
110
|
+
priority: 9999,
|
|
111
|
+
log: true,
|
|
112
|
+
name: "OpenList\u5907\u4EFD\u5230\u5BF9\u7AEFOpenList",
|
|
113
|
+
permission: "all"
|
|
114
|
+
});
|
|
115
|
+
var groupFileUploadedAutoBackup = karin.accept("notice.groupFileUploaded", (e, next) => {
|
|
116
|
+
try {
|
|
117
|
+
handleGroupFileUploadedAutoBackup(e);
|
|
118
|
+
} finally {
|
|
119
|
+
next();
|
|
120
|
+
}
|
|
121
|
+
}, { log: false, name: "\u7FA4\u6587\u4EF6\u4E0A\u4F20\u81EA\u52A8\u5907\u4EFD" });
|
|
122
|
+
export {
|
|
123
|
+
backupOpenListToOpenList,
|
|
124
|
+
groupFileUploadedAutoBackup
|
|
125
|
+
};
|
package/lib/apps/groupFiles.js
CHANGED
|
@@ -1,16 +1,332 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
exportGroupFiles,
|
|
4
|
-
groupFileUploadedAutoBackup,
|
|
5
|
-
syncGroupFilesToOpenList,
|
|
2
|
+
exportGroupFilesToDisk,
|
|
6
3
|
syncGroupFilesToOpenListCore
|
|
7
|
-
} from "../chunk-
|
|
8
|
-
import
|
|
4
|
+
} from "../chunk-PR4QXAZC.js";
|
|
5
|
+
import {
|
|
6
|
+
formatErrorMessage
|
|
7
|
+
} from "../chunk-47BWXSCO.js";
|
|
8
|
+
import {
|
|
9
|
+
normalizePosixPath
|
|
10
|
+
} from "../chunk-QZNZIRBJ.js";
|
|
11
|
+
import {
|
|
12
|
+
config
|
|
13
|
+
} from "../chunk-DA4U55JC.js";
|
|
9
14
|
import "../chunk-IZS467MR.js";
|
|
15
|
+
|
|
16
|
+
// src/apps/groupFiles.ts
|
|
17
|
+
import path from "path";
|
|
18
|
+
import { pathToFileURL } from "url";
|
|
19
|
+
import { karin, logger } from "node-karin";
|
|
20
|
+
var MAX_FILE_TIMEOUT_SEC = 3e3;
|
|
21
|
+
var MIN_FILE_TIMEOUT_SEC = 10;
|
|
22
|
+
var DEFAULT_PROGRESS_REPORT_EVERY = 10;
|
|
23
|
+
var buildUploadFileCandidates = (filePath) => {
|
|
24
|
+
const normalized = filePath.replaceAll("\\", "/");
|
|
25
|
+
const candidates = [
|
|
26
|
+
filePath,
|
|
27
|
+
normalized
|
|
28
|
+
];
|
|
29
|
+
try {
|
|
30
|
+
candidates.push(pathToFileURL(filePath).href);
|
|
31
|
+
} catch {
|
|
32
|
+
}
|
|
33
|
+
if (/^[a-zA-Z]:\//.test(normalized)) {
|
|
34
|
+
candidates.push(`file:///${normalized}`);
|
|
35
|
+
}
|
|
36
|
+
return [...new Set(candidates.filter(Boolean))];
|
|
37
|
+
};
|
|
38
|
+
var parseArgs = (text) => {
|
|
39
|
+
const raw = text.trim();
|
|
40
|
+
const tokens = raw ? raw.split(/\s+/).filter(Boolean) : [];
|
|
41
|
+
const format = /(^|\s)(--csv|csv)(\s|$)/i.test(raw) ? "csv" : "json";
|
|
42
|
+
const withUrl = !/(^|\s)(--no-url|--nourl|no-url|nourl)(\s|$)/i.test(raw);
|
|
43
|
+
const urlOnly = /(^|\s)(--url-only|--urlonly|url-only|urlonly)(\s|$)/i.test(raw);
|
|
44
|
+
const sendFile = /(^|\s)(--send-file|--sendfile|send-file|sendfile)(\s|$)/i.test(raw);
|
|
45
|
+
let groupId;
|
|
46
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
47
|
+
const token = tokens[i];
|
|
48
|
+
const nextToken = tokens[i + 1];
|
|
49
|
+
if (/^--(group|gid|groupid)$/i.test(token) && nextToken && /^\d+$/.test(nextToken)) {
|
|
50
|
+
groupId = nextToken;
|
|
51
|
+
break;
|
|
52
|
+
}
|
|
53
|
+
const assignMatch = token.match(/^(group|gid|groupid)=(\d+)$/i);
|
|
54
|
+
if (assignMatch) {
|
|
55
|
+
groupId = assignMatch[2];
|
|
56
|
+
break;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
if (!groupId) {
|
|
60
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
61
|
+
const token = tokens[i];
|
|
62
|
+
const prevToken = tokens[i - 1];
|
|
63
|
+
if (!/^\d+$/.test(token)) continue;
|
|
64
|
+
if (prevToken && /^--(folder|max|concurrency|group|gid|groupid)$/i.test(prevToken)) continue;
|
|
65
|
+
groupId = token;
|
|
66
|
+
break;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
const folderMatch = raw.match(/--folder\s+(\S+)/i) ?? raw.match(/(^|\s)folder=(\S+)/i);
|
|
70
|
+
const folderId = folderMatch ? folderMatch[folderMatch.length - 1] : void 0;
|
|
71
|
+
const maxMatch = raw.match(/--max\s+(\d+)/i) ?? raw.match(/(^|\s)max=(\d+)/i);
|
|
72
|
+
const maxFiles = maxMatch ? Number(maxMatch[maxMatch.length - 1]) : void 0;
|
|
73
|
+
const concurrencyMatch = raw.match(/--concurrency\s+(\d+)/i) ?? raw.match(/(^|\s)concurrency=(\d+)/i);
|
|
74
|
+
const concurrency = concurrencyMatch ? Number(concurrencyMatch[concurrencyMatch.length - 1]) : void 0;
|
|
75
|
+
const concurrencySpecified = Boolean(concurrencyMatch);
|
|
76
|
+
const help = /(^|\s)(--help|-h|help|\?)(\s|$)/i.test(raw);
|
|
77
|
+
return { groupId, format, withUrl, urlOnly, sendFile, folderId, maxFiles, concurrency, concurrencySpecified, help };
|
|
78
|
+
};
|
|
79
|
+
var parseSyncArgs = (text) => {
|
|
80
|
+
const raw = text.trim();
|
|
81
|
+
const base = parseArgs(text);
|
|
82
|
+
const toMatch = raw.match(/--to\s+(\S+)/i) ?? raw.match(/(^|\s)to=(\S+)/i);
|
|
83
|
+
const to = toMatch ? toMatch[toMatch.length - 1] : void 0;
|
|
84
|
+
const toSpecified = Boolean(toMatch);
|
|
85
|
+
const flatFlag = /(^|\s)(--flat|flat)(\s|$)/i.test(raw);
|
|
86
|
+
const keepFlag = /(^|\s)(--keep|keep)(\s|$)/i.test(raw);
|
|
87
|
+
const flatSpecified = flatFlag || keepFlag;
|
|
88
|
+
const flat = flatFlag ? true : keepFlag ? false : void 0;
|
|
89
|
+
const timeoutMatch = raw.match(/--timeout\s+(\d+)/i) ?? raw.match(/(^|\s)timeout=(\d+)/i);
|
|
90
|
+
const timeoutSec = timeoutMatch ? Number(timeoutMatch[timeoutMatch.length - 1]) : void 0;
|
|
91
|
+
const timeoutSpecified = Boolean(timeoutMatch);
|
|
92
|
+
const modeFull = /(^|\s)(--full|full)(\s|$)/i.test(raw);
|
|
93
|
+
const modeInc = /(^|\s)(--inc|--incremental|inc|incremental)(\s|$)/i.test(raw);
|
|
94
|
+
const mode = modeFull ? "full" : modeInc ? "incremental" : void 0;
|
|
95
|
+
return {
|
|
96
|
+
groupId: base.groupId,
|
|
97
|
+
folderId: base.folderId,
|
|
98
|
+
maxFiles: base.maxFiles,
|
|
99
|
+
concurrency: base.concurrency,
|
|
100
|
+
concurrencySpecified: base.concurrencySpecified,
|
|
101
|
+
flat,
|
|
102
|
+
flatSpecified,
|
|
103
|
+
to,
|
|
104
|
+
toSpecified,
|
|
105
|
+
timeoutSec,
|
|
106
|
+
timeoutSpecified,
|
|
107
|
+
mode,
|
|
108
|
+
help: base.help
|
|
109
|
+
};
|
|
110
|
+
};
|
|
111
|
+
var helpText = [
|
|
112
|
+
"\u7FA4\u6587\u4EF6\u5BFC\u51FA\u7528\u6CD5\uFF1A",
|
|
113
|
+
"- \u8BF7\u79C1\u804A\u53D1\u9001\uFF1A#\u5BFC\u51FA\u7FA4\u6587\u4EF6 <\u7FA4\u53F7> [\u53C2\u6570]",
|
|
114
|
+
"- \u793A\u4F8B\uFF1A#\u5BFC\u51FA\u7FA4\u6587\u4EF6 123456",
|
|
115
|
+
"- #\u5BFC\u51FA\u7FA4\u6587\u4EF6 123456 --no-url\uFF1A\u53EA\u5BFC\u51FA\u5217\u8868\uFF0C\u4E0D\u89E3\u6790 URL",
|
|
116
|
+
"- #\u5BFC\u51FA\u7FA4\u6587\u4EF6 123456 --url-only\uFF1A\u4EC5\u8F93\u51FA URL\uFF08\u66F4\u65B9\u4FBF\u590D\u5236\uFF09",
|
|
117
|
+
"- #\u5BFC\u51FA\u7FA4\u6587\u4EF6 123456 --csv\uFF1A\u5BFC\u51FA\u4E3A CSV\uFF08\u9ED8\u8BA4 JSON\uFF09",
|
|
118
|
+
"- #\u5BFC\u51FA\u7FA4\u6587\u4EF6 123456 --folder <id>\uFF1A\u4ECE\u6307\u5B9A\u6587\u4EF6\u5939\u5F00\u59CB\u5BFC\u51FA",
|
|
119
|
+
"- #\u5BFC\u51FA\u7FA4\u6587\u4EF6 123456 --max <n>\uFF1A\u6700\u591A\u5BFC\u51FA n \u6761\u6587\u4EF6\u8BB0\u5F55",
|
|
120
|
+
"- #\u5BFC\u51FA\u7FA4\u6587\u4EF6 123456 --concurrency <n>\uFF1A\u89E3\u6790 URL \u5E76\u53D1\u6570\uFF08\u9ED8\u8BA4 3\uFF09",
|
|
121
|
+
"- #\u5BFC\u51FA\u7FA4\u6587\u4EF6 123456 --send-file\uFF1A\u5C1D\u8BD5\u53D1\u9001\u5BFC\u51FA\u6587\u4EF6\uFF08\u4F9D\u8D56\u534F\u8BAE\u7AEF\u652F\u6301\uFF09",
|
|
122
|
+
"\u63D0\u793A\uFF1A\u4E0B\u8F7D URL \u901A\u5E38\u6709\u65F6\u6548\uFF0C\u8FC7\u671F\u540E\u9700\u91CD\u65B0\u5BFC\u51FA\u3002"
|
|
123
|
+
].join("\n");
|
|
124
|
+
var exportGroupFiles = karin.command(/^#?(导出群文件|群文件导出)(.*)$/i, async (e) => {
|
|
125
|
+
if (!e.isPrivate) return false;
|
|
126
|
+
const argsText = e.msg.replace(/^#?(导出群文件|群文件导出)/i, "");
|
|
127
|
+
const { groupId, format, withUrl, urlOnly, sendFile, folderId, maxFiles, concurrency, help } = parseArgs(argsText);
|
|
128
|
+
if (help) {
|
|
129
|
+
await e.reply(helpText);
|
|
130
|
+
return true;
|
|
131
|
+
}
|
|
132
|
+
if (!groupId) {
|
|
133
|
+
await e.reply(`\u7F3A\u5C11\u7FA4\u53F7\u53C2\u6570
|
|
134
|
+
|
|
135
|
+
${helpText}`);
|
|
136
|
+
return true;
|
|
137
|
+
}
|
|
138
|
+
const groupContact = karin.contactGroup(groupId);
|
|
139
|
+
await e.reply(`\u5F00\u59CB\u5BFC\u51FA\u7FA4\u6587\u4EF6\u5217\u8868\uFF0C\u8BF7\u7A0D\u5019..
|
|
140
|
+
- \u7FA4\u53F7\uFF1A${groupId}
|
|
141
|
+
- \u683C\u5F0F\uFF1A${format}
|
|
142
|
+
- \u5305\u542BURL\uFF1A${withUrl ? "\u662F" : "\u5426"}`);
|
|
143
|
+
let result;
|
|
144
|
+
try {
|
|
145
|
+
const urlConcurrency = typeof concurrency === "number" && Number.isFinite(concurrency) && concurrency > 0 ? concurrency : 3;
|
|
146
|
+
result = await exportGroupFilesToDisk({
|
|
147
|
+
bot: e.bot,
|
|
148
|
+
contact: groupContact,
|
|
149
|
+
groupId,
|
|
150
|
+
folderId,
|
|
151
|
+
maxFiles,
|
|
152
|
+
withUrl,
|
|
153
|
+
urlConcurrency,
|
|
154
|
+
format
|
|
155
|
+
});
|
|
156
|
+
} catch (error) {
|
|
157
|
+
logger.error(error);
|
|
158
|
+
await e.reply(`\u5BFC\u51FA\u5931\u8D25\uFF1A${formatErrorMessage(error)}`);
|
|
159
|
+
return true;
|
|
160
|
+
}
|
|
161
|
+
const { limitedList, exportPath, exportName, urlErrors } = result;
|
|
162
|
+
const errorsByFileId = /* @__PURE__ */ new Map();
|
|
163
|
+
for (const err of urlErrors) {
|
|
164
|
+
if (!err.fileId) continue;
|
|
165
|
+
if (!errorsByFileId.has(err.fileId)) errorsByFileId.set(err.fileId, err.message);
|
|
166
|
+
}
|
|
167
|
+
await e.reply([
|
|
168
|
+
"\u5BFC\u51FA\u5B8C\u6210\uFF1A",
|
|
169
|
+
`- \u603B\u6570\uFF1A${limitedList.length}`,
|
|
170
|
+
`- URL\u83B7\u53D6\u5931\u8D25\uFF1A${urlErrors.length}`,
|
|
171
|
+
`- \u6587\u4EF6\uFF1A${exportPath}`
|
|
172
|
+
].join("\n"));
|
|
173
|
+
const compactError = (message) => message.replace(/\s+/g, " ").slice(0, 120);
|
|
174
|
+
const preview = limitedList.slice(0, 20);
|
|
175
|
+
const lines = preview.map((item, index) => {
|
|
176
|
+
if (urlOnly) return `${index + 1}. ${item.url ?? ""}`.trim();
|
|
177
|
+
const errMsg = errorsByFileId.get(item.fileId);
|
|
178
|
+
if (item.url) return `${index + 1}. ${item.path}
|
|
179
|
+
${item.url}`;
|
|
180
|
+
return `${index + 1}. ${item.path}
|
|
181
|
+
(\u83B7\u53D6URL\u5931\u8D25) fileId=${item.fileId}${errMsg ? `
|
|
182
|
+
\u539F\u56E0\uFF1A${compactError(errMsg)}` : ""}`;
|
|
183
|
+
});
|
|
184
|
+
const chunks = [];
|
|
185
|
+
const maxChunkLen = 1500;
|
|
186
|
+
let buf = "";
|
|
187
|
+
for (const line of lines) {
|
|
188
|
+
const next = buf ? `${buf}
|
|
189
|
+
|
|
190
|
+
${line}` : line;
|
|
191
|
+
if (next.length > maxChunkLen) {
|
|
192
|
+
if (buf) chunks.push(buf);
|
|
193
|
+
buf = line;
|
|
194
|
+
} else {
|
|
195
|
+
buf = next;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
if (buf) chunks.push(buf);
|
|
199
|
+
const maxMessages = 10;
|
|
200
|
+
for (const chunk of chunks.slice(0, maxMessages)) {
|
|
201
|
+
await e.reply(chunk);
|
|
202
|
+
}
|
|
203
|
+
if (limitedList.length > preview.length) {
|
|
204
|
+
await e.reply(`\uFF08\u5DF2\u7701\u7565 ${limitedList.length - preview.length} \u6761\uFF0C\u53EF\u4F7F\u7528 --max \u8C03\u6574\uFF09`);
|
|
205
|
+
} else if (chunks.length > maxMessages) {
|
|
206
|
+
await e.reply("\uFF08\u6D88\u606F\u8FC7\u957F\uFF0C\u5DF2\u7701\u7565\u540E\u7EED\u5185\u5BB9\uFF1B\u53EF\u4F7F\u7528 --max \u51CF\u5C11\u6761\u6570\uFF09");
|
|
207
|
+
}
|
|
208
|
+
if (sendFile && typeof e.bot?.uploadFile === "function") {
|
|
209
|
+
const candidates = buildUploadFileCandidates(exportPath);
|
|
210
|
+
for (const fileParam of candidates) {
|
|
211
|
+
try {
|
|
212
|
+
await e.bot.uploadFile(e.contact, fileParam, exportName);
|
|
213
|
+
break;
|
|
214
|
+
} catch {
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
return true;
|
|
219
|
+
}, {
|
|
220
|
+
priority: 9999,
|
|
221
|
+
log: true,
|
|
222
|
+
name: "\u5BFC\u51FA\u7FA4\u6587\u4EF6",
|
|
223
|
+
permission: "all"
|
|
224
|
+
});
|
|
225
|
+
var syncHelpText = [
|
|
226
|
+
"\u7FA4\u6587\u4EF6\u540C\u6B65\u5230 OpenList \u7528\u6CD5\uFF1A",
|
|
227
|
+
"- \u79C1\u804A\uFF1A#\u540C\u6B65\u7FA4\u6587\u4EF6 <\u7FA4\u53F7> [\u53C2\u6570]",
|
|
228
|
+
"- \u6CE8\u610F\uFF1A\u9ED8\u8BA4\u4EC5\u79C1\u804A\u54CD\u5E94\uFF08\u7FA4\u804A\u4E0D\u4F1A\u89E6\u53D1\u8BE5\u6307\u4EE4\uFF09",
|
|
229
|
+
"- \u793A\u4F8B\uFF1A#\u540C\u6B65\u7FA4\u6587\u4EF6 123456",
|
|
230
|
+
"- #\u540C\u6B65\u7FA4\u6587\u4EF6 123456 --to /\u76EE\u6807\u76EE\u5F55\uFF1A\u4E0A\u4F20\u5230\u6307\u5B9A\u76EE\u5F55\uFF08\u9ED8\u8BA4\u4F7F\u7528\u914D\u7F6E openlistTargetDir\uFF09",
|
|
231
|
+
"- #\u540C\u6B65\u7FA4\u6587\u4EF6 123456 --flat\uFF1A\u4E0D\u4FDD\u7559\u7FA4\u6587\u4EF6\u5939\u7ED3\u6784\uFF0C\u5168\u90E8\u5E73\u94FA\u5230\u76EE\u6807\u76EE\u5F55",
|
|
232
|
+
"- #\u540C\u6B65\u7FA4\u6587\u4EF6 123456 --keep\uFF1A\u5F3A\u5236\u4FDD\u7559\u76EE\u5F55\u7ED3\u6784\uFF08\u8986\u76D6\u7FA4\u914D\u7F6E flat\uFF09",
|
|
233
|
+
"- #\u540C\u6B65\u7FA4\u6587\u4EF6 123456 --folder <id>\uFF1A\u4ECE\u6307\u5B9A\u6587\u4EF6\u5939\u5F00\u59CB",
|
|
234
|
+
"- #\u540C\u6B65\u7FA4\u6587\u4EF6 123456 --max <n>\uFF1A\u6700\u591A\u5904\u7406 n \u4E2A\u6587\u4EF6",
|
|
235
|
+
"- #\u540C\u6B65\u7FA4\u6587\u4EF6 123456 --concurrency <n>\uFF1A\u5E76\u53D1\u6570\uFF08\u4F1A\u8986\u76D6\u7FA4\u914D\u7F6E\u7684\u5E76\u53D1\uFF09",
|
|
236
|
+
"- #\u540C\u6B65\u7FA4\u6587\u4EF6 123456 --timeout <sec>\uFF1A\u5355\u6587\u4EF6\u8D85\u65F6\u79D2\u6570\uFF08\u4EC5\u5F71\u54CD\u5355\u4E2A\u6587\u4EF6\uFF09",
|
|
237
|
+
"- #\u540C\u6B65\u7FA4\u6587\u4EF6 123456 --full/--inc\uFF1A\u8986\u76D6\u7FA4\u914D\u7F6E\u7684\u540C\u6B65\u6A21\u5F0F\uFF08\u5168\u91CF/\u589E\u91CF\uFF09",
|
|
238
|
+
"\u524D\u7F6E\uFF1A\u8BF7\u5148\u5728\u914D\u7F6E\u6587\u4EF6\u586B\u5199 openlistBaseUrl/openlistUsername/openlistPassword\u3002"
|
|
239
|
+
].join("\n");
|
|
240
|
+
var normalizeSyncMode = (value, fallback) => {
|
|
241
|
+
const v = String(value ?? "").trim().toLowerCase();
|
|
242
|
+
if (v === "full" || v === "\u5168\u91CF") return "full";
|
|
243
|
+
if (v === "incremental" || v === "\u589E\u91CF" || v === "inc") return "incremental";
|
|
244
|
+
return fallback;
|
|
245
|
+
};
|
|
246
|
+
var getGroupSyncTarget = (cfg, groupId) => {
|
|
247
|
+
const list = cfg?.groupSyncTargets;
|
|
248
|
+
if (!Array.isArray(list)) return void 0;
|
|
249
|
+
return list.find((it) => String(it?.groupId) === String(groupId));
|
|
250
|
+
};
|
|
251
|
+
var syncGroupFilesToOpenList = karin.command(/^#?(同步群文件|群文件同步)(.*)$/i, async (e) => {
|
|
252
|
+
if (!e.isPrivate) return false;
|
|
253
|
+
const argsText = e.msg.replace(/^#?(同步群文件|群文件同步)/i, "");
|
|
254
|
+
const {
|
|
255
|
+
groupId: parsedGroupId,
|
|
256
|
+
folderId: parsedFolderId,
|
|
257
|
+
maxFiles: parsedMaxFiles,
|
|
258
|
+
concurrency,
|
|
259
|
+
concurrencySpecified,
|
|
260
|
+
flat,
|
|
261
|
+
flatSpecified,
|
|
262
|
+
to,
|
|
263
|
+
toSpecified,
|
|
264
|
+
timeoutSec,
|
|
265
|
+
timeoutSpecified,
|
|
266
|
+
mode: forcedMode,
|
|
267
|
+
help
|
|
268
|
+
} = parseSyncArgs(argsText);
|
|
269
|
+
if (help) {
|
|
270
|
+
await e.reply(syncHelpText);
|
|
271
|
+
return true;
|
|
272
|
+
}
|
|
273
|
+
const cfg = config();
|
|
274
|
+
const groupId = parsedGroupId ?? (e.isGroup ? e.groupId : void 0);
|
|
275
|
+
if (!groupId) {
|
|
276
|
+
await e.reply(`\u7F3A\u5C11\u7FA4\u53F7\u53C2\u6570
|
|
277
|
+
|
|
278
|
+
${syncHelpText}`);
|
|
279
|
+
return true;
|
|
280
|
+
}
|
|
281
|
+
const defaults = cfg.groupSyncDefaults ?? {};
|
|
282
|
+
const targetCfg = getGroupSyncTarget(cfg, groupId);
|
|
283
|
+
const mode = forcedMode ?? (targetCfg ? normalizeSyncMode(targetCfg?.mode, normalizeSyncMode(defaults?.mode, "incremental")) : "full");
|
|
284
|
+
const urlC = concurrencySpecified ? typeof concurrency === "number" ? concurrency : 3 : typeof targetCfg?.urlConcurrency === "number" ? targetCfg.urlConcurrency : typeof defaults?.urlConcurrency === "number" ? defaults.urlConcurrency : 3;
|
|
285
|
+
const transferC = concurrencySpecified ? typeof concurrency === "number" ? concurrency : 3 : typeof targetCfg?.transferConcurrency === "number" ? targetCfg.transferConcurrency : typeof defaults?.transferConcurrency === "number" ? defaults.transferConcurrency : 3;
|
|
286
|
+
const fileTimeout = timeoutSpecified ? typeof timeoutSec === "number" ? timeoutSec : 600 : typeof targetCfg?.fileTimeoutSec === "number" ? targetCfg.fileTimeoutSec : typeof defaults?.fileTimeoutSec === "number" ? defaults.fileTimeoutSec : 600;
|
|
287
|
+
const retryTimes = typeof targetCfg?.retryTimes === "number" ? targetCfg.retryTimes : typeof defaults?.retryTimes === "number" ? defaults.retryTimes : 2;
|
|
288
|
+
const retryDelayMs = typeof targetCfg?.retryDelayMs === "number" ? targetCfg.retryDelayMs : typeof defaults?.retryDelayMs === "number" ? defaults.retryDelayMs : 1500;
|
|
289
|
+
const progressEvery = typeof targetCfg?.progressReportEvery === "number" ? targetCfg.progressReportEvery : typeof defaults?.progressReportEvery === "number" ? defaults.progressReportEvery : DEFAULT_PROGRESS_REPORT_EVERY;
|
|
290
|
+
const downloadLimitKbps = typeof targetCfg?.downloadLimitKbps === "number" ? targetCfg.downloadLimitKbps : typeof defaults?.downloadLimitKbps === "number" ? defaults.downloadLimitKbps : 0;
|
|
291
|
+
const uploadLimitKbps = typeof targetCfg?.uploadLimitKbps === "number" ? targetCfg.uploadLimitKbps : typeof defaults?.uploadLimitKbps === "number" ? defaults.uploadLimitKbps : 0;
|
|
292
|
+
const targetDir = normalizePosixPath(
|
|
293
|
+
toSpecified ? to ?? "" : String(targetCfg?.targetDir ?? "").trim() || path.posix.join(String(cfg.openlistTargetDir ?? "/"), String(groupId))
|
|
294
|
+
);
|
|
295
|
+
const finalFlat = flatSpecified ? Boolean(flat) : typeof targetCfg?.flat === "boolean" ? targetCfg.flat : Boolean(defaults?.flat ?? false);
|
|
296
|
+
const folderId = parsedFolderId ?? targetCfg?.sourceFolderId;
|
|
297
|
+
const maxFiles = typeof parsedMaxFiles === "number" ? parsedMaxFiles : targetCfg?.maxFiles;
|
|
298
|
+
try {
|
|
299
|
+
await syncGroupFilesToOpenListCore({
|
|
300
|
+
bot: e.bot,
|
|
301
|
+
groupId,
|
|
302
|
+
folderId,
|
|
303
|
+
maxFiles,
|
|
304
|
+
flat: Boolean(finalFlat),
|
|
305
|
+
targetDir,
|
|
306
|
+
mode,
|
|
307
|
+
urlConcurrency: Math.max(1, Math.floor(urlC) || 1),
|
|
308
|
+
transferConcurrency: Math.max(1, Math.floor(transferC) || 1),
|
|
309
|
+
fileTimeoutSec: Math.min(MAX_FILE_TIMEOUT_SEC, Math.max(MIN_FILE_TIMEOUT_SEC, Math.floor(fileTimeout) || MIN_FILE_TIMEOUT_SEC)),
|
|
310
|
+
retryTimes: Math.max(0, Math.floor(retryTimes) || 0),
|
|
311
|
+
retryDelayMs: Math.max(0, Math.floor(retryDelayMs) || 0),
|
|
312
|
+
progressReportEvery: Math.max(0, Math.floor(progressEvery) || 0),
|
|
313
|
+
downloadLimitKbps: Math.max(0, Math.floor(downloadLimitKbps) || 0),
|
|
314
|
+
uploadLimitKbps: Math.max(0, Math.floor(uploadLimitKbps) || 0),
|
|
315
|
+
report: (msg) => e.reply(msg)
|
|
316
|
+
});
|
|
317
|
+
} catch (error) {
|
|
318
|
+
logger.error(error);
|
|
319
|
+
await e.reply(formatErrorMessage(error));
|
|
320
|
+
return true;
|
|
321
|
+
}
|
|
322
|
+
return true;
|
|
323
|
+
}, {
|
|
324
|
+
priority: 9999,
|
|
325
|
+
log: true,
|
|
326
|
+
name: "\u540C\u6B65\u7FA4\u6587\u4EF6\u5230OpenList",
|
|
327
|
+
permission: "all"
|
|
328
|
+
});
|
|
10
329
|
export {
|
|
11
|
-
backupOpenListToOpenList,
|
|
12
330
|
exportGroupFiles,
|
|
13
|
-
|
|
14
|
-
syncGroupFilesToOpenList,
|
|
15
|
-
syncGroupFilesToOpenListCore
|
|
331
|
+
syncGroupFilesToOpenList
|
|
16
332
|
};
|