karin-plugin-qgroup-file2openlist 0.0.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/README.md +171 -0
- package/config/config.json +20 -0
- package/lib/apps/example.js +41 -0
- package/lib/apps/groupFiles.js +12 -0
- package/lib/apps/groupSyncConfig.js +398 -0
- package/lib/apps/groupSyncScheduler.js +188 -0
- package/lib/apps/handler.js +24 -0
- package/lib/apps/render.js +89 -0
- package/lib/apps/sendMsg.js +128 -0
- package/lib/apps/task.js +19 -0
- package/lib/chunk-5WVKHIPK.js +38 -0
- package/lib/chunk-IZS467MR.js +47 -0
- package/lib/chunk-QVWWPGHK.js +1138 -0
- package/lib/dir.js +6 -0
- package/lib/index.js +7 -0
- package/lib/web.config.js +756 -0
- package/package.json +67 -0
- package/resources/image//345/220/257/347/250/213/345/256/243/345/217/221.png +0 -0
- package/resources/template/test.html +21 -0
package/README.md
ADDED
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
# Karin TypeScript 插件开发模板
|
|
2
|
+
|
|
3
|
+
## 📖 目录
|
|
4
|
+
|
|
5
|
+
- [前言](#前言)
|
|
6
|
+
- [群文件导出](#群文件导出)
|
|
7
|
+
- [同步到 OpenList](#同步到-openlist)
|
|
8
|
+
- [快速开始](#快速开始)
|
|
9
|
+
- [详细开发流程](#详细开发流程)
|
|
10
|
+
- [常见问题与建议](#常见问题与建议)
|
|
11
|
+
- [贡献与反馈](#贡献与反馈)
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## 前言
|
|
16
|
+
|
|
17
|
+
TypeScript 插件开发流程现在更加简单,无需手动克隆模板仓库,只需一条命令即可快速开始!
|
|
18
|
+
|
|
19
|
+
> TypeScript 编写 → 编译为 JS → 发布 NPM 包 → 用户安装
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## 群文件导出
|
|
24
|
+
|
|
25
|
+
在私聊中发送(默认所有人可用,可在 `src/apps/groupFiles.ts` 调整权限):
|
|
26
|
+
|
|
27
|
+
- `#导出群文件 <群号>`:递归导出群文件列表(JSON,包含下载 URL)
|
|
28
|
+
- `#导出群文件 <群号> --csv`:导出 CSV
|
|
29
|
+
- `#导出群文件 <群号> --no-url`:只导出列表,不解析 URL
|
|
30
|
+
- `#导出群文件 <群号> --url-only`:仅输出 URL(更方便复制)
|
|
31
|
+
- `#导出群文件 <群号> --folder <id>`:从指定文件夹开始导出
|
|
32
|
+
- `#导出群文件 <群号> --max <n>`:最多导出 n 条文件记录
|
|
33
|
+
- `#导出群文件 <群号> --concurrency <n>`:解析 URL 并发数(默认 3)
|
|
34
|
+
- `#导出群文件 <群号> --send-file`:尝试发送导出文件(依赖协议端支持)
|
|
35
|
+
|
|
36
|
+
提示:下载 URL 通常有时效,过期后需重新导出。
|
|
37
|
+
|
|
38
|
+
## 同步到 OpenList
|
|
39
|
+
|
|
40
|
+
前置配置:`config/config.json`(会自动复制到 `@karinjs/<插件名>/config/config.json`)
|
|
41
|
+
|
|
42
|
+
- `openlistBaseUrl`:例如 `http://127.0.0.1:5244`
|
|
43
|
+
- `openlistUsername` / `openlistPassword`:用于 WebDAV BasicAuth 登录
|
|
44
|
+
- `openlistTargetDir`:目标目录(例:`/挂载目录/QQ群文件`)
|
|
45
|
+
- `groupSyncDefaults`:群同步默认策略(增量/全量、并发、单文件超时、重试等)
|
|
46
|
+
- `groupSyncTargets`:同步对象群配置(每群可单独覆盖策略/目录/并发/定时计划/同步时段)
|
|
47
|
+
|
|
48
|
+
私聊命令:
|
|
49
|
+
|
|
50
|
+
- `#同步群文件 <群号>`:下载群文件并通过 OpenList WebDAV 上传到目标目录
|
|
51
|
+
- `#同步群文件 <群号> --to /目标目录`:覆盖配置里的目标目录
|
|
52
|
+
- `#同步群文件 <群号> --flat`:不保留群文件夹结构,全部平铺上传
|
|
53
|
+
- `#同步群文件 <群号> --max <n>` / `--folder <id>` / `--concurrency <n>`:同导出命令
|
|
54
|
+
- `#同步群文件 <群号> --full/--inc`:强制全量/增量(覆盖群配置)
|
|
55
|
+
|
|
56
|
+
群聊命令(简化):
|
|
57
|
+
|
|
58
|
+
- `#同步群文件`:默认同步本群(建议先在 WebUI 配置 `groupSyncTargets`)
|
|
59
|
+
|
|
60
|
+
群同步配置命令(主人权限):
|
|
61
|
+
|
|
62
|
+
- `#群同步配置 列表`
|
|
63
|
+
- `#群同步配置 <群号> 添加/删除/查看`
|
|
64
|
+
- `#群同步配置 <群号> 启用/停用`
|
|
65
|
+
- `#群同步配置 <群号> 模式 全量|增量`
|
|
66
|
+
- `#群同步配置 <群号> 计划 <cron>`:设置定时同步(例:`0 0 3 * * *`)
|
|
67
|
+
- `#群同步配置 <群号> 时段 00:00-06:00,23:00-23:59`:限制定时同步时间段(空=不限制)
|
|
68
|
+
|
|
69
|
+
## 🚀 快速开始
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
pnpm create karin
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
- 按提示选择“ts插件开发模板”即可自动初始化项目。
|
|
76
|
+
- 进入新建的项目目录,继续开发。
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## 详细开发流程
|
|
81
|
+
|
|
82
|
+
1. **一键创建项目**
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
pnpm create karin
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
- 选择“ts插件开发模板”
|
|
89
|
+
- 填写你的插件名称(会自动作为 package.json 的 name)
|
|
90
|
+
- 其余信息按提示填写
|
|
91
|
+
|
|
92
|
+
2. **安装依赖**
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
pnpm install
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
3. **开发与调试**
|
|
99
|
+
|
|
100
|
+
- 启动开发命令:
|
|
101
|
+
```bash
|
|
102
|
+
pnpm dev
|
|
103
|
+
```
|
|
104
|
+
- 编写你的插件代码于 `src/` 目录。
|
|
105
|
+
- 编译输出:
|
|
106
|
+
```bash
|
|
107
|
+
pnpm build
|
|
108
|
+
```
|
|
109
|
+
- 调试编译之后的代码:
|
|
110
|
+
```bash
|
|
111
|
+
pnpm app
|
|
112
|
+
```
|
|
113
|
+
- 本地调试建议:
|
|
114
|
+
- 可用 `pnpm link --global` 进行全局软链测试。
|
|
115
|
+
- 或在 karin 根目录用 `pnpm add ../your-plugin-repo -w` 进行本地依赖测试。
|
|
116
|
+
|
|
117
|
+
4. **配置 NPM 秘钥**
|
|
118
|
+
|
|
119
|
+
> 用于自动化发布,建议开启 2FA。
|
|
120
|
+
|
|
121
|
+
1. 注册 [npmjs](https://www.npmjs.com/) 账号。
|
|
122
|
+
2. 进入 `Access Tokens`,新建 `Classic Token`,类型选 `Automation`。
|
|
123
|
+
3. 复制生成的 Token。
|
|
124
|
+
4. 打开你的 GitHub 仓库 → Settings → Secrets and variables → Actions。
|
|
125
|
+
5. 新建 `NPM_TOKEN`,粘贴 Token。
|
|
126
|
+
6. 允许 GitHub Actions 创建和批准 PR(Settings → Actions)。
|
|
127
|
+
|
|
128
|
+
5. **设置包信息**
|
|
129
|
+
|
|
130
|
+
> 包名必须唯一,建议先在 [npm](https://www.npmjs.com/) 搜索确认。
|
|
131
|
+
|
|
132
|
+
- 初始化时填写的插件名会自动作为 package.json 的 name,无需手动修改。
|
|
133
|
+
- 其他如 `author`、`description`、`homepage`、`bugs.url`、`repository` 可在 package.json 中补充完善。
|
|
134
|
+
- **CI 配置无需再手动修改 package-name,已自动同步。**
|
|
135
|
+
|
|
136
|
+
6. **自动化发布**
|
|
137
|
+
|
|
138
|
+
> 推送代码后,GitHub Actions 会自动编译并发布到 npm。
|
|
139
|
+
|
|
140
|
+
- 常规开发流程:
|
|
141
|
+
1. `git add . && git commit -m "feat: ..." && git push`
|
|
142
|
+
2. 等待 CI 自动发布
|
|
143
|
+
3. 发布成功后可在 npm 页面看到新版本
|
|
144
|
+
|
|
145
|
+
7. **安装与验证**
|
|
146
|
+
|
|
147
|
+
- 在 karin 根目录下安装你的插件:
|
|
148
|
+
```bash
|
|
149
|
+
pnpm add your-package-name -w
|
|
150
|
+
```
|
|
151
|
+
- 验证插件是否生效,可查看 karin 启动日志或相关功能。
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
## 💡 常见问题与建议
|
|
156
|
+
|
|
157
|
+
- **Q: 发布失败怎么办?**
|
|
158
|
+
- 检查 NPM_TOKEN 是否配置正确,权限是否足够。
|
|
159
|
+
- 包名是否唯一,未被占用。
|
|
160
|
+
- Actions 日志可定位具体报错。
|
|
161
|
+
- **Q: 如何本地调试插件?**
|
|
162
|
+
- 推荐用 `pnpm link` 或本地依赖安装。
|
|
163
|
+
- **Q: 如何贡献代码?**
|
|
164
|
+
- 欢迎 PR,建议先提 issue 讨论。
|
|
165
|
+
|
|
166
|
+
---
|
|
167
|
+
|
|
168
|
+
## 贡献与反馈
|
|
169
|
+
|
|
170
|
+
- 有任何建议或问题,欢迎在 [Issues](https://github.com/KarinJS/karin-plugin-template-ts/issues) 提出。
|
|
171
|
+
- 也可加入官方交流群交流经验。
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"yiyanApi": "https://api.yujn.cn/api/sjyy.php",
|
|
3
|
+
"openlistBaseUrl": "http://127.0.0.1:5244",
|
|
4
|
+
"openlistUsername": "",
|
|
5
|
+
"openlistPassword": "",
|
|
6
|
+
"openlistTargetDir": "/",
|
|
7
|
+
"groupSyncDefaults": {
|
|
8
|
+
"mode": "incremental",
|
|
9
|
+
"flat": false,
|
|
10
|
+
"urlConcurrency": 3,
|
|
11
|
+
"transferConcurrency": 3,
|
|
12
|
+
"fileTimeoutSec": 600,
|
|
13
|
+
"retryTimes": 2,
|
|
14
|
+
"retryDelayMs": 1500,
|
|
15
|
+
"progressReportEvery": 10,
|
|
16
|
+
"downloadLimitKbps": 0,
|
|
17
|
+
"uploadLimitKbps": 0
|
|
18
|
+
},
|
|
19
|
+
"groupSyncTargets": []
|
|
20
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// src/apps/example.ts
|
|
2
|
+
import { karin, segment } from "node-karin";
|
|
3
|
+
var hello = karin.command("^(#)?\u4F60\u597D$", async (e) => {
|
|
4
|
+
await e.reply("\u4F60\u597D\u554A\uFF01\u6211\u662FKarin\uFF0C\u5F88\u9AD8\u5174\u8BA4\u8BC6\u4F60~ (\u3002\u30FB\u2200\u30FB)\u30CE", { at: false, recallMsg: 0, reply: true });
|
|
5
|
+
return true;
|
|
6
|
+
});
|
|
7
|
+
var test = karin.command("^(#)?\u6D4B\u8BD5$", "\u8BA9\u6211\u6765\u5C55\u793A\u4E00\u4E0B\u6211\u7684\u529F\u80FD\u5427\uFF01\u2728");
|
|
8
|
+
var text = karin.command("^(#)?\u6253\u62DB\u547C$", segment.text("\u5927\u5BB6\u597D\u5440\uFF01\u4ECA\u5929\u4E5F\u8981\u5143\u6C14\u6EE1\u6EE1\u54E6\uFF01\u2570(*\xB0\u25BD\xB0*)\u256F"), { name: "\u6253\u62DB\u547C" });
|
|
9
|
+
var test2 = karin.command("^(#)?\u83DC\u5355$", "\u6765\u770B\u770B\u6211\u90FD\u4F1A\u4E9B\u4EC0\u4E48\u5427~\n- #\u4F60\u597D\uFF1A\u6253\u4E2A\u62DB\u547C\n- #\u6D4B\u8BD5\uFF1A\u529F\u80FD\u5C55\u793A\n- #\u6253\u62DB\u547C\uFF1A\u5143\u6C14\u95EE\u5019\n(\uFF61\uFF65\u03C9\uFF65\uFF61)\uFF89\u2661", {
|
|
10
|
+
event: "message",
|
|
11
|
+
// 监听的事件
|
|
12
|
+
name: "\u83DC\u5355",
|
|
13
|
+
// 插件名称
|
|
14
|
+
perm: "all",
|
|
15
|
+
// 触发权限
|
|
16
|
+
at: false,
|
|
17
|
+
// 是否加上at 仅在群聊中有效
|
|
18
|
+
reply: false,
|
|
19
|
+
// 是否加上引用回复
|
|
20
|
+
recallMsg: 0,
|
|
21
|
+
// 发送是否撤回消息 单位秒
|
|
22
|
+
log: true,
|
|
23
|
+
// 是否启用日志
|
|
24
|
+
rank: 1e4,
|
|
25
|
+
// 优先级
|
|
26
|
+
adapter: [],
|
|
27
|
+
// 生效的适配器
|
|
28
|
+
dsbAdapter: [],
|
|
29
|
+
// 禁用的适配器
|
|
30
|
+
delay: 0,
|
|
31
|
+
// 延迟回复 单位毫秒 仅在第二个参数非函数时有效
|
|
32
|
+
stop: false,
|
|
33
|
+
// 是否停止执行后续插件 仅在第二个参数非函数时有效
|
|
34
|
+
authFailMsg: "\u54CE\u5440\uFF0C\u8FD9\u4E2A\u529F\u80FD\u53EA\u6709\u4E3B\u4EBA\u624D\u80FD\u7528\u54E6\uFF01\u8981\u4E0D\u4F60\u5148\u8BB8\u4E2A\u613F\uFF1F(\u0E51\u2022\u0300\u3142\u2022\u0301)\u0648\u2727"
|
|
35
|
+
});
|
|
36
|
+
export {
|
|
37
|
+
hello,
|
|
38
|
+
test,
|
|
39
|
+
test2,
|
|
40
|
+
text
|
|
41
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import {
|
|
2
|
+
exportGroupFiles,
|
|
3
|
+
syncGroupFilesToOpenList,
|
|
4
|
+
syncGroupFilesToOpenListCore
|
|
5
|
+
} from "../chunk-QVWWPGHK.js";
|
|
6
|
+
import "../chunk-5WVKHIPK.js";
|
|
7
|
+
import "../chunk-IZS467MR.js";
|
|
8
|
+
export {
|
|
9
|
+
exportGroupFiles,
|
|
10
|
+
syncGroupFilesToOpenList,
|
|
11
|
+
syncGroupFilesToOpenListCore
|
|
12
|
+
};
|
|
@@ -0,0 +1,398 @@
|
|
|
1
|
+
import {
|
|
2
|
+
config
|
|
3
|
+
} from "../chunk-5WVKHIPK.js";
|
|
4
|
+
import {
|
|
5
|
+
dir
|
|
6
|
+
} from "../chunk-IZS467MR.js";
|
|
7
|
+
|
|
8
|
+
// src/apps/groupSyncConfig.ts
|
|
9
|
+
import fs from "fs";
|
|
10
|
+
import path from "path";
|
|
11
|
+
import { karin, logger } from "node-karin";
|
|
12
|
+
var readJsonSafe = (filePath) => {
|
|
13
|
+
try {
|
|
14
|
+
if (!fs.existsSync(filePath)) return {};
|
|
15
|
+
const raw = fs.readFileSync(filePath, "utf8");
|
|
16
|
+
return raw ? JSON.parse(raw) : {};
|
|
17
|
+
} catch (error) {
|
|
18
|
+
logger.error(error);
|
|
19
|
+
return {};
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
var writeJsonSafe = (filePath, data) => {
|
|
23
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
24
|
+
fs.writeFileSync(filePath, JSON.stringify(data, null, 2), "utf8");
|
|
25
|
+
};
|
|
26
|
+
var getConfigFilePath = () => path.join(dir.ConfigDir, "config.json");
|
|
27
|
+
var getDefaultConfigPath = () => path.join(dir.defConfigDir, "config.json");
|
|
28
|
+
var normalizePosixPath = (inputPath, { ensureLeadingSlash = true, stripTrailingSlash = true } = {}) => {
|
|
29
|
+
let value = String(inputPath ?? "").trim().replaceAll("\\", "/");
|
|
30
|
+
value = value.replace(/\/+/g, "/");
|
|
31
|
+
if (!value) value = "/";
|
|
32
|
+
if (ensureLeadingSlash && !value.startsWith("/")) value = `/${value}`;
|
|
33
|
+
if (stripTrailingSlash && value.length > 1) value = value.replace(/\/+$/, "");
|
|
34
|
+
return value;
|
|
35
|
+
};
|
|
36
|
+
var parseBoolean = (value) => {
|
|
37
|
+
const v = String(value ?? "").trim().toLowerCase();
|
|
38
|
+
if (!v) return void 0;
|
|
39
|
+
if (["1", "true", "yes", "y", "on", "\u5F00\u542F", "\u5F00", "\u662F"].includes(v)) return true;
|
|
40
|
+
if (["0", "false", "no", "n", "off", "\u5173\u95ED", "\u5173", "\u5426"].includes(v)) return false;
|
|
41
|
+
return void 0;
|
|
42
|
+
};
|
|
43
|
+
var parseIntSafe = (value) => {
|
|
44
|
+
if (value == null) return void 0;
|
|
45
|
+
const n = Number(value);
|
|
46
|
+
if (!Number.isFinite(n)) return void 0;
|
|
47
|
+
return Math.floor(n);
|
|
48
|
+
};
|
|
49
|
+
var parseMode = (value) => {
|
|
50
|
+
const v = String(value ?? "").trim().toLowerCase();
|
|
51
|
+
if (!v) return void 0;
|
|
52
|
+
if (v === "full" || v === "\u5168\u91CF") return "full";
|
|
53
|
+
if (v === "incremental" || v === "\u589E\u91CF") return "incremental";
|
|
54
|
+
return void 0;
|
|
55
|
+
};
|
|
56
|
+
var formatTargetLine = (t) => {
|
|
57
|
+
const mode = t.mode ?? "-";
|
|
58
|
+
const enabled = t.enabled ?? true ? "\u542F\u7528" : "\u505C\u7528";
|
|
59
|
+
const cron = t.schedule?.enabled ? t.schedule?.cron || "(\u672A\u586Bcron)" : "\u5173\u95ED";
|
|
60
|
+
const targetDir = t.targetDir ? t.targetDir : "(\u9ED8\u8BA4)";
|
|
61
|
+
return `- ${t.groupId} | ${enabled} | ${mode} | \u8BA1\u5212:${cron} | \u76EE\u5F55:${targetDir}`;
|
|
62
|
+
};
|
|
63
|
+
var upsertTarget = (cfg, groupId, patch) => {
|
|
64
|
+
const list = getTargets(cfg);
|
|
65
|
+
const next = [...list];
|
|
66
|
+
const index = next.findIndex((it) => String(it.groupId) === String(groupId));
|
|
67
|
+
const base = index >= 0 ? next[index] : ensureTargetWithDefaults(groupId, cfg);
|
|
68
|
+
const merged = {
|
|
69
|
+
...base,
|
|
70
|
+
...patch,
|
|
71
|
+
groupId: String(groupId)
|
|
72
|
+
};
|
|
73
|
+
if (index >= 0) next[index] = merged;
|
|
74
|
+
else next.push(merged);
|
|
75
|
+
return next;
|
|
76
|
+
};
|
|
77
|
+
var ensureTargetWithDefaults = (groupId, cfg) => {
|
|
78
|
+
const defaults = cfg.groupSyncDefaults ?? {};
|
|
79
|
+
const baseTargetDir = normalizePosixPath(String(cfg.openlistTargetDir ?? "/"));
|
|
80
|
+
return {
|
|
81
|
+
groupId,
|
|
82
|
+
enabled: true,
|
|
83
|
+
mode: defaults.mode === "full" ? "full" : "incremental",
|
|
84
|
+
flat: Boolean(defaults.flat ?? false),
|
|
85
|
+
urlConcurrency: parseIntSafe(String(defaults.urlConcurrency ?? "")) ?? 3,
|
|
86
|
+
transferConcurrency: parseIntSafe(String(defaults.transferConcurrency ?? "")) ?? 3,
|
|
87
|
+
fileTimeoutSec: parseIntSafe(String(defaults.fileTimeoutSec ?? "")) ?? 600,
|
|
88
|
+
retryTimes: parseIntSafe(String(defaults.retryTimes ?? "")) ?? 2,
|
|
89
|
+
retryDelayMs: parseIntSafe(String(defaults.retryDelayMs ?? "")) ?? 1500,
|
|
90
|
+
targetDir: normalizePosixPath(path.posix.join(baseTargetDir, String(groupId))),
|
|
91
|
+
schedule: { enabled: false, cron: "" }
|
|
92
|
+
};
|
|
93
|
+
};
|
|
94
|
+
var updateConfigFile = (updater) => {
|
|
95
|
+
const configPath = getConfigFilePath();
|
|
96
|
+
const current = readJsonSafe(configPath);
|
|
97
|
+
const def = readJsonSafe(getDefaultConfigPath());
|
|
98
|
+
const merged = { ...def, ...current };
|
|
99
|
+
const next = updater({ ...merged });
|
|
100
|
+
writeJsonSafe(configPath, next);
|
|
101
|
+
return next;
|
|
102
|
+
};
|
|
103
|
+
var getTargets = (cfg) => {
|
|
104
|
+
const list = cfg?.groupSyncTargets;
|
|
105
|
+
return Array.isArray(list) ? list : [];
|
|
106
|
+
};
|
|
107
|
+
var cfgHelp = [
|
|
108
|
+
"\u7FA4\u540C\u6B65\u914D\u7F6E\u7528\u6CD5\uFF08\u5EFA\u8BAE\u5728 WebUI \u914D\u7F6E\u66F4\u5B8C\u6574\uFF09\uFF1A",
|
|
109
|
+
"- #\u7FA4\u540C\u6B65\u914D\u7F6E \u5217\u8868",
|
|
110
|
+
"- #\u7FA4\u540C\u6B65\u914D\u7F6E <\u7FA4\u53F7> \u67E5\u770B",
|
|
111
|
+
"- #\u7FA4\u540C\u6B65\u914D\u7F6E <\u7FA4\u53F7> \u6DFB\u52A0",
|
|
112
|
+
"- #\u7FA4\u540C\u6B65\u914D\u7F6E <\u7FA4\u53F7> \u5220\u9664",
|
|
113
|
+
"- #\u7FA4\u540C\u6B65\u914D\u7F6E <\u7FA4\u53F7> \u542F\u7528 / \u505C\u7528",
|
|
114
|
+
"- #\u7FA4\u540C\u6B65\u914D\u7F6E <\u7FA4\u53F7> \u6A21\u5F0F \u5168\u91CF|\u589E\u91CF",
|
|
115
|
+
"- #\u7FA4\u540C\u6B65\u914D\u7F6E <\u7FA4\u53F7> \u76EE\u5F55 /\u6302\u8F7D/QQ\u7FA4\u6587\u4EF6/123456",
|
|
116
|
+
"- #\u7FA4\u540C\u6B65\u914D\u7F6E <\u7FA4\u53F7> \u5E73\u94FA \u5F00|\u5173",
|
|
117
|
+
"- #\u7FA4\u540C\u6B65\u914D\u7F6E <\u7FA4\u53F7> \u5E76\u53D1 <n>\uFF08\u4E0B\u8F7D/\u4E0A\u4F20\u5E76\u53D1\uFF09",
|
|
118
|
+
"- #\u7FA4\u540C\u6B65\u914D\u7F6E <\u7FA4\u53F7> url\u5E76\u53D1 <n>\uFF08\u89E3\u6790URL\u5E76\u53D1\uFF09",
|
|
119
|
+
"- #\u7FA4\u540C\u6B65\u914D\u7F6E <\u7FA4\u53F7> \u8D85\u65F6 <sec>\uFF08\u5355\u6587\u4EF6\u8D85\u65F6\uFF09",
|
|
120
|
+
"- #\u7FA4\u540C\u6B65\u914D\u7F6E <\u7FA4\u53F7> \u91CD\u8BD5 <n>",
|
|
121
|
+
"- #\u7FA4\u540C\u6B65\u914D\u7F6E <\u7FA4\u53F7> \u65F6\u6BB5 00:00-06:00,23:00-23:59\uFF08\u7A7A=\u4E0D\u9650\u5236\uFF09",
|
|
122
|
+
"- #\u7FA4\u540C\u6B65\u914D\u7F6E <\u7FA4\u53F7> \u8BA1\u5212 <cron>\uFF08\u4F8B\uFF1A0 0 3 * * *\uFF09",
|
|
123
|
+
"- #\u7FA4\u540C\u6B65\u914D\u7F6E <\u7FA4\u53F7> \u8BA1\u5212 \u5F00\u542F|\u5173\u95ED"
|
|
124
|
+
].join("\n");
|
|
125
|
+
var groupSyncConfig = karin.command(/^#?(群同步配置|同步群配置|群文件同步配置)(.*)$/i, async (e) => {
|
|
126
|
+
const raw = e.msg.replace(/^#?(群同步配置|同步群配置|群文件同步配置)/i, "").trim();
|
|
127
|
+
if (!raw || /^(help|帮助|\?)$/i.test(raw)) {
|
|
128
|
+
await e.reply(cfgHelp);
|
|
129
|
+
return true;
|
|
130
|
+
}
|
|
131
|
+
const tokens = raw.split(/\s+/).filter(Boolean);
|
|
132
|
+
const first = tokens[0]?.toLowerCase();
|
|
133
|
+
const isList = ["\u5217\u8868", "list", "ls"].includes(first);
|
|
134
|
+
if (isList) {
|
|
135
|
+
const cfg = config();
|
|
136
|
+
const targets = getTargets(cfg);
|
|
137
|
+
if (!targets.length) {
|
|
138
|
+
await e.reply("\u5F53\u524D\u6CA1\u6709\u914D\u7F6E\u4EFB\u4F55\u540C\u6B65\u76EE\u6807\u7FA4\u3002\u53EF\u5728 WebUI \u6216\u7528\uFF1A#\u7FA4\u540C\u6B65\u914D\u7F6E <\u7FA4\u53F7> \u6DFB\u52A0");
|
|
139
|
+
return true;
|
|
140
|
+
}
|
|
141
|
+
const lines = targets.map(formatTargetLine);
|
|
142
|
+
await e.reply(["\u540C\u6B65\u76EE\u6807\u7FA4\u5217\u8868\uFF1A", ...lines].join("\n"));
|
|
143
|
+
return true;
|
|
144
|
+
}
|
|
145
|
+
const actionWords = /* @__PURE__ */ new Map([
|
|
146
|
+
["\u67E5\u770B", "show"],
|
|
147
|
+
["\u8BE6\u60C5", "show"],
|
|
148
|
+
["show", "show"],
|
|
149
|
+
["get", "show"],
|
|
150
|
+
["\u6DFB\u52A0", "add"],
|
|
151
|
+
["\u65B0\u589E", "add"],
|
|
152
|
+
["add", "add"],
|
|
153
|
+
["create", "add"],
|
|
154
|
+
["\u5220\u9664", "remove"],
|
|
155
|
+
["\u79FB\u9664", "remove"],
|
|
156
|
+
["remove", "remove"],
|
|
157
|
+
["del", "remove"],
|
|
158
|
+
["delete", "remove"],
|
|
159
|
+
["\u542F\u7528", "enable"],
|
|
160
|
+
["\u5F00\u542F", "enable"],
|
|
161
|
+
["enable", "enable"],
|
|
162
|
+
["on", "enable"],
|
|
163
|
+
["\u505C\u7528", "disable"],
|
|
164
|
+
["\u5173\u95ED", "disable"],
|
|
165
|
+
["disable", "disable"],
|
|
166
|
+
["off", "disable"],
|
|
167
|
+
["\u6A21\u5F0F", "mode"],
|
|
168
|
+
["\u7B56\u7565", "mode"],
|
|
169
|
+
["mode", "mode"],
|
|
170
|
+
["\u76EE\u5F55", "dir"],
|
|
171
|
+
["\u76EE\u6807\u76EE\u5F55", "dir"],
|
|
172
|
+
["dir", "dir"],
|
|
173
|
+
["to", "dir"],
|
|
174
|
+
["\u5E73\u94FA", "flat"],
|
|
175
|
+
["flat", "flat"],
|
|
176
|
+
["\u5E76\u53D1", "concurrency"],
|
|
177
|
+
["\u7EBF\u7A0B", "concurrency"],
|
|
178
|
+
["concurrency", "concurrency"],
|
|
179
|
+
["threads", "concurrency"],
|
|
180
|
+
["url\u5E76\u53D1", "urlConcurrency"],
|
|
181
|
+
["url\u7EBF\u7A0B", "urlConcurrency"],
|
|
182
|
+
["urlconcurrency", "urlConcurrency"],
|
|
183
|
+
["\u8D85\u65F6", "timeout"],
|
|
184
|
+
["timeout", "timeout"],
|
|
185
|
+
["\u91CD\u8BD5", "retry"],
|
|
186
|
+
["retry", "retry"],
|
|
187
|
+
["\u65F6\u6BB5", "timeWindows"],
|
|
188
|
+
["\u65F6\u95F4\u7A97", "timeWindows"],
|
|
189
|
+
["time", "timeWindows"],
|
|
190
|
+
["window", "timeWindows"],
|
|
191
|
+
["\u8BA1\u5212", "schedule"],
|
|
192
|
+
["\u5B9A\u65F6", "schedule"],
|
|
193
|
+
["cron", "schedule"],
|
|
194
|
+
["schedule", "schedule"]
|
|
195
|
+
]);
|
|
196
|
+
let actionToken;
|
|
197
|
+
let groupId;
|
|
198
|
+
let rest = [];
|
|
199
|
+
if (/^\d+$/.test(tokens[0] ?? "")) {
|
|
200
|
+
groupId = tokens[0];
|
|
201
|
+
actionToken = tokens[1];
|
|
202
|
+
rest = tokens.slice(2);
|
|
203
|
+
} else if (actionWords.has(tokens[0] ?? "") && /^\d+$/.test(tokens[1] ?? "")) {
|
|
204
|
+
actionToken = tokens[0];
|
|
205
|
+
groupId = tokens[1];
|
|
206
|
+
rest = tokens.slice(2);
|
|
207
|
+
} else if (e.isGroup && actionWords.has(tokens[0] ?? "")) {
|
|
208
|
+
groupId = e.groupId;
|
|
209
|
+
actionToken = tokens[0];
|
|
210
|
+
rest = tokens.slice(1);
|
|
211
|
+
} else if (e.isGroup && /^\d+$/.test(tokens[0] ?? "") === false) {
|
|
212
|
+
groupId = e.groupId;
|
|
213
|
+
actionToken = tokens[0];
|
|
214
|
+
rest = tokens.slice(1);
|
|
215
|
+
}
|
|
216
|
+
if (!groupId || !/^\d+$/.test(groupId)) {
|
|
217
|
+
await e.reply(`\u7F3A\u5C11\u7FA4\u53F7\u53C2\u6570\u3002
|
|
218
|
+
|
|
219
|
+
${cfgHelp}`);
|
|
220
|
+
return true;
|
|
221
|
+
}
|
|
222
|
+
const action = actionWords.get(actionToken ?? "") ?? "";
|
|
223
|
+
if (!action) {
|
|
224
|
+
await e.reply(`\u672A\u77E5\u64CD\u4F5C\uFF1A${actionToken ?? ""}
|
|
225
|
+
|
|
226
|
+
${cfgHelp}`);
|
|
227
|
+
return true;
|
|
228
|
+
}
|
|
229
|
+
if (action === "show") {
|
|
230
|
+
const cfg = config();
|
|
231
|
+
const t = getTargets(cfg).find((it) => String(it.groupId) === String(groupId));
|
|
232
|
+
if (!t) {
|
|
233
|
+
await e.reply(`\u672A\u627E\u5230\u7FA4 ${groupId} \u7684\u914D\u7F6E\uFF0C\u53EF\u5148\u6DFB\u52A0\uFF1A#\u7FA4\u540C\u6B65\u914D\u7F6E ${groupId} \u6DFB\u52A0`);
|
|
234
|
+
return true;
|
|
235
|
+
}
|
|
236
|
+
await e.reply(["\u7FA4\u540C\u6B65\u914D\u7F6E\uFF1A", formatTargetLine(t), `
|
|
237
|
+
\u5B8C\u6574\u914D\u7F6E\u6587\u4EF6\uFF1A${getConfigFilePath()}`].join("\n"));
|
|
238
|
+
return true;
|
|
239
|
+
}
|
|
240
|
+
if (action === "add") {
|
|
241
|
+
const cfg = config();
|
|
242
|
+
const defaultsTarget = ensureTargetWithDefaults(groupId, cfg);
|
|
243
|
+
updateConfigFile((next) => {
|
|
244
|
+
const list = getTargets(next);
|
|
245
|
+
if (list.some((it) => String(it.groupId) === String(groupId))) return next;
|
|
246
|
+
next.groupSyncTargets = [...list, defaultsTarget];
|
|
247
|
+
return next;
|
|
248
|
+
});
|
|
249
|
+
await e.reply(`\u5DF2\u6DFB\u52A0\u7FA4 ${groupId} \u7684\u540C\u6B65\u914D\u7F6E\uFF08\u9ED8\u8BA4\u542F\u7528\uFF09\u3002`);
|
|
250
|
+
return true;
|
|
251
|
+
}
|
|
252
|
+
if (action === "remove") {
|
|
253
|
+
const before = config();
|
|
254
|
+
const beforeList = getTargets(before);
|
|
255
|
+
if (!beforeList.some((it) => String(it.groupId) === String(groupId))) {
|
|
256
|
+
await e.reply(`\u7FA4 ${groupId} \u672A\u914D\u7F6E\uFF0C\u65E0\u9700\u5220\u9664\u3002`);
|
|
257
|
+
return true;
|
|
258
|
+
}
|
|
259
|
+
updateConfigFile((next) => {
|
|
260
|
+
next.groupSyncTargets = getTargets(next).filter((it) => String(it.groupId) !== String(groupId));
|
|
261
|
+
return next;
|
|
262
|
+
});
|
|
263
|
+
await e.reply(`\u5DF2\u5220\u9664\u7FA4 ${groupId} \u7684\u540C\u6B65\u914D\u7F6E\u3002`);
|
|
264
|
+
return true;
|
|
265
|
+
}
|
|
266
|
+
if (action === "enable" || action === "disable") {
|
|
267
|
+
const enabled = action === "enable";
|
|
268
|
+
updateConfigFile((next) => {
|
|
269
|
+
next.groupSyncTargets = upsertTarget(next, groupId, { enabled });
|
|
270
|
+
return next;
|
|
271
|
+
});
|
|
272
|
+
await e.reply(`\u7FA4 ${groupId} \u5DF2${enabled ? "\u542F\u7528" : "\u505C\u7528"}\u81EA\u52A8\u540C\u6B65\u3002`);
|
|
273
|
+
return true;
|
|
274
|
+
}
|
|
275
|
+
if (action === "mode") {
|
|
276
|
+
const mode = parseMode(rest[0]);
|
|
277
|
+
if (!mode) {
|
|
278
|
+
await e.reply("\u6A21\u5F0F\u53C2\u6570\u9519\u8BEF\uFF0C\u8BF7\u4F7F\u7528\uFF1A\u5168\u91CF/\u589E\u91CF \u6216 full/incremental");
|
|
279
|
+
return true;
|
|
280
|
+
}
|
|
281
|
+
updateConfigFile((next) => {
|
|
282
|
+
next.groupSyncTargets = upsertTarget(next, groupId, { mode });
|
|
283
|
+
return next;
|
|
284
|
+
});
|
|
285
|
+
await e.reply(`\u7FA4 ${groupId} \u540C\u6B65\u6A21\u5F0F\u5DF2\u8BBE\u7F6E\u4E3A\uFF1A${mode === "full" ? "\u5168\u91CF" : "\u589E\u91CF"}`);
|
|
286
|
+
return true;
|
|
287
|
+
}
|
|
288
|
+
if (action === "dir") {
|
|
289
|
+
const dirValue = normalizePosixPath(rest.join(" ").trim() || "");
|
|
290
|
+
if (!dirValue || dirValue === "/") {
|
|
291
|
+
await e.reply("\u76EE\u5F55\u53C2\u6570\u4E3A\u7A7A\u6216\u975E\u6CD5\u3002");
|
|
292
|
+
return true;
|
|
293
|
+
}
|
|
294
|
+
updateConfigFile((next) => {
|
|
295
|
+
next.groupSyncTargets = upsertTarget(next, groupId, { targetDir: dirValue });
|
|
296
|
+
return next;
|
|
297
|
+
});
|
|
298
|
+
await e.reply(`\u7FA4 ${groupId} \u76EE\u6807\u76EE\u5F55\u5DF2\u8BBE\u7F6E\u4E3A\uFF1A${dirValue}`);
|
|
299
|
+
return true;
|
|
300
|
+
}
|
|
301
|
+
if (action === "flat") {
|
|
302
|
+
const bool = parseBoolean(rest[0]);
|
|
303
|
+
if (typeof bool === "undefined") {
|
|
304
|
+
await e.reply("\u5E73\u94FA\u53C2\u6570\u9519\u8BEF\uFF0C\u8BF7\u4F7F\u7528\uFF1A\u5F00/\u5173 \u6216 true/false");
|
|
305
|
+
return true;
|
|
306
|
+
}
|
|
307
|
+
updateConfigFile((next) => {
|
|
308
|
+
next.groupSyncTargets = upsertTarget(next, groupId, { flat: bool });
|
|
309
|
+
return next;
|
|
310
|
+
});
|
|
311
|
+
await e.reply(`\u7FA4 ${groupId} \u5E73\u94FA\u4E0A\u4F20\u5DF2\u8BBE\u7F6E\u4E3A\uFF1A${bool ? "\u5F00" : "\u5173"}`);
|
|
312
|
+
return true;
|
|
313
|
+
}
|
|
314
|
+
if (action === "concurrency" || action === "urlConcurrency") {
|
|
315
|
+
const value = parseIntSafe(rest[0]);
|
|
316
|
+
if (!value || value <= 0) {
|
|
317
|
+
await e.reply("\u5E76\u53D1\u53C2\u6570\u9519\u8BEF\uFF0C\u8BF7\u8F93\u5165\u5927\u4E8E0\u7684\u6574\u6570\u3002");
|
|
318
|
+
return true;
|
|
319
|
+
}
|
|
320
|
+
const patch = action === "concurrency" ? { transferConcurrency: value } : { urlConcurrency: value };
|
|
321
|
+
updateConfigFile((next) => {
|
|
322
|
+
next.groupSyncTargets = upsertTarget(next, groupId, patch);
|
|
323
|
+
return next;
|
|
324
|
+
});
|
|
325
|
+
await e.reply(`\u7FA4 ${groupId} \u5DF2\u8BBE\u7F6E${action === "concurrency" ? "\u4E0B\u8F7D/\u4E0A\u4F20" : "\u89E3\u6790URL"}\u5E76\u53D1\uFF1A${value}`);
|
|
326
|
+
return true;
|
|
327
|
+
}
|
|
328
|
+
if (action === "timeout") {
|
|
329
|
+
const sec = parseIntSafe(rest[0]);
|
|
330
|
+
if (!sec || sec <= 0) {
|
|
331
|
+
await e.reply("\u8D85\u65F6\u53C2\u6570\u9519\u8BEF\uFF0C\u8BF7\u8F93\u5165\u5927\u4E8E0\u7684\u6574\u6570\uFF08\u79D2\uFF09\u3002");
|
|
332
|
+
return true;
|
|
333
|
+
}
|
|
334
|
+
const capped = Math.min(3e3, sec);
|
|
335
|
+
updateConfigFile((next) => {
|
|
336
|
+
next.groupSyncTargets = upsertTarget(next, groupId, { fileTimeoutSec: capped });
|
|
337
|
+
return next;
|
|
338
|
+
});
|
|
339
|
+
await e.reply(`\u7FA4 ${groupId} \u5355\u6587\u4EF6\u8D85\u65F6\u5DF2\u8BBE\u7F6E\u4E3A\uFF1A${capped}s${sec !== capped ? "\uFF08\u5DF2\u6309\u4E0A\u96503000s\u622A\u65AD\uFF09" : ""}`);
|
|
340
|
+
return true;
|
|
341
|
+
}
|
|
342
|
+
if (action === "retry") {
|
|
343
|
+
const n = parseIntSafe(rest[0]);
|
|
344
|
+
if (typeof n === "undefined" || n < 0) {
|
|
345
|
+
await e.reply("\u91CD\u8BD5\u6B21\u6570\u53C2\u6570\u9519\u8BEF\uFF0C\u8BF7\u8F93\u5165 >=0 \u7684\u6574\u6570\u3002");
|
|
346
|
+
return true;
|
|
347
|
+
}
|
|
348
|
+
updateConfigFile((next) => {
|
|
349
|
+
next.groupSyncTargets = upsertTarget(next, groupId, { retryTimes: n });
|
|
350
|
+
return next;
|
|
351
|
+
});
|
|
352
|
+
await e.reply(`\u7FA4 ${groupId} \u91CD\u8BD5\u6B21\u6570\u5DF2\u8BBE\u7F6E\u4E3A\uFF1A${n}`);
|
|
353
|
+
return true;
|
|
354
|
+
}
|
|
355
|
+
if (action === "timeWindows") {
|
|
356
|
+
const win = rest.join(" ").trim();
|
|
357
|
+
updateConfigFile((next) => {
|
|
358
|
+
next.groupSyncTargets = upsertTarget(next, groupId, { timeWindows: win });
|
|
359
|
+
return next;
|
|
360
|
+
});
|
|
361
|
+
await e.reply(`\u7FA4 ${groupId} \u540C\u6B65\u65F6\u6BB5\u5DF2\u8BBE\u7F6E\u4E3A\uFF1A${win || "(\u4E0D\u9650\u5236)"}`);
|
|
362
|
+
return true;
|
|
363
|
+
}
|
|
364
|
+
if (action === "schedule") {
|
|
365
|
+
const value = rest.join(" ").trim();
|
|
366
|
+
const bool = parseBoolean(rest[0]);
|
|
367
|
+
const cron = bool == null ? value : "";
|
|
368
|
+
if (!value) {
|
|
369
|
+
await e.reply("\u8BA1\u5212\u53C2\u6570\u4E0D\u80FD\u4E3A\u7A7A\u3002");
|
|
370
|
+
return true;
|
|
371
|
+
}
|
|
372
|
+
updateConfigFile((next) => {
|
|
373
|
+
const existing = getTargets(next).find((it) => String(it.groupId) === String(groupId));
|
|
374
|
+
const base = existing ?? ensureTargetWithDefaults(groupId, next);
|
|
375
|
+
const schedule = { ...base.schedule ?? {} };
|
|
376
|
+
if (typeof bool === "boolean") {
|
|
377
|
+
schedule.enabled = bool;
|
|
378
|
+
} else {
|
|
379
|
+
schedule.cron = cron;
|
|
380
|
+
schedule.enabled = true;
|
|
381
|
+
}
|
|
382
|
+
next.groupSyncTargets = upsertTarget(next, groupId, { schedule });
|
|
383
|
+
return next;
|
|
384
|
+
});
|
|
385
|
+
await e.reply(`\u7FA4 ${groupId} \u5B9A\u65F6\u8BA1\u5212\u5DF2\u66F4\u65B0\uFF1A${value}`);
|
|
386
|
+
return true;
|
|
387
|
+
}
|
|
388
|
+
await e.reply(cfgHelp);
|
|
389
|
+
return true;
|
|
390
|
+
}, {
|
|
391
|
+
name: "\u7FA4\u540C\u6B65\u914D\u7F6E",
|
|
392
|
+
log: true,
|
|
393
|
+
priority: 9999,
|
|
394
|
+
permission: "master"
|
|
395
|
+
});
|
|
396
|
+
export {
|
|
397
|
+
groupSyncConfig
|
|
398
|
+
};
|