koishi-plugin-disaster-warning 0.1.3 → 0.1.4
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 +30 -1
- package/lib/chatluna.d.ts +41 -0
- package/lib/chatluna.js +346 -0
- package/lib/index.d.ts +10 -0
- package/lib/index.js +18 -1
- package/package.json +14 -4
package/README.md
CHANGED
|
@@ -30,6 +30,9 @@ Koishi 灾害预警插件,支持多数据源(地震、海啸、气象预警
|
|
|
30
30
|
### 🔁 事件去重功能
|
|
31
31
|
插件具备基础的事件去重功能,防止同一地震被同一个数据源重复推送。
|
|
32
32
|
|
|
33
|
+
### 🤖 ChatLuna 工具调用
|
|
34
|
+
可选注册 `disaster_warning` 工具给 ChatLuna 使用,让模型主动查询近期地震、按地点/震级/时间过滤,或查看数据源连接状态。适合回答“哪里地震了”“某地最近有没有地震”“我关心的人在某地,那里安全吗”等问题。
|
|
35
|
+
|
|
33
36
|
## 🚀 安装与使用
|
|
34
37
|
|
|
35
38
|
### 安装
|
|
@@ -54,7 +57,33 @@ npm install koishi-plugin-disaster-warning
|
|
|
54
57
|
|
|
55
58
|
`regions` 同时控制数据源连接与地震震中地区过滤。例如仅开启中国大陆和台湾、关闭日本与全球时,插件仍可连接 CENC/CWA 等相关数据源,但智利、印尼等不在已开启地区内的地震会被丢弃,不会因为由 CENC 发布就被推送。
|
|
56
59
|
|
|
57
|
-
当前震中识别优先使用地名关键词,其次使用经纬度范围;无法识别且带有非空地名的地震默认视为全球事件。
|
|
60
|
+
当前震中识别优先使用地名关键词,其次使用经纬度范围;无法识别且带有非空地名的地震默认视为全球事件。
|
|
61
|
+
|
|
62
|
+
### ChatLuna 工具设置
|
|
63
|
+
|
|
64
|
+
如果安装了 `koishi-plugin-chatluna`,可以在 `chatluna` 配置项中开启工具注册:
|
|
65
|
+
|
|
66
|
+
- `chatluna.enabled`:开启后注册 ChatLuna 工具,默认关闭。
|
|
67
|
+
- `chatluna.name`:工具名称,默认 `disaster_warning`。
|
|
68
|
+
- `chatluna.default_source`:模型未指定来源时查询 `all`、`cenc`、`jma` 或 `usgs`。
|
|
69
|
+
- `chatluna.default_days`:默认查询最近多少天。
|
|
70
|
+
- `chatluna.default_limit`:默认最多返回多少条记录。
|
|
71
|
+
- `chatluna.min_magnitude`:默认最低震级。
|
|
72
|
+
- `chatluna.include_usgs_when_all`:综合查询时是否包含 USGS 全球地震。
|
|
73
|
+
|
|
74
|
+
工具输入示例:
|
|
75
|
+
|
|
76
|
+
```json
|
|
77
|
+
{
|
|
78
|
+
"action": "recent_earthquakes",
|
|
79
|
+
"location": "智利",
|
|
80
|
+
"min_magnitude": 5,
|
|
81
|
+
"days": 7,
|
|
82
|
+
"limit": 5
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
也可以使用 `{ "action": "status" }` 查询当前数据源连接状态。
|
|
58
87
|
|
|
59
88
|
## 📋 使用命令
|
|
60
89
|
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { Context } from 'koishi';
|
|
2
|
+
import { StructuredTool } from '@langchain/core/tools';
|
|
3
|
+
import type { Config } from './index';
|
|
4
|
+
import { DisasterWarningService } from './service';
|
|
5
|
+
type ToolSource = 'all' | 'cenc' | 'jma' | 'usgs';
|
|
6
|
+
type ToolInput = {
|
|
7
|
+
action?: 'recent_earthquakes' | 'status';
|
|
8
|
+
source?: ToolSource;
|
|
9
|
+
location?: string;
|
|
10
|
+
min_magnitude?: number;
|
|
11
|
+
days?: number;
|
|
12
|
+
limit?: number;
|
|
13
|
+
};
|
|
14
|
+
interface ChatLunaToolConfig {
|
|
15
|
+
enabled: boolean;
|
|
16
|
+
name: string;
|
|
17
|
+
description: string;
|
|
18
|
+
default_source: ToolSource;
|
|
19
|
+
default_limit: number;
|
|
20
|
+
default_days: number;
|
|
21
|
+
min_magnitude: number;
|
|
22
|
+
include_usgs_when_all: boolean;
|
|
23
|
+
}
|
|
24
|
+
export declare class DisasterWarningTool extends StructuredTool<any, ToolInput, ToolInput, string> {
|
|
25
|
+
private ctx;
|
|
26
|
+
private config;
|
|
27
|
+
private service;
|
|
28
|
+
name: string;
|
|
29
|
+
description: string;
|
|
30
|
+
schema: any;
|
|
31
|
+
constructor(ctx: Context, config: Config, service: DisasterWarningService);
|
|
32
|
+
_call(input: ToolInput): Promise<string>;
|
|
33
|
+
private formatStatus;
|
|
34
|
+
private queryRecentEarthquakes;
|
|
35
|
+
private fetchSource;
|
|
36
|
+
private fetchWolfxList;
|
|
37
|
+
private fetchUsgs;
|
|
38
|
+
}
|
|
39
|
+
export declare function applyChatLunaTools(ctx: Context, config: Config, service: DisasterWarningService): void;
|
|
40
|
+
export declare function getChatLunaConfig(config: Config): ChatLunaToolConfig;
|
|
41
|
+
export {};
|
package/lib/chatluna.js
ADDED
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DisasterWarningTool = void 0;
|
|
4
|
+
exports.applyChatLunaTools = applyChatLunaTools;
|
|
5
|
+
exports.getChatLunaConfig = getChatLunaConfig;
|
|
6
|
+
const koishi_1 = require("koishi");
|
|
7
|
+
const tools_1 = require("@langchain/core/tools");
|
|
8
|
+
const zod_1 = require("zod");
|
|
9
|
+
const logger = new koishi_1.Logger('disaster-chatluna');
|
|
10
|
+
const sourceSchema = zod_1.z.enum(['all', 'cenc', 'jma', 'usgs']);
|
|
11
|
+
const actionSchema = zod_1.z.enum(['recent_earthquakes', 'status']);
|
|
12
|
+
const toolSchema = zod_1.z.object({
|
|
13
|
+
action: actionSchema.optional().describe('操作类型:recent_earthquakes 查询近期地震,status 查询数据源连接状态。默认 recent_earthquakes。'),
|
|
14
|
+
source: sourceSchema.optional().describe('数据源:all 综合查询,cenc 中国地震台网列表,jma 日本气象厅列表,usgs USGS 全球地震。默认使用插件配置。'),
|
|
15
|
+
location: zod_1.z.string().optional().describe('可选,地点关键词、国家、地区或城市,例如“四川”“台湾”“日本”“智利”“Chile”。'),
|
|
16
|
+
min_magnitude: zod_1.z.number().min(0).max(10).optional().describe('可选,最低震级,默认使用插件配置。'),
|
|
17
|
+
days: zod_1.z.number().int().min(1).max(30).optional().describe('可选,查询最近多少天,默认使用插件配置,最多 30 天。'),
|
|
18
|
+
limit: zod_1.z.number().int().min(1).max(50).optional().describe('可选,最多返回多少条地震记录,默认使用插件配置。')
|
|
19
|
+
});
|
|
20
|
+
const DEFAULT_CHATLUNA_CONFIG = {
|
|
21
|
+
enabled: false,
|
|
22
|
+
name: 'disaster_warning',
|
|
23
|
+
description: '查询近期地震和灾害预警数据源状态,可按地点、震级、时间范围过滤。适合回答“哪里地震了”“某地最近有没有地震”“关心的人所在地区是否有地震”等问题。',
|
|
24
|
+
default_source: 'all',
|
|
25
|
+
default_limit: 8,
|
|
26
|
+
default_days: 7,
|
|
27
|
+
min_magnitude: 4,
|
|
28
|
+
include_usgs_when_all: true
|
|
29
|
+
};
|
|
30
|
+
class DisasterWarningTool extends tools_1.StructuredTool {
|
|
31
|
+
constructor(ctx, config, service) {
|
|
32
|
+
super({});
|
|
33
|
+
this.ctx = ctx;
|
|
34
|
+
this.config = config;
|
|
35
|
+
this.service = service;
|
|
36
|
+
this.schema = toolSchema;
|
|
37
|
+
const cfg = getChatLunaConfig(config);
|
|
38
|
+
this.name = normalizeToolName(cfg.name);
|
|
39
|
+
this.description = cfg.description.trim() || DEFAULT_CHATLUNA_CONFIG.description;
|
|
40
|
+
}
|
|
41
|
+
async _call(input) {
|
|
42
|
+
const action = input.action || 'recent_earthquakes';
|
|
43
|
+
try {
|
|
44
|
+
if (action === 'status') {
|
|
45
|
+
return JSON.stringify(this.formatStatus(), null, 2);
|
|
46
|
+
}
|
|
47
|
+
const cfg = getChatLunaConfig(this.config);
|
|
48
|
+
const options = {
|
|
49
|
+
source: input.source || cfg.default_source,
|
|
50
|
+
location: input.location?.trim() || undefined,
|
|
51
|
+
minMagnitude: input.min_magnitude ?? cfg.min_magnitude,
|
|
52
|
+
days: clamp(input.days ?? cfg.default_days, 1, 30),
|
|
53
|
+
limit: clamp(input.limit ?? cfg.default_limit, 1, 50)
|
|
54
|
+
};
|
|
55
|
+
const result = await this.queryRecentEarthquakes(options);
|
|
56
|
+
return JSON.stringify(result, null, 2);
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
60
|
+
logger.warn(`ChatLuna tool call failed: ${message}`);
|
|
61
|
+
return `灾害预警查询失败:${message}`;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
formatStatus() {
|
|
65
|
+
const status = this.service.getStatus();
|
|
66
|
+
return {
|
|
67
|
+
type: 'disaster_warning_status',
|
|
68
|
+
generated_at: new Date().toISOString(),
|
|
69
|
+
sources: Object.entries(status).map(([name, item]) => ({
|
|
70
|
+
name,
|
|
71
|
+
connected: item.connected,
|
|
72
|
+
retry_count: item.retryCount,
|
|
73
|
+
url: item.url
|
|
74
|
+
}))
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
async queryRecentEarthquakes(options) {
|
|
78
|
+
const cfg = getChatLunaConfig(this.config);
|
|
79
|
+
const sources = expandSources(options.source, cfg.include_usgs_when_all);
|
|
80
|
+
const settled = await Promise.allSettled(sources.map((source) => this.fetchSource(source, options)));
|
|
81
|
+
const errors = [];
|
|
82
|
+
const records = [];
|
|
83
|
+
settled.forEach((item, index) => {
|
|
84
|
+
const source = sources[index];
|
|
85
|
+
if (item.status === 'fulfilled') {
|
|
86
|
+
records.push(...item.value);
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
const message = item.reason instanceof Error ? item.reason.message : String(item.reason);
|
|
90
|
+
errors.push({ source, message });
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
const terms = buildSearchTerms(options.location);
|
|
94
|
+
const filtered = records
|
|
95
|
+
.filter((record) => record.magnitude === undefined || record.magnitude >= options.minMagnitude)
|
|
96
|
+
.filter((record) => isWithinDays(record.time, options.days))
|
|
97
|
+
.filter((record) => matchesLocation(record, terms))
|
|
98
|
+
.sort((a, b) => timeValue(b.time) - timeValue(a.time))
|
|
99
|
+
.slice(0, options.limit);
|
|
100
|
+
return compact({
|
|
101
|
+
type: 'recent_earthquakes',
|
|
102
|
+
generated_at: new Date().toISOString(),
|
|
103
|
+
query: {
|
|
104
|
+
source: options.source,
|
|
105
|
+
expanded_sources: sources,
|
|
106
|
+
location: options.location,
|
|
107
|
+
min_magnitude: options.minMagnitude,
|
|
108
|
+
days: options.days,
|
|
109
|
+
limit: options.limit
|
|
110
|
+
},
|
|
111
|
+
count: filtered.length,
|
|
112
|
+
earthquakes: filtered,
|
|
113
|
+
errors: errors.length ? errors : undefined,
|
|
114
|
+
note: '地震查询工具用于对话查询,不会改变群推送过滤配置。regions.global 关闭时仍可通过工具主动查询全球地震。'
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
fetchSource(source, options) {
|
|
118
|
+
if (source === 'cenc')
|
|
119
|
+
return this.fetchWolfxList('cenc');
|
|
120
|
+
if (source === 'jma')
|
|
121
|
+
return this.fetchWolfxList('jma');
|
|
122
|
+
if (source === 'usgs')
|
|
123
|
+
return this.fetchUsgs(options);
|
|
124
|
+
return Promise.resolve([]);
|
|
125
|
+
}
|
|
126
|
+
async fetchWolfxList(type) {
|
|
127
|
+
const cache = this.service.getEqListCache();
|
|
128
|
+
let data = cache[type];
|
|
129
|
+
if (!data || Object.keys(data).length === 0) {
|
|
130
|
+
const url = type === 'cenc'
|
|
131
|
+
? 'https://api.wolfx.jp/cenc_eqlist.json'
|
|
132
|
+
: 'https://api.wolfx.jp/jma_eqlist.json';
|
|
133
|
+
data = await this.ctx.http.get(url);
|
|
134
|
+
}
|
|
135
|
+
const keys = Object.keys(data || {})
|
|
136
|
+
.filter((key) => /^No\d+$/.test(key))
|
|
137
|
+
.sort((a, b) => Number(a.slice(2)) - Number(b.slice(2)));
|
|
138
|
+
return keys
|
|
139
|
+
.map((key) => parseWolfxRecord(type, data[key]))
|
|
140
|
+
.filter((record) => Boolean(record));
|
|
141
|
+
}
|
|
142
|
+
async fetchUsgs(options) {
|
|
143
|
+
const url = new URL('https://earthquake.usgs.gov/fdsnws/event/1/query');
|
|
144
|
+
url.searchParams.set('format', 'geojson');
|
|
145
|
+
url.searchParams.set('orderby', 'time');
|
|
146
|
+
url.searchParams.set('starttime', new Date(Date.now() - options.days * 86400000).toISOString());
|
|
147
|
+
url.searchParams.set('minmagnitude', String(options.minMagnitude));
|
|
148
|
+
url.searchParams.set('limit', String(Math.min(Math.max(options.limit * 5, 50), 200)));
|
|
149
|
+
const data = await this.ctx.http.get(url.toString());
|
|
150
|
+
const features = Array.isArray(data?.features) ? data.features : [];
|
|
151
|
+
return features.map(parseUsgsFeature).filter((record) => Boolean(record));
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
exports.DisasterWarningTool = DisasterWarningTool;
|
|
155
|
+
function applyChatLunaTools(ctx, config, service) {
|
|
156
|
+
ctx.on('ready', () => {
|
|
157
|
+
const cfg = getChatLunaConfig(config);
|
|
158
|
+
if (!cfg.enabled)
|
|
159
|
+
return;
|
|
160
|
+
const chatluna = ctx.chatluna;
|
|
161
|
+
if (!chatluna?.platform?.registerTool) {
|
|
162
|
+
logger.warn('未检测到 ChatLuna 服务,跳过注册灾害预警工具。');
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
const toolName = normalizeToolName(cfg.name);
|
|
166
|
+
ctx.effect(() => chatluna.platform.registerTool(toolName, {
|
|
167
|
+
description: cfg.description.trim() || DEFAULT_CHATLUNA_CONFIG.description,
|
|
168
|
+
selector() {
|
|
169
|
+
return true;
|
|
170
|
+
},
|
|
171
|
+
createTool() {
|
|
172
|
+
return new DisasterWarningTool(ctx, config, service);
|
|
173
|
+
},
|
|
174
|
+
meta: {
|
|
175
|
+
source: 'extension',
|
|
176
|
+
group: 'disaster-warning',
|
|
177
|
+
tags: ['disaster', 'earthquake', 'warning'],
|
|
178
|
+
defaultAvailability: {
|
|
179
|
+
enabled: true,
|
|
180
|
+
main: true,
|
|
181
|
+
chatluna: true,
|
|
182
|
+
characterScope: 'all'
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}));
|
|
186
|
+
logger.info(`已注册 ChatLuna 灾害预警工具:${toolName}`);
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
function getChatLunaConfig(config) {
|
|
190
|
+
return {
|
|
191
|
+
...DEFAULT_CHATLUNA_CONFIG,
|
|
192
|
+
...(config.chatluna || {})
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
function normalizeToolName(name) {
|
|
196
|
+
const normalized = (name || DEFAULT_CHATLUNA_CONFIG.name).trim();
|
|
197
|
+
return normalized || DEFAULT_CHATLUNA_CONFIG.name;
|
|
198
|
+
}
|
|
199
|
+
function expandSources(source, includeUsgsWhenAll) {
|
|
200
|
+
if (source !== 'all')
|
|
201
|
+
return [source];
|
|
202
|
+
return includeUsgsWhenAll ? ['cenc', 'jma', 'usgs'] : ['cenc', 'jma'];
|
|
203
|
+
}
|
|
204
|
+
function parseWolfxRecord(type, item) {
|
|
205
|
+
if (!item || typeof item !== 'object')
|
|
206
|
+
return null;
|
|
207
|
+
const magnitude = toNumber(item.magnitude);
|
|
208
|
+
return compact({
|
|
209
|
+
source: type,
|
|
210
|
+
source_name: type === 'cenc' ? 'CENC 中国地震台网' : 'JMA 日本气象厅',
|
|
211
|
+
id: item.md5 || item.id,
|
|
212
|
+
time: normalizeTime(item.time),
|
|
213
|
+
location: item.location || item.hypocenter || '',
|
|
214
|
+
magnitude,
|
|
215
|
+
depth_km: parseDepth(item.depth),
|
|
216
|
+
intensity: item.intensity ? String(item.intensity) : undefined,
|
|
217
|
+
scale: item.shindo ? String(item.shindo) : undefined,
|
|
218
|
+
latitude: toNumber(item.latitude),
|
|
219
|
+
longitude: toNumber(item.longitude)
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
function parseUsgsFeature(feature) {
|
|
223
|
+
const props = feature?.properties || {};
|
|
224
|
+
const coordinates = feature?.geometry?.coordinates || [];
|
|
225
|
+
if (!props.place && props.mag === undefined)
|
|
226
|
+
return null;
|
|
227
|
+
return compact({
|
|
228
|
+
source: 'usgs',
|
|
229
|
+
source_name: 'USGS',
|
|
230
|
+
id: feature.id,
|
|
231
|
+
time: props.time ? new Date(props.time).toISOString() : undefined,
|
|
232
|
+
location: props.place || '',
|
|
233
|
+
magnitude: toNumber(props.mag),
|
|
234
|
+
depth_km: toNumber(coordinates[2]),
|
|
235
|
+
latitude: toNumber(coordinates[1]),
|
|
236
|
+
longitude: toNumber(coordinates[0]),
|
|
237
|
+
url: props.url
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
function normalizeTime(value) {
|
|
241
|
+
if (!value)
|
|
242
|
+
return undefined;
|
|
243
|
+
const text = String(value).trim();
|
|
244
|
+
const date = new Date(text.replace(/\//g, '-'));
|
|
245
|
+
if (!Number.isFinite(date.getTime()))
|
|
246
|
+
return text;
|
|
247
|
+
return date.toISOString();
|
|
248
|
+
}
|
|
249
|
+
function parseDepth(value) {
|
|
250
|
+
if (value === undefined || value === null)
|
|
251
|
+
return undefined;
|
|
252
|
+
const match = String(value).match(/-?\d+(?:\.\d+)?/);
|
|
253
|
+
return match ? Number(match[0]) : undefined;
|
|
254
|
+
}
|
|
255
|
+
function toNumber(value) {
|
|
256
|
+
if (value === undefined || value === null || value === '')
|
|
257
|
+
return undefined;
|
|
258
|
+
const num = Number(value);
|
|
259
|
+
return Number.isFinite(num) ? num : undefined;
|
|
260
|
+
}
|
|
261
|
+
function isWithinDays(time, days) {
|
|
262
|
+
const value = timeValue(time);
|
|
263
|
+
if (!value)
|
|
264
|
+
return true;
|
|
265
|
+
return value >= Date.now() - days * 86400000;
|
|
266
|
+
}
|
|
267
|
+
function timeValue(time) {
|
|
268
|
+
if (!time)
|
|
269
|
+
return 0;
|
|
270
|
+
const value = new Date(time).getTime();
|
|
271
|
+
return Number.isFinite(value) ? value : 0;
|
|
272
|
+
}
|
|
273
|
+
function matchesLocation(record, terms) {
|
|
274
|
+
if (!terms.length)
|
|
275
|
+
return true;
|
|
276
|
+
const haystack = normalizeText([
|
|
277
|
+
record.location,
|
|
278
|
+
record.source_name,
|
|
279
|
+
record.latitude,
|
|
280
|
+
record.longitude
|
|
281
|
+
].filter((item) => item !== undefined).join(' '));
|
|
282
|
+
return terms.some((term) => haystack.includes(term));
|
|
283
|
+
}
|
|
284
|
+
function buildSearchTerms(location) {
|
|
285
|
+
if (!location)
|
|
286
|
+
return [];
|
|
287
|
+
const base = normalizeText(location);
|
|
288
|
+
const aliases = {
|
|
289
|
+
'智利': ['chile'],
|
|
290
|
+
'印尼': ['indonesia'],
|
|
291
|
+
'印度尼西亚': ['indonesia'],
|
|
292
|
+
'菲律宾': ['philippines'],
|
|
293
|
+
'秘鲁': ['peru'],
|
|
294
|
+
'墨西哥': ['mexico'],
|
|
295
|
+
'阿根廷': ['argentina'],
|
|
296
|
+
'土耳其': ['turkey'],
|
|
297
|
+
'希腊': ['greece'],
|
|
298
|
+
'俄罗斯': ['russia'],
|
|
299
|
+
'美国': ['unitedstates', 'usa'],
|
|
300
|
+
'阿拉斯加': ['alaska'],
|
|
301
|
+
'加州': ['california'],
|
|
302
|
+
'新西兰': ['newzealand'],
|
|
303
|
+
'巴布亚': ['papua'],
|
|
304
|
+
'汤加': ['tonga'],
|
|
305
|
+
'斐济': ['fiji'],
|
|
306
|
+
'瓦努阿图': ['vanuatu'],
|
|
307
|
+
'日本': ['japan'],
|
|
308
|
+
'台湾': ['taiwan', '臺灣', '台灣'],
|
|
309
|
+
'中国': ['china'],
|
|
310
|
+
'四川': ['sichuan'],
|
|
311
|
+
'云南': ['yunnan'],
|
|
312
|
+
'西藏': ['tibet', 'xizang'],
|
|
313
|
+
'新疆': ['xinjiang'],
|
|
314
|
+
'青海': ['qinghai'],
|
|
315
|
+
'甘肃': ['gansu']
|
|
316
|
+
};
|
|
317
|
+
const terms = new Set([base]);
|
|
318
|
+
for (const [key, values] of Object.entries(aliases)) {
|
|
319
|
+
if (base.includes(normalizeText(key))) {
|
|
320
|
+
values.forEach((value) => terms.add(normalizeText(value)));
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
return Array.from(terms).filter(Boolean);
|
|
324
|
+
}
|
|
325
|
+
function normalizeText(value) {
|
|
326
|
+
return String(value ?? '').toLowerCase().replace(/[\s,,、()()-]/g, '');
|
|
327
|
+
}
|
|
328
|
+
function clamp(value, min, max) {
|
|
329
|
+
return Math.min(Math.max(value, min), max);
|
|
330
|
+
}
|
|
331
|
+
function compact(value) {
|
|
332
|
+
if (Array.isArray(value))
|
|
333
|
+
return value.map((item) => compact(item));
|
|
334
|
+
if (!value || typeof value !== 'object')
|
|
335
|
+
return value;
|
|
336
|
+
const out = {};
|
|
337
|
+
for (const [key, item] of Object.entries(value)) {
|
|
338
|
+
if (item === undefined || item === null || item === '')
|
|
339
|
+
continue;
|
|
340
|
+
const next = compact(item);
|
|
341
|
+
if (Array.isArray(next) && next.length === 0)
|
|
342
|
+
continue;
|
|
343
|
+
out[key] = next;
|
|
344
|
+
}
|
|
345
|
+
return out;
|
|
346
|
+
}
|
package/lib/index.d.ts
CHANGED
|
@@ -24,6 +24,16 @@ export interface Config {
|
|
|
24
24
|
min_intensity_for_push: number;
|
|
25
25
|
min_scale_for_push: number;
|
|
26
26
|
};
|
|
27
|
+
chatluna: {
|
|
28
|
+
enabled: boolean;
|
|
29
|
+
name: string;
|
|
30
|
+
description: string;
|
|
31
|
+
default_source: 'all' | 'cenc' | 'jma' | 'usgs';
|
|
32
|
+
default_limit: number;
|
|
33
|
+
default_days: number;
|
|
34
|
+
min_magnitude: number;
|
|
35
|
+
include_usgs_when_all: boolean;
|
|
36
|
+
};
|
|
27
37
|
}
|
|
28
38
|
export declare const Config: Schema<Config>;
|
|
29
39
|
export declare function apply(ctx: Context, config: Config): void;
|
package/lib/index.js
CHANGED
|
@@ -5,10 +5,11 @@ exports.apply = apply;
|
|
|
5
5
|
const koishi_1 = require("koishi");
|
|
6
6
|
const service_1 = require("./service");
|
|
7
7
|
const commands_1 = require("./commands");
|
|
8
|
+
const chatluna_1 = require("./chatluna");
|
|
8
9
|
exports.name = 'disaster-warning';
|
|
9
10
|
exports.inject = {
|
|
10
11
|
required: ['http'],
|
|
11
|
-
optional: ['database']
|
|
12
|
+
optional: ['database', 'chatluna']
|
|
12
13
|
};
|
|
13
14
|
exports.Config = koishi_1.Schema.object({
|
|
14
15
|
target_groups: koishi_1.Schema.array(koishi_1.Schema.string())
|
|
@@ -32,10 +33,26 @@ exports.Config = koishi_1.Schema.object({
|
|
|
32
33
|
min_intensity_for_push: koishi_1.Schema.number().default(4.0).description('推送烈度门槛(中国):最大烈度达到此值则推送'),
|
|
33
34
|
min_scale_for_push: koishi_1.Schema.number().default(4.0).description('推送震度门槛(日本):最大震度达到此值则推送(4 = 震度4)'),
|
|
34
35
|
}).description('过滤阈值(地震类事件,海啸/气象不受此限制)'),
|
|
36
|
+
chatluna: koishi_1.Schema.object({
|
|
37
|
+
enabled: koishi_1.Schema.boolean().default(false).description('开启后注册 ChatLuna 工具,供模型主动查询近期地震与数据源状态'),
|
|
38
|
+
name: koishi_1.Schema.string().default('disaster_warning').description('ChatLuna 工具名称'),
|
|
39
|
+
description: koishi_1.Schema.string().default('查询近期地震和灾害预警数据源状态,可按地点、震级、时间范围过滤。适合回答“哪里地震了”“某地最近有没有地震”“关心的人所在地区是否有地震”等问题。').description('ChatLuna 工具描述'),
|
|
40
|
+
default_source: koishi_1.Schema.union([
|
|
41
|
+
koishi_1.Schema.const('all').description('综合 CENC / JMA / USGS'),
|
|
42
|
+
koishi_1.Schema.const('cenc').description('中国地震台网 / Wolfx CENC'),
|
|
43
|
+
koishi_1.Schema.const('jma').description('日本气象厅 / Wolfx JMA'),
|
|
44
|
+
koishi_1.Schema.const('usgs').description('USGS 全球地震')
|
|
45
|
+
]).default('all').description('模型未指定数据源时的默认查询源'),
|
|
46
|
+
default_limit: koishi_1.Schema.number().default(8).min(1).max(50).description('模型未指定 limit 时最多返回多少条'),
|
|
47
|
+
default_days: koishi_1.Schema.number().default(7).min(1).max(30).description('模型未指定 days 时查询最近多少天'),
|
|
48
|
+
min_magnitude: koishi_1.Schema.number().default(4.0).min(0).max(10).description('模型未指定 min_magnitude 时的最低震级'),
|
|
49
|
+
include_usgs_when_all: koishi_1.Schema.boolean().default(true).description('default_source/all 查询时是否包含 USGS 全球地震')
|
|
50
|
+
}).description('ChatLuna 工具调用'),
|
|
35
51
|
});
|
|
36
52
|
function apply(ctx, config) {
|
|
37
53
|
const service = new service_1.DisasterWarningService(ctx, config);
|
|
38
54
|
ctx.on('ready', () => service.start());
|
|
39
55
|
ctx.on('dispose', () => service.stop());
|
|
40
56
|
(0, commands_1.applyCommands)(ctx, config, service);
|
|
57
|
+
(0, chatluna_1.applyChatLunaTools)(ctx, config, service);
|
|
41
58
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "koishi-plugin-disaster-warning",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "Koishi 灾害预警插件,支持多数据源(地震、海啸、气象预警)",
|
|
5
5
|
"contributors": [
|
|
6
6
|
"lumia.wang <fenglian19980510@gmail.com>"
|
|
@@ -27,12 +27,14 @@
|
|
|
27
27
|
"chatbot",
|
|
28
28
|
"koishi",
|
|
29
29
|
"plugin",
|
|
30
|
+
"chatluna",
|
|
30
31
|
"disaster",
|
|
31
32
|
"earthquake",
|
|
32
33
|
"warning"
|
|
33
34
|
],
|
|
34
35
|
"peerDependencies": {
|
|
35
|
-
"koishi": "^4.18.0"
|
|
36
|
+
"koishi": "^4.18.0",
|
|
37
|
+
"koishi-plugin-chatluna": "^1.3.0 || ^1.4.0-alpha.0"
|
|
36
38
|
},
|
|
37
39
|
"devDependencies": {
|
|
38
40
|
"@types/node": "^20.0.0",
|
|
@@ -50,11 +52,19 @@
|
|
|
50
52
|
"http"
|
|
51
53
|
],
|
|
52
54
|
"optional": [
|
|
53
|
-
"database"
|
|
55
|
+
"database",
|
|
56
|
+
"chatluna"
|
|
54
57
|
]
|
|
55
58
|
}
|
|
56
59
|
},
|
|
57
60
|
"dependencies": {
|
|
58
|
-
"
|
|
61
|
+
"@langchain/core": "^0.3.80",
|
|
62
|
+
"ws": "^8.18.0",
|
|
63
|
+
"zod": "^3.25.76"
|
|
64
|
+
},
|
|
65
|
+
"peerDependenciesMeta": {
|
|
66
|
+
"koishi-plugin-chatluna": {
|
|
67
|
+
"optional": true
|
|
68
|
+
}
|
|
59
69
|
}
|
|
60
70
|
}
|