koishi-plugin-disaster-warning 0.1.0 → 0.1.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/handlers/fanstudio.d.ts +3 -1
- package/lib/handlers/fanstudio.js +91 -69
- package/lib/handlers/global_quake.d.ts +8 -0
- package/lib/handlers/global_quake.js +27 -18
- package/lib/handlers/p2p.js +2 -1
- package/lib/handlers/wolfx.js +2 -1
- package/lib/index.d.ts +0 -7
- package/lib/index.js +5 -12
- package/lib/models.js +1 -1
- package/lib/service.js +31 -39
- package/package.json +2 -2
|
@@ -3,7 +3,9 @@ import { DisasterEvent } from '../models';
|
|
|
3
3
|
export declare class FanStudioHandler extends BaseDataHandler {
|
|
4
4
|
constructor();
|
|
5
5
|
parseMessage(data: any): DisasterEvent | null;
|
|
6
|
-
private
|
|
6
|
+
private parseUpdate;
|
|
7
|
+
private parseInitialAll;
|
|
8
|
+
private dispatchBySource;
|
|
7
9
|
private parseEarthquakeWarning;
|
|
8
10
|
private parseEarthquakeInfo;
|
|
9
11
|
private parseWeather;
|
|
@@ -3,33 +3,37 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.FanStudioHandler = void 0;
|
|
4
4
|
const base_1 = require("./base");
|
|
5
5
|
const models_1 = require("../models");
|
|
6
|
+
/**
|
|
7
|
+
* FanStudio WebSocket /all 端点消息解析器
|
|
8
|
+
*
|
|
9
|
+
* 协议格式:
|
|
10
|
+
* { type: "update", source: "<src>", Data: { ... } }
|
|
11
|
+
* { type: "initial_all", "<src>": { ... }, ... }
|
|
12
|
+
*
|
|
13
|
+
* source 取值:cea | cenc | jma | cwa | cwa-eew | usgs | weatheralarm | tsunami | cea-pr
|
|
14
|
+
*/
|
|
15
|
+
const FS_SOURCE_MAP = {
|
|
16
|
+
'cea': models_1.DataSource.FAN_STUDIO_CEA,
|
|
17
|
+
'cea-pr': models_1.DataSource.FAN_STUDIO_CEA,
|
|
18
|
+
'cenc': models_1.DataSource.FAN_STUDIO_CENC,
|
|
19
|
+
'jma': models_1.DataSource.FAN_STUDIO_JMA,
|
|
20
|
+
'cwa': models_1.DataSource.FAN_STUDIO_CWA,
|
|
21
|
+
'cwa-eew': models_1.DataSource.FAN_STUDIO_CWA,
|
|
22
|
+
'usgs': models_1.DataSource.FAN_STUDIO_USGS,
|
|
23
|
+
'weatheralarm': models_1.DataSource.FAN_STUDIO_WEATHER,
|
|
24
|
+
'tsunami': models_1.DataSource.FAN_STUDIO_TSUNAMI,
|
|
25
|
+
};
|
|
6
26
|
class FanStudioHandler extends base_1.BaseDataHandler {
|
|
7
27
|
constructor() {
|
|
8
28
|
super('fan_studio');
|
|
9
29
|
}
|
|
10
30
|
parseMessage(data) {
|
|
11
31
|
try {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
if (!msgData)
|
|
15
|
-
return null;
|
|
16
|
-
// Detect data source based on message content
|
|
17
|
-
const source = this.detectSource(msgData);
|
|
18
|
-
// Earthquake Warning (CEA, CWA, JMA EEW)
|
|
19
|
-
if (msgData.epiIntensity !== undefined || (msgData.magnitude !== undefined && msgData.isFinal !== undefined)) {
|
|
20
|
-
return this.parseEarthquakeWarning(msgData, source);
|
|
21
|
-
}
|
|
22
|
-
// Earthquake Info (CENC, USGS, JMA Info)
|
|
23
|
-
if (msgData.eventId && msgData.magnitude !== undefined && msgData.epiIntensity === undefined) {
|
|
24
|
-
return this.parseEarthquakeInfo(msgData, source);
|
|
25
|
-
}
|
|
26
|
-
// Weather
|
|
27
|
-
if (msgData.headline && msgData.description) {
|
|
28
|
-
return this.parseWeather(msgData);
|
|
32
|
+
if (data.type === 'update') {
|
|
33
|
+
return this.parseUpdate(data);
|
|
29
34
|
}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
return this.parseTsunami(msgData);
|
|
35
|
+
else if (data.type === 'initial_all') {
|
|
36
|
+
return this.parseInitialAll(data);
|
|
33
37
|
}
|
|
34
38
|
return null;
|
|
35
39
|
}
|
|
@@ -38,51 +42,63 @@ class FanStudioHandler extends base_1.BaseDataHandler {
|
|
|
38
42
|
return null;
|
|
39
43
|
}
|
|
40
44
|
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
if (
|
|
62
|
-
return
|
|
63
|
-
if (sourceStr.includes('jma') || sourceStr.includes('japan'))
|
|
64
|
-
return models_1.DataSource.FAN_STUDIO_JMA;
|
|
65
|
-
if (sourceStr.includes('usgs'))
|
|
66
|
-
return models_1.DataSource.FAN_STUDIO_USGS;
|
|
67
|
-
}
|
|
68
|
-
// Check province for Taiwan
|
|
69
|
-
if (data.province && String(data.province).includes('台湾')) {
|
|
70
|
-
return models_1.DataSource.FAN_STUDIO_CWA;
|
|
71
|
-
}
|
|
72
|
-
// Check for Japan-specific fields (scale instead of intensity)
|
|
73
|
-
if (data.scale !== undefined && data.epiIntensity === undefined) {
|
|
74
|
-
return models_1.DataSource.FAN_STUDIO_JMA;
|
|
45
|
+
parseUpdate(msg) {
|
|
46
|
+
const src = String(msg.source || '').toLowerCase();
|
|
47
|
+
const source = FS_SOURCE_MAP[src];
|
|
48
|
+
if (!source)
|
|
49
|
+
return null;
|
|
50
|
+
const payload = msg.Data || msg.data;
|
|
51
|
+
if (!payload)
|
|
52
|
+
return null;
|
|
53
|
+
return this.dispatchBySource(source, src, payload);
|
|
54
|
+
}
|
|
55
|
+
parseInitialAll(msg) {
|
|
56
|
+
// initial_all 包含多个数据源快照,取第一个有效的推送
|
|
57
|
+
for (const [key, payload] of Object.entries(msg)) {
|
|
58
|
+
if (key === 'type')
|
|
59
|
+
continue;
|
|
60
|
+
const src = key.toLowerCase();
|
|
61
|
+
const source = FS_SOURCE_MAP[src];
|
|
62
|
+
if (!source || !payload)
|
|
63
|
+
continue;
|
|
64
|
+
const event = this.dispatchBySource(source, src, payload);
|
|
65
|
+
if (event)
|
|
66
|
+
return event;
|
|
75
67
|
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
dispatchBySource(source, src, data) {
|
|
71
|
+
switch (source) {
|
|
72
|
+
case models_1.DataSource.FAN_STUDIO_CEA:
|
|
73
|
+
return this.parseEarthquakeWarning(data, source);
|
|
74
|
+
case models_1.DataSource.FAN_STUDIO_CWA:
|
|
75
|
+
// cwa-eew 是预警,cwa 是地震报告
|
|
76
|
+
if (src === 'cwa-eew')
|
|
77
|
+
return this.parseEarthquakeWarning(data, source);
|
|
78
|
+
return this.parseEarthquakeInfo(data, source);
|
|
79
|
+
case models_1.DataSource.FAN_STUDIO_JMA:
|
|
80
|
+
// jma 消息既有 EEW 也有地震信息,通过 isFinal/epiIntensity 判断
|
|
81
|
+
if (data.epiIntensity !== undefined || data.isFinal !== undefined) {
|
|
82
|
+
return this.parseEarthquakeWarning(data, source);
|
|
83
|
+
}
|
|
84
|
+
return this.parseEarthquakeInfo(data, source);
|
|
85
|
+
case models_1.DataSource.FAN_STUDIO_CENC:
|
|
86
|
+
return this.parseEarthquakeInfo(data, source);
|
|
87
|
+
case models_1.DataSource.FAN_STUDIO_USGS:
|
|
88
|
+
return this.parseEarthquakeInfo(data, source);
|
|
89
|
+
case models_1.DataSource.FAN_STUDIO_WEATHER:
|
|
90
|
+
return this.parseWeather(data);
|
|
91
|
+
case models_1.DataSource.FAN_STUDIO_TSUNAMI:
|
|
92
|
+
return this.parseTsunami(data);
|
|
93
|
+
default:
|
|
94
|
+
return null;
|
|
79
95
|
}
|
|
80
|
-
// Default to CEA for Chinese earthquake warnings
|
|
81
|
-
return models_1.DataSource.FAN_STUDIO_CEA;
|
|
82
96
|
}
|
|
83
97
|
parseEarthquakeWarning(data, source) {
|
|
98
|
+
if (!data.id && !data.eventId)
|
|
99
|
+
return null;
|
|
84
100
|
const earthquake = {
|
|
85
|
-
id: data.id || '',
|
|
101
|
+
id: data.id || data.eventId || '',
|
|
86
102
|
event_id: data.eventId || data.id || '',
|
|
87
103
|
source: source,
|
|
88
104
|
disaster_type: models_1.DisasterType.EARTHQUAKE_WARNING,
|
|
@@ -91,7 +107,7 @@ class FanStudioHandler extends base_1.BaseDataHandler {
|
|
|
91
107
|
longitude: Number(data.longitude) || 0,
|
|
92
108
|
depth: Number(data.depth),
|
|
93
109
|
magnitude: Number(data.magnitude),
|
|
94
|
-
intensity: Number(data.epiIntensity),
|
|
110
|
+
intensity: data.epiIntensity !== undefined ? Number(data.epiIntensity) : undefined,
|
|
95
111
|
scale: data.scale !== undefined ? Number(data.scale) : undefined,
|
|
96
112
|
place_name: data.placeName || '',
|
|
97
113
|
province: data.province,
|
|
@@ -111,18 +127,20 @@ class FanStudioHandler extends base_1.BaseDataHandler {
|
|
|
111
127
|
};
|
|
112
128
|
}
|
|
113
129
|
parseEarthquakeInfo(data, source) {
|
|
114
|
-
|
|
115
|
-
|
|
130
|
+
if (!data.id && !data.eventId)
|
|
131
|
+
return null;
|
|
116
132
|
const earthquake = {
|
|
117
|
-
id: data.id || '',
|
|
133
|
+
id: data.id || data.eventId || '',
|
|
118
134
|
event_id: data.eventId || data.id || '',
|
|
119
|
-
source:
|
|
135
|
+
source: source,
|
|
120
136
|
disaster_type: models_1.DisasterType.EARTHQUAKE,
|
|
121
137
|
shock_time: this.parseDateTime(data.shockTime) || new Date().toISOString(),
|
|
122
138
|
latitude: Number(data.latitude) || 0,
|
|
123
139
|
longitude: Number(data.longitude) || 0,
|
|
124
140
|
depth: Number(data.depth),
|
|
125
141
|
magnitude: Number(data.magnitude),
|
|
142
|
+
intensity: data.epiIntensity !== undefined ? Number(data.epiIntensity) : undefined,
|
|
143
|
+
scale: data.scale !== undefined ? Number(data.scale) : undefined,
|
|
126
144
|
place_name: data.placeName || '',
|
|
127
145
|
updates: 1,
|
|
128
146
|
is_final: true,
|
|
@@ -132,7 +150,7 @@ class FanStudioHandler extends base_1.BaseDataHandler {
|
|
|
132
150
|
return {
|
|
133
151
|
id: earthquake.id,
|
|
134
152
|
data: earthquake,
|
|
135
|
-
source:
|
|
153
|
+
source: source,
|
|
136
154
|
disaster_type: models_1.DisasterType.EARTHQUAKE,
|
|
137
155
|
receive_time: new Date().toISOString(),
|
|
138
156
|
push_count: 0,
|
|
@@ -140,12 +158,14 @@ class FanStudioHandler extends base_1.BaseDataHandler {
|
|
|
140
158
|
};
|
|
141
159
|
}
|
|
142
160
|
parseWeather(data) {
|
|
161
|
+
if (!data.headline && !data.title)
|
|
162
|
+
return null;
|
|
143
163
|
const weather = {
|
|
144
164
|
id: data.id || `weather_${Date.now()}`,
|
|
145
165
|
source: models_1.DataSource.FAN_STUDIO_WEATHER,
|
|
146
|
-
headline: data.headline,
|
|
147
|
-
title: data.title || data.headline,
|
|
148
|
-
description: data.description,
|
|
166
|
+
headline: data.headline || data.title || '',
|
|
167
|
+
title: data.title || data.headline || '',
|
|
168
|
+
description: data.description || '',
|
|
149
169
|
type: data.type || 'unknown',
|
|
150
170
|
effective_time: this.parseDateTime(data.effectiveTime) || new Date().toISOString(),
|
|
151
171
|
disaster_type: models_1.DisasterType.WEATHER_ALARM,
|
|
@@ -164,6 +184,8 @@ class FanStudioHandler extends base_1.BaseDataHandler {
|
|
|
164
184
|
};
|
|
165
185
|
}
|
|
166
186
|
parseTsunami(data) {
|
|
187
|
+
if (!data.title && !data.warningInfo)
|
|
188
|
+
return null;
|
|
167
189
|
const tsunami = {
|
|
168
190
|
id: data.id || `tsunami_${Date.now()}`,
|
|
169
191
|
code: data.code || '',
|
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
import { BaseDataHandler } from './base';
|
|
2
2
|
import { DisasterEvent } from '../models';
|
|
3
|
+
/**
|
|
4
|
+
* GlobalQuake WebSocket 消息解析器
|
|
5
|
+
*
|
|
6
|
+
* JSON 格式字段(来自 astrbot 参考实现):
|
|
7
|
+
* id, latitude, longitude, depth, magnitude, region,
|
|
8
|
+
* originTimeIso / origin_time_iso / origin_time_ms,
|
|
9
|
+
* revisionId / revision_id
|
|
10
|
+
*/
|
|
3
11
|
export declare class GlobalQuakeHandler extends BaseDataHandler {
|
|
4
12
|
constructor();
|
|
5
13
|
parseMessage(data: any): DisasterEvent | null;
|
|
@@ -3,35 +3,44 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.GlobalQuakeHandler = void 0;
|
|
4
4
|
const base_1 = require("./base");
|
|
5
5
|
const models_1 = require("../models");
|
|
6
|
+
/**
|
|
7
|
+
* GlobalQuake WebSocket 消息解析器
|
|
8
|
+
*
|
|
9
|
+
* JSON 格式字段(来自 astrbot 参考实现):
|
|
10
|
+
* id, latitude, longitude, depth, magnitude, region,
|
|
11
|
+
* originTimeIso / origin_time_iso / origin_time_ms,
|
|
12
|
+
* revisionId / revision_id
|
|
13
|
+
*/
|
|
6
14
|
class GlobalQuakeHandler extends base_1.BaseDataHandler {
|
|
7
15
|
constructor() {
|
|
8
16
|
super('global_quake');
|
|
9
17
|
}
|
|
10
18
|
parseMessage(data) {
|
|
11
19
|
try {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
// I'll assume a generic structure or try to find it.
|
|
15
|
-
// Wait, `global_sources.py` might contain it.
|
|
16
|
-
// For now, let's implement a placeholder or basic structure.
|
|
17
|
-
// If data has 'magnitude' and 'latitude', it's likely an earthquake.
|
|
18
|
-
if (!data.uuid || !data.magnitude)
|
|
20
|
+
const id = data.id;
|
|
21
|
+
if (!id || data.magnitude == null)
|
|
19
22
|
return null;
|
|
23
|
+
const lat = Number(data.latitude);
|
|
24
|
+
const lon = Number(data.longitude);
|
|
25
|
+
if (isNaN(lat) || isNaN(lon))
|
|
26
|
+
return null;
|
|
27
|
+
const originTime = data.originTimeIso ||
|
|
28
|
+
data.origin_time_iso ||
|
|
29
|
+
(data.origin_time_ms ? new Date(data.origin_time_ms).toISOString() : null);
|
|
20
30
|
const earthquake = {
|
|
21
|
-
id:
|
|
22
|
-
event_id:
|
|
31
|
+
id: String(id),
|
|
32
|
+
event_id: String(id),
|
|
23
33
|
source: models_1.DataSource.GLOBAL_QUAKE,
|
|
24
|
-
disaster_type: models_1.DisasterType.EARTHQUAKE_WARNING,
|
|
25
|
-
shock_time: this.parseDateTime(
|
|
26
|
-
latitude:
|
|
27
|
-
longitude:
|
|
28
|
-
depth: Number(data.depth),
|
|
34
|
+
disaster_type: models_1.DisasterType.EARTHQUAKE_WARNING,
|
|
35
|
+
shock_time: (originTime && this.parseDateTime(originTime)) || new Date().toISOString(),
|
|
36
|
+
latitude: lat,
|
|
37
|
+
longitude: lon,
|
|
38
|
+
depth: data.depth != null ? Number(data.depth) : undefined,
|
|
29
39
|
magnitude: Number(data.magnitude),
|
|
30
|
-
place_name: data.region || '
|
|
31
|
-
updates: data.
|
|
32
|
-
is_final: false,
|
|
40
|
+
place_name: data.region || 'Global',
|
|
41
|
+
updates: data.revisionId ?? data.revision_id ?? 1,
|
|
42
|
+
is_final: false,
|
|
33
43
|
is_cancel: false,
|
|
34
|
-
max_pga: data.maxPGA,
|
|
35
44
|
raw_data: data
|
|
36
45
|
};
|
|
37
46
|
return {
|
package/lib/handlers/p2p.js
CHANGED
|
@@ -196,9 +196,10 @@ class P2PHandler extends base_1.BaseDataHandler {
|
|
|
196
196
|
};
|
|
197
197
|
}
|
|
198
198
|
convertP2PScale(scale) {
|
|
199
|
+
// 46 = "5弱以上(暫定)",显示同 5弱(4.5)
|
|
199
200
|
const mapping = {
|
|
200
201
|
10: 1.0, 20: 2.0, 30: 3.0, 40: 4.0,
|
|
201
|
-
45: 4.5, 46: 4.
|
|
202
|
+
45: 4.5, 46: 4.5, 50: 5.0, 55: 5.5,
|
|
202
203
|
60: 6.0, 70: 7.0
|
|
203
204
|
};
|
|
204
205
|
return mapping[scale];
|
package/lib/handlers/wolfx.js
CHANGED
|
@@ -251,6 +251,7 @@ class WolfxHandler extends base_1.BaseDataHandler {
|
|
|
251
251
|
parseJMAScale(scaleStr) {
|
|
252
252
|
if (!scaleStr)
|
|
253
253
|
return undefined;
|
|
254
|
+
// JMA 震度映射:5弱→4.5, 5強→5.0, 6弱→5.5, 6強→6.0
|
|
254
255
|
const match = scaleStr.match(/(\d+)(弱|強)?/);
|
|
255
256
|
if (match) {
|
|
256
257
|
const base = parseInt(match[1]);
|
|
@@ -258,7 +259,7 @@ class WolfxHandler extends base_1.BaseDataHandler {
|
|
|
258
259
|
if (suffix === '弱')
|
|
259
260
|
return base - 0.5;
|
|
260
261
|
if (suffix === '強')
|
|
261
|
-
return base
|
|
262
|
+
return base; // 5強=5.0, 6強=6.0
|
|
262
263
|
return base;
|
|
263
264
|
}
|
|
264
265
|
return undefined;
|
package/lib/index.d.ts
CHANGED
|
@@ -5,7 +5,6 @@ export declare const inject: {
|
|
|
5
5
|
optional: string[];
|
|
6
6
|
};
|
|
7
7
|
export interface Config {
|
|
8
|
-
enabled: boolean;
|
|
9
8
|
target_groups: string[];
|
|
10
9
|
data_types: {
|
|
11
10
|
earthquake_warning: boolean;
|
|
@@ -19,12 +18,6 @@ export interface Config {
|
|
|
19
18
|
japan: boolean;
|
|
20
19
|
global: boolean;
|
|
21
20
|
};
|
|
22
|
-
data_sources: {
|
|
23
|
-
fan_studio: boolean;
|
|
24
|
-
wolfx: boolean;
|
|
25
|
-
p2p: boolean;
|
|
26
|
-
global_quake: boolean;
|
|
27
|
-
};
|
|
28
21
|
filter: {
|
|
29
22
|
min_magnitude_absolute: number;
|
|
30
23
|
min_magnitude_for_push: number;
|
package/lib/index.js
CHANGED
|
@@ -11,7 +11,6 @@ exports.inject = {
|
|
|
11
11
|
optional: ['database']
|
|
12
12
|
};
|
|
13
13
|
exports.Config = koishi_1.Schema.object({
|
|
14
|
-
enabled: koishi_1.Schema.boolean().default(true).description('启用灾害预警插件'),
|
|
15
14
|
target_groups: koishi_1.Schema.array(koishi_1.Schema.string())
|
|
16
15
|
.default([])
|
|
17
16
|
.description('推送目标群号列表,直接填写群号即可,例如 123456789'),
|
|
@@ -22,17 +21,11 @@ exports.Config = koishi_1.Schema.object({
|
|
|
22
21
|
weather_alarm: koishi_1.Schema.boolean().default(false).description('气象预警(中国)'),
|
|
23
22
|
}).description('接收的灾害类型'),
|
|
24
23
|
regions: koishi_1.Schema.object({
|
|
25
|
-
china: koishi_1.Schema.boolean().default(true).description('
|
|
26
|
-
taiwan: koishi_1.Schema.boolean().default(true).description('
|
|
27
|
-
japan: koishi_1.Schema.boolean().default(true).description('
|
|
28
|
-
global: koishi_1.Schema.boolean().default(false).description('全球(USGS / GlobalQuake
|
|
29
|
-
}).description('
|
|
30
|
-
data_sources: koishi_1.Schema.object({
|
|
31
|
-
fan_studio: koishi_1.Schema.boolean().default(true).description('FAN Studio(中国预警/台湾/USGS/日本/气象/海啸)'),
|
|
32
|
-
wolfx: koishi_1.Schema.boolean().default(true).description('Wolfx(中国/台湾/日本 EEW,以及中国/日本地震列表)'),
|
|
33
|
-
p2p: koishi_1.Schema.boolean().default(true).description('P2P地震情報(日本 EEW / 地震情报 / 海啸)'),
|
|
34
|
-
global_quake: koishi_1.Schema.boolean().default(false).description('GlobalQuake(全球实时地震,流量较大)'),
|
|
35
|
-
}).description('数据源开关(可单独禁用某个来源)'),
|
|
24
|
+
china: koishi_1.Schema.boolean().default(true).description('中国大陆(CEA预警 / CENC地震台网 / 气象 / 海啸)'),
|
|
25
|
+
taiwan: koishi_1.Schema.boolean().default(true).description('台湾(CWA预警与地震报告)'),
|
|
26
|
+
japan: koishi_1.Schema.boolean().default(true).description('日本(JMA EEW / P2P地震情报 / 海啸)'),
|
|
27
|
+
global: koishi_1.Schema.boolean().default(false).description('全球(USGS 地震信息 / GlobalQuake 实时预警)'),
|
|
28
|
+
}).description('接收的地区(数据源连接将依据此项自动开启)'),
|
|
36
29
|
filter: koishi_1.Schema.object({
|
|
37
30
|
min_magnitude_absolute: koishi_1.Schema.number().default(3.0).description('绝对过滤震级:低于此震级直接丢弃(不推送)'),
|
|
38
31
|
min_magnitude_for_push: koishi_1.Schema.number().default(4.0).description('推送震级门槛:震级达到此值则推送'),
|
package/lib/models.js
CHANGED
|
@@ -58,7 +58,7 @@ function getDataSourceFromId(id) {
|
|
|
58
58
|
* 用 place+magnitude+分钟桶 作为指纹,窗口期内同一事件只推一次
|
|
59
59
|
*/
|
|
60
60
|
class EventDeduplicator {
|
|
61
|
-
constructor(windowMs =
|
|
61
|
+
constructor(windowMs = 8 * 60 * 1000) {
|
|
62
62
|
// fingerprint -> first-seen timestamp (ms)
|
|
63
63
|
this.seen = new Map();
|
|
64
64
|
this.windowMs = windowMs;
|
package/lib/service.js
CHANGED
|
@@ -7,12 +7,12 @@ const models_1 = require("./models");
|
|
|
7
7
|
const handlers_1 = require("./handlers");
|
|
8
8
|
const pusher_1 = require("./pusher");
|
|
9
9
|
const logger = new koishi_1.Logger('disaster-warning');
|
|
10
|
-
// Wolfx HTTP
|
|
10
|
+
// Wolfx HTTP 列表轮询间隔(5 分钟)
|
|
11
11
|
const WOLFX_HTTP_INTERVAL_MS = 5 * 60 * 1000;
|
|
12
12
|
// FanStudio 备用服务器
|
|
13
13
|
const FAN_STUDIO_PRIMARY = 'wss://ws.fanstudio.tech/all';
|
|
14
14
|
const FAN_STUDIO_BACKUP = 'wss://ws.fanstudio.hk/all';
|
|
15
|
-
// WebSocket
|
|
15
|
+
// WebSocket 重连延迟,超过阈值次数后切换到备用服务器
|
|
16
16
|
const RECONNECT_DELAY_MS = 10000;
|
|
17
17
|
const FALLBACK_RETRY_THRESHOLD = 5;
|
|
18
18
|
class DisasterWarningService {
|
|
@@ -32,8 +32,6 @@ class DisasterWarningService {
|
|
|
32
32
|
this.wolfxHandler = new handlers_1.WolfxHandler('wolfx_all');
|
|
33
33
|
}
|
|
34
34
|
async start() {
|
|
35
|
-
if (!this.config.enabled)
|
|
36
|
-
return;
|
|
37
35
|
this.stopped = false;
|
|
38
36
|
logger.info('Disaster Warning Service starting...');
|
|
39
37
|
this.connectAll();
|
|
@@ -60,37 +58,33 @@ class DisasterWarningService {
|
|
|
60
58
|
}
|
|
61
59
|
// ---- 连接调度 --------------------------------------------------------
|
|
62
60
|
connectAll() {
|
|
63
|
-
const { regions, data_types
|
|
64
|
-
//
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
this.
|
|
72
|
-
|
|
73
|
-
});
|
|
74
|
-
}
|
|
61
|
+
const { regions, data_types } = this.config;
|
|
62
|
+
// FanStudio /all:覆盖中国(CEA/CENC/气象/海啸)、台湾(CWA)、日本(JMA)、全球(USGS)
|
|
63
|
+
const needFanStudio = (regions.china && (data_types.earthquake_warning || data_types.earthquake_info || data_types.weather_alarm || data_types.tsunami_warning)) ||
|
|
64
|
+
(regions.taiwan && (data_types.earthquake_warning || data_types.earthquake_info)) ||
|
|
65
|
+
(regions.japan && (data_types.earthquake_warning || data_types.earthquake_info)) ||
|
|
66
|
+
(regions.global && data_types.earthquake_info);
|
|
67
|
+
if (needFanStudio) {
|
|
68
|
+
this.openConnection('fan_studio', FAN_STUDIO_PRIMARY, FAN_STUDIO_BACKUP, (data) => {
|
|
69
|
+
this.handleEvent(this.handlers.fanStudio.parseMessage(data));
|
|
70
|
+
});
|
|
75
71
|
}
|
|
76
|
-
// P2P
|
|
77
|
-
if (
|
|
72
|
+
// P2P:日本 EEW / 地震情报 / 海啸
|
|
73
|
+
if (regions.japan &&
|
|
78
74
|
(data_types.earthquake_warning || data_types.earthquake_info || data_types.tsunami_warning)) {
|
|
79
75
|
this.openConnection('p2p', 'wss://api.p2pquake.net/v2/ws', undefined, (data) => {
|
|
80
76
|
this.handleEvent(this.handlers.p2p.parseMessage(data));
|
|
81
77
|
});
|
|
82
78
|
}
|
|
83
|
-
// Wolfx
|
|
84
|
-
|
|
85
|
-
if (data_sources.wolfx &&
|
|
86
|
-
(data_types.earthquake_warning) &&
|
|
79
|
+
// Wolfx /all_eew:中国/台湾/日本 EEW(仅预警类型)
|
|
80
|
+
if (data_types.earthquake_warning &&
|
|
87
81
|
(regions.china || regions.taiwan || regions.japan)) {
|
|
88
82
|
this.openConnection('wolfx_eew', 'wss://ws-api.wolfx.jp/all_eew', undefined, (data) => {
|
|
89
83
|
this.handleEvent(this.wolfxHandler.parseMessage(data));
|
|
90
84
|
});
|
|
91
85
|
}
|
|
92
|
-
// GlobalQuake
|
|
93
|
-
if (
|
|
86
|
+
// GlobalQuake:全球实时预警
|
|
87
|
+
if (regions.global && data_types.earthquake_warning) {
|
|
94
88
|
this.openConnection('global_quake', 'wss://gqm.aloys233.top/ws', undefined, (data) => {
|
|
95
89
|
this.handleEvent(this.handlers.globalQuake.parseMessage(data));
|
|
96
90
|
});
|
|
@@ -145,20 +139,23 @@ class DisasterWarningService {
|
|
|
145
139
|
logger.error(`[${name}] Error:`, err);
|
|
146
140
|
});
|
|
147
141
|
}
|
|
148
|
-
// ---- Wolfx HTTP
|
|
142
|
+
// ---- Wolfx HTTP 轮询(地震列表)------------------------------------
|
|
149
143
|
startWolfxHttpPoller() {
|
|
150
|
-
|
|
144
|
+
const { regions, data_types } = this.config;
|
|
145
|
+
if (!data_types.earthquake_info)
|
|
146
|
+
return;
|
|
147
|
+
if (!regions.china && !regions.japan)
|
|
151
148
|
return;
|
|
152
149
|
const poll = async () => {
|
|
153
150
|
if (this.stopped)
|
|
154
151
|
return;
|
|
155
152
|
try {
|
|
156
|
-
if (
|
|
153
|
+
if (regions.china) {
|
|
157
154
|
const data = await this.ctx.http.get('https://api.wolfx.jp/cenc_eqlist.json');
|
|
158
155
|
if (data)
|
|
159
156
|
this.handleEvent(this.wolfxHandler.parseEqList(data, 'cenc'));
|
|
160
157
|
}
|
|
161
|
-
if (
|
|
158
|
+
if (regions.japan) {
|
|
162
159
|
const data = await this.ctx.http.get('https://api.wolfx.jp/jma_eqlist.json');
|
|
163
160
|
if (data)
|
|
164
161
|
this.handleEvent(this.wolfxHandler.parseEqList(data, 'jma'));
|
|
@@ -168,8 +165,8 @@ class DisasterWarningService {
|
|
|
168
165
|
logger.warn('[wolfx_http] Fetch failed:', e);
|
|
169
166
|
}
|
|
170
167
|
};
|
|
171
|
-
//
|
|
172
|
-
|
|
168
|
+
// 延迟首次拉取,避免重启时将旧地震当新事件推送
|
|
169
|
+
// 去重窗口(8分钟)> 轮询间隔(5分钟),不会漏报
|
|
173
170
|
this.wolfxHttpTimer = setInterval(poll, WOLFX_HTTP_INTERVAL_MS);
|
|
174
171
|
}
|
|
175
172
|
// ---- 事件处理 --------------------------------------------------------
|
|
@@ -199,14 +196,10 @@ class DisasterWarningService {
|
|
|
199
196
|
if (isWeather && !data_types.weather_alarm)
|
|
200
197
|
return false;
|
|
201
198
|
const src = event.source;
|
|
202
|
-
const japanSources = [
|
|
203
|
-
models_1.DataSource.
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
const chinaSources = [
|
|
207
|
-
models_1.DataSource.FAN_STUDIO_CEA, models_1.DataSource.FAN_STUDIO_CENC, models_1.DataSource.FAN_STUDIO_WEATHER,
|
|
208
|
-
models_1.DataSource.FAN_STUDIO_TSUNAMI, models_1.DataSource.WOLFX_CENC_EEW, models_1.DataSource.WOLFX_CENC_EQ
|
|
209
|
-
];
|
|
199
|
+
const japanSources = [models_1.DataSource.P2P_EEW, models_1.DataSource.P2P_EARTHQUAKE, models_1.DataSource.P2P_TSUNAMI,
|
|
200
|
+
models_1.DataSource.WOLFX_JMA_EEW, models_1.DataSource.WOLFX_JMA_EQ, models_1.DataSource.FAN_STUDIO_JMA];
|
|
201
|
+
const chinaSources = [models_1.DataSource.FAN_STUDIO_CEA, models_1.DataSource.FAN_STUDIO_CENC, models_1.DataSource.FAN_STUDIO_WEATHER,
|
|
202
|
+
models_1.DataSource.FAN_STUDIO_TSUNAMI, models_1.DataSource.WOLFX_CENC_EEW, models_1.DataSource.WOLFX_CENC_EQ];
|
|
210
203
|
const taiwanSources = [models_1.DataSource.FAN_STUDIO_CWA, models_1.DataSource.WOLFX_CWA_EEW];
|
|
211
204
|
const globalSources = [models_1.DataSource.FAN_STUDIO_USGS, models_1.DataSource.GLOBAL_QUAKE];
|
|
212
205
|
if (japanSources.includes(src) && !regions.japan)
|
|
@@ -229,7 +222,6 @@ class DisasterWarningService {
|
|
|
229
222
|
url: entry.url
|
|
230
223
|
};
|
|
231
224
|
}
|
|
232
|
-
// Wolfx HTTP poller 状态
|
|
233
225
|
result['wolfx_http_poller'] = {
|
|
234
226
|
connected: this.wolfxHttpTimer !== null,
|
|
235
227
|
retryCount: 0,
|
package/package.json
CHANGED