koishi-plugin-deltaforceqqhelp 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/lib/index.d.ts +20 -0
- package/lib/index.js +192 -0
- package/package.json +29 -0
- package/readme.md +106 -0
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Context, Schema } from 'koishi';
|
|
2
|
+
export declare const name = "Koishi-plugin-deltaforceqqhelp";
|
|
3
|
+
export interface Config {
|
|
4
|
+
ocrSimilarity?: number;
|
|
5
|
+
}
|
|
6
|
+
export declare const Config: Schema<Config>;
|
|
7
|
+
interface PlayerInfo {
|
|
8
|
+
id: string;
|
|
9
|
+
nickname: string;
|
|
10
|
+
userId?: string;
|
|
11
|
+
registerTime: Date;
|
|
12
|
+
}
|
|
13
|
+
declare module 'koishi' {
|
|
14
|
+
interface Tables {
|
|
15
|
+
deltaforceqqhelp: PlayerInfo;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
export declare const inject: string[];
|
|
19
|
+
export declare function apply(ctx: Context, config: Config): Promise<void>;
|
|
20
|
+
export {};
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
var __create = Object.create;
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name2 in all)
|
|
10
|
+
__defProp(target, name2, { get: all[name2], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/index.ts
|
|
31
|
+
var src_exports = {};
|
|
32
|
+
__export(src_exports, {
|
|
33
|
+
Config: () => Config,
|
|
34
|
+
apply: () => apply,
|
|
35
|
+
inject: () => inject,
|
|
36
|
+
name: () => name
|
|
37
|
+
});
|
|
38
|
+
module.exports = __toCommonJS(src_exports);
|
|
39
|
+
var import_koishi = require("koishi");
|
|
40
|
+
var import_tesseract = require("tesseract.js");
|
|
41
|
+
var import_axios = __toESM(require("axios"));
|
|
42
|
+
var name = "Koishi-plugin-deltaforceqqhelp";
|
|
43
|
+
var Config = import_koishi.Schema.object({
|
|
44
|
+
ocrSimilarity: import_koishi.Schema.number().min(0).max(1).default(0.5).description("OCR 模糊匹配阈值")
|
|
45
|
+
});
|
|
46
|
+
function calculateSimilarity(str1, str2) {
|
|
47
|
+
const len1 = str1.length;
|
|
48
|
+
const len2 = str2.length;
|
|
49
|
+
const matrix = Array(len1 + 1).fill(null).map(() => Array(len2 + 1).fill(0));
|
|
50
|
+
for (let i = 0; i <= len1; i++) matrix[i][0] = i;
|
|
51
|
+
for (let j = 0; j <= len2; j++) matrix[0][j] = j;
|
|
52
|
+
for (let i = 1; i <= len1; i++) {
|
|
53
|
+
for (let j = 1; j <= len2; j++) {
|
|
54
|
+
const cost = str1[i - 1] === str2[j - 1] ? 0 : 1;
|
|
55
|
+
matrix[i][j] = Math.min(
|
|
56
|
+
matrix[i - 1][j] + 1,
|
|
57
|
+
matrix[i][j - 1] + 1,
|
|
58
|
+
matrix[i - 1][j - 1] + cost
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
const maxLen = Math.max(len1, len2);
|
|
63
|
+
return maxLen === 0 ? 1 : 1 - matrix[len1][len2] / maxLen;
|
|
64
|
+
}
|
|
65
|
+
__name(calculateSimilarity, "calculateSimilarity");
|
|
66
|
+
var inject = ["database"];
|
|
67
|
+
async function apply(ctx, config) {
|
|
68
|
+
if (!process.env.TESSDATA_PREFIX) {
|
|
69
|
+
process.env.TESSDATA_PREFIX = require("path").join(__dirname, "..", "..", "tessdata");
|
|
70
|
+
}
|
|
71
|
+
ctx.model.extend("deltaforceqqhelp", {
|
|
72
|
+
id: { type: "string", length: 100, nullable: false },
|
|
73
|
+
nickname: { type: "string", length: 100, nullable: false },
|
|
74
|
+
userId: { type: "string", length: 50 },
|
|
75
|
+
registerTime: "timestamp"
|
|
76
|
+
}, { primary: "id", autoInc: false });
|
|
77
|
+
ctx.command("注册 <gameId> <qqNickname>", "注册玩家信息").action(async ({ session }, gameId, qqNickname) => {
|
|
78
|
+
if (!gameId || !qqNickname) return "请输入正确的格式:注册 <游戏ID> <QQ昵称>";
|
|
79
|
+
if (gameId.length > 50) return "游戏ID过长,最多50个字符";
|
|
80
|
+
if (qqNickname.length > 50) return "QQ昵称过长,最多50个字符";
|
|
81
|
+
const userId = session?.userId;
|
|
82
|
+
try {
|
|
83
|
+
const existing = await ctx.database.get("deltaforceqqhelp", { id: gameId });
|
|
84
|
+
if (existing.length > 0) {
|
|
85
|
+
const cur = existing[0];
|
|
86
|
+
if (cur.nickname === qqNickname) {
|
|
87
|
+
await ctx.database.set("deltaforceqqhelp", { id: gameId }, { userId, registerTime: /* @__PURE__ */ new Date() });
|
|
88
|
+
return `玩家 "${gameId}" 的信息已更新!
|
|
89
|
+
QQ昵称:${qqNickname}`;
|
|
90
|
+
}
|
|
91
|
+
const nickUsers = await ctx.database.get("deltaforceqqhelp", { nickname: qqNickname });
|
|
92
|
+
const used = nickUsers.some((u) => u.id !== gameId);
|
|
93
|
+
if (used) return `昵称 "${qqNickname}" 已被其他玩家使用,请换一个昵称`;
|
|
94
|
+
await ctx.database.set("deltaforceqqhelp", { id: gameId }, { nickname: qqNickname, userId, registerTime: /* @__PURE__ */ new Date() });
|
|
95
|
+
return `玩家 "${gameId}" 的信息已更新!
|
|
96
|
+
QQ昵称:${qqNickname}`;
|
|
97
|
+
} else {
|
|
98
|
+
const nickUsers = await ctx.database.get("deltaforceqqhelp", { nickname: qqNickname });
|
|
99
|
+
if (nickUsers.length > 0) return `昵称 "${qqNickname}" 已被使用,请换一个昵称`;
|
|
100
|
+
await ctx.database.create("deltaforceqqhelp", { id: gameId, nickname: qqNickname, userId, registerTime: /* @__PURE__ */ new Date() });
|
|
101
|
+
return `玩家 "${gameId}" 注册成功!
|
|
102
|
+
QQ昵称:${qqNickname}`;
|
|
103
|
+
}
|
|
104
|
+
} catch (e) {
|
|
105
|
+
ctx.logger.error("注册失败", e);
|
|
106
|
+
return "注册失败,请稍后重试";
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
ctx.command("查找 <gameId>", "查找玩家信息").action(async ({ session }, gameId) => {
|
|
110
|
+
if (!gameId) return "请输入游戏ID:查找 <游戏ID>";
|
|
111
|
+
try {
|
|
112
|
+
const players = await ctx.database.get("deltaforceqqhelp", { id: gameId });
|
|
113
|
+
return players.length ? `是群友,昵称是 ${players[0].nickname}` : "不是群友";
|
|
114
|
+
} catch (e) {
|
|
115
|
+
ctx.logger.error("查找失败", e);
|
|
116
|
+
return "查找失败,请稍后重试";
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
ctx.command("玩家列表", "列出所有注册玩家").action(async () => {
|
|
120
|
+
try {
|
|
121
|
+
const players = await ctx.database.get("deltaforceqqhelp", {});
|
|
122
|
+
if (players.length === 0) return "暂无注册玩家";
|
|
123
|
+
let msg = `已注册玩家(共 ${players.length} 人):
|
|
124
|
+
`;
|
|
125
|
+
players.forEach((p, i) => {
|
|
126
|
+
msg += `
|
|
127
|
+
${i + 1}. 游戏ID: ${p.id} - QQ昵称: ${p.nickname}`;
|
|
128
|
+
});
|
|
129
|
+
return msg;
|
|
130
|
+
} catch (e) {
|
|
131
|
+
ctx.logger.error("获取玩家列表失败", e);
|
|
132
|
+
return "获取玩家列表失败";
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
ctx.command("图查", "上传截图,识别图中是否包含已注册群友的三角洲昵称").usage('直接上传图片即可,例:"图查 {图片}"').action(async ({ session }) => {
|
|
136
|
+
const imgEl = session.elements?.find((el) => el.type === "img");
|
|
137
|
+
if (!imgEl) return "请上传一张图片~";
|
|
138
|
+
const url = imgEl.attrs.src;
|
|
139
|
+
if (!url) return "图片链接异常";
|
|
140
|
+
ctx.logger.info("图查图片URL:", url);
|
|
141
|
+
await session.send("正在识别图片,请稍候…");
|
|
142
|
+
try {
|
|
143
|
+
const { data } = await import_axios.default.get(url, { responseType: "arraybuffer" });
|
|
144
|
+
const buf = Buffer.from(data);
|
|
145
|
+
const worker = await (0, import_tesseract.createWorker)("chi_sim+eng");
|
|
146
|
+
const { data: { text } } = await worker.recognize(buf);
|
|
147
|
+
await worker.terminate();
|
|
148
|
+
ctx.logger.info("OCR识别结果原文:", text);
|
|
149
|
+
const ocrLines = text.split(/\r?\n/).map((l) => l.replace(/\s+/g, "").toUpperCase()).filter(Boolean);
|
|
150
|
+
ctx.logger.info("OCR处理后的行:", ocrLines);
|
|
151
|
+
if (ocrLines.length === 0) return "图中未识别到任何文字";
|
|
152
|
+
const players = await ctx.database.get("deltaforceqqhelp", {});
|
|
153
|
+
if (players.length === 0) return "当前没有任何注册记录";
|
|
154
|
+
const dict = /* @__PURE__ */ new Map();
|
|
155
|
+
players.forEach((p) => {
|
|
156
|
+
dict.set(p.id.replace(/\s+/g, "").toUpperCase(), p);
|
|
157
|
+
});
|
|
158
|
+
ctx.logger.info("注册玩家字典:", Array.from(dict.keys()));
|
|
159
|
+
const threshold = config.ocrSimilarity ?? 0.5;
|
|
160
|
+
ctx.logger.info("相似度阈值:", threshold);
|
|
161
|
+
const hitNicknames = [];
|
|
162
|
+
const already = /* @__PURE__ */ new Set();
|
|
163
|
+
for (const line of ocrLines) {
|
|
164
|
+
for (const [key, info] of dict) {
|
|
165
|
+
if (already.has(info.nickname)) continue;
|
|
166
|
+
const similarity = calculateSimilarity(line, key);
|
|
167
|
+
if (similarity >= threshold) {
|
|
168
|
+
ctx.logger.info(`匹配成功: 行 "${line}" 与游戏ID "${key}" 相似度 ${similarity}`);
|
|
169
|
+
hitNicknames.push(info.nickname);
|
|
170
|
+
already.add(info.nickname);
|
|
171
|
+
break;
|
|
172
|
+
} else {
|
|
173
|
+
ctx.logger.debug(`匹配失败: 行 "${line}" 与游戏ID "${key}" 相似度 ${similarity}`);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
ctx.logger.info("最终匹配到的昵称:", hitNicknames);
|
|
178
|
+
return hitNicknames.length ? `图中有群友:${hitNicknames.join(" ")}` : "图中未找到已注册群友";
|
|
179
|
+
} catch (e) {
|
|
180
|
+
ctx.logger.error("图查失败", e);
|
|
181
|
+
return "识别失败,请稍后重试";
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
__name(apply, "apply");
|
|
186
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
187
|
+
0 && (module.exports = {
|
|
188
|
+
Config,
|
|
189
|
+
apply,
|
|
190
|
+
inject,
|
|
191
|
+
name
|
|
192
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "koishi-plugin-deltaforceqqhelp",
|
|
3
|
+
"description": "快速识别对面有没有群内群友",
|
|
4
|
+
"version": "0.0.1",
|
|
5
|
+
"main": "lib/index.js",
|
|
6
|
+
"typings": "lib/index.d.ts",
|
|
7
|
+
"files": [
|
|
8
|
+
"lib",
|
|
9
|
+
"dist"
|
|
10
|
+
],
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"keywords": [
|
|
13
|
+
"chatbot",
|
|
14
|
+
"koishi",
|
|
15
|
+
"plugin",
|
|
16
|
+
"deltaforce",
|
|
17
|
+
"player",
|
|
18
|
+
"registration",
|
|
19
|
+
"qq",
|
|
20
|
+
"help"
|
|
21
|
+
],
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"axios": "^1.13.2",
|
|
24
|
+
"tesseract.js": "^7.0.0"
|
|
25
|
+
},
|
|
26
|
+
"peerDependencies": {
|
|
27
|
+
"koishi": "^4.18.8"
|
|
28
|
+
}
|
|
29
|
+
}
|
package/readme.md
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# Koishi 三角洲游戏玩家注册与查询插件
|
|
2
|
+
|
|
3
|
+
这是一个为Koishi机器人开发的插件,用于提供三角洲游戏群的玩家快速寻找这局有没有群友(仅大战场)。
|
|
4
|
+
|
|
5
|
+
## 功能特性
|
|
6
|
+
|
|
7
|
+
1. **玩家注册**
|
|
8
|
+
- 注册 玩家ID(三角洲游戏名称) 玩家昵称(QQ昵称)
|
|
9
|
+
- 支持更新已注册玩家的信息
|
|
10
|
+
|
|
11
|
+
2. **玩家查询**
|
|
12
|
+
- 根据游戏ID查找玩家
|
|
13
|
+
- 返回是否为群友及对应的QQ昵称
|
|
14
|
+
|
|
15
|
+
3. **玩家列表**
|
|
16
|
+
- 查看所有已注册玩家的列表
|
|
17
|
+
|
|
18
|
+
## 安装方法
|
|
19
|
+
|
|
20
|
+
1. 确保已安装Koishi框架
|
|
21
|
+
2. 将插件添加到`koishi.yml`配置文件中:
|
|
22
|
+
```yaml
|
|
23
|
+
plugins:
|
|
24
|
+
Koishi-plugin-deltaforceqqhelp: {}
|
|
25
|
+
```
|
|
26
|
+
3. 重启Koishi服务
|
|
27
|
+
|
|
28
|
+
## 使用方法
|
|
29
|
+
|
|
30
|
+
### 1. 注册玩家
|
|
31
|
+
```
|
|
32
|
+
注册 <游戏ID> <QQ昵称>
|
|
33
|
+
```
|
|
34
|
+
示例:
|
|
35
|
+
```
|
|
36
|
+
注册 Player123 小明
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### 2. 查找玩家
|
|
40
|
+
```
|
|
41
|
+
查找 <游戏ID>
|
|
42
|
+
```
|
|
43
|
+
示例:
|
|
44
|
+
```
|
|
45
|
+
查找 Player123
|
|
46
|
+
```
|
|
47
|
+
返回结果:
|
|
48
|
+
- 如果已注册:`是群友,昵称是 小明`
|
|
49
|
+
- 如果未注册:`不是群友`
|
|
50
|
+
|
|
51
|
+
### 3. 查看玩家列表
|
|
52
|
+
```
|
|
53
|
+
玩家列表
|
|
54
|
+
```
|
|
55
|
+
返回所有已注册玩家的列表。
|
|
56
|
+
|
|
57
|
+
## 技术细节
|
|
58
|
+
|
|
59
|
+
### 数据库结构
|
|
60
|
+
插件使用Koishi的数据库功能存储玩家信息:
|
|
61
|
+
- `id`: 玩家ID(三角洲游戏名称),主键
|
|
62
|
+
- `nickname`: 玩家昵称(QQ昵称)
|
|
63
|
+
- `userId`: QQ用户ID(可选)
|
|
64
|
+
- `registerTime`: 注册时间
|
|
65
|
+
|
|
66
|
+
### 相似度算法
|
|
67
|
+
- 使用Levenshtein距离计算字符串相似度
|
|
68
|
+
- 相似度阈值:60%
|
|
69
|
+
- 不区分大小写进行匹配
|
|
70
|
+
|
|
71
|
+
## 开发说明
|
|
72
|
+
|
|
73
|
+
### 构建插件
|
|
74
|
+
```bash
|
|
75
|
+
cd external/deltaforceqqhelp
|
|
76
|
+
yarn build
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### 测试插件
|
|
80
|
+
1. 启动Koishi服务
|
|
81
|
+
2. 使用上述命令进行测试
|
|
82
|
+
|
|
83
|
+
## 注意事项
|
|
84
|
+
|
|
85
|
+
1. 插件会自动创建数据库表,无需手动配置
|
|
86
|
+
|
|
87
|
+
## 故障排除
|
|
88
|
+
|
|
89
|
+
1. **数据库错误**
|
|
90
|
+
- 检查数据库连接是否正常
|
|
91
|
+
- 确认有足够的数据库权限
|
|
92
|
+
|
|
93
|
+
2. **插件未加载**
|
|
94
|
+
- 检查koishi.yml配置
|
|
95
|
+
- 查看Koishi启动日志
|
|
96
|
+
- 确认插件已正确构建
|
|
97
|
+
|
|
98
|
+
## 更新日志
|
|
99
|
+
|
|
100
|
+
### v0.0.1
|
|
101
|
+
- 初始版本发布
|
|
102
|
+
- 实现基本注册、查询功能
|
|
103
|
+
|
|
104
|
+
## 许可证
|
|
105
|
+
|
|
106
|
+
MIT License
|