koishi-plugin-disaster-warning 0.0.6 → 0.0.8
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/commands.js +0 -30
- package/lib/handlers/fanstudio.d.ts +1 -0
- package/lib/handlers/fanstudio.js +57 -22
- package/lib/handlers/p2p.js +76 -7
- package/lib/handlers/wolfx.d.ts +2 -0
- package/lib/handlers/wolfx.js +98 -4
- package/lib/index.d.ts +11 -43
- package/lib/index.js +19 -46
- package/lib/pusher.d.ts +1 -0
- package/lib/pusher.js +78 -71
- package/lib/service.d.ts +3 -2
- package/lib/service.js +95 -68
- package/package.json +16 -7
package/lib/commands.js
CHANGED
|
@@ -10,36 +10,6 @@ function applyCommands(ctx, config, service) {
|
|
|
10
10
|
if (!session)
|
|
11
11
|
return;
|
|
12
12
|
await session.send('正在发送测试消息...');
|
|
13
|
-
const testEvent = {
|
|
14
|
-
id: 'test_event_' + Date.now(),
|
|
15
|
-
data: {
|
|
16
|
-
id: 'test_id',
|
|
17
|
-
event_id: 'test_event_id',
|
|
18
|
-
source: 'Test Source',
|
|
19
|
-
disaster_type: 'earthquake_warning',
|
|
20
|
-
shock_time: new Date().toISOString(),
|
|
21
|
-
latitude: 30.0,
|
|
22
|
-
longitude: 120.0,
|
|
23
|
-
depth: 10,
|
|
24
|
-
magnitude: 5.0,
|
|
25
|
-
intensity: 4.0,
|
|
26
|
-
place_name: '测试地点',
|
|
27
|
-
updates: 1,
|
|
28
|
-
is_final: false,
|
|
29
|
-
is_cancel: false,
|
|
30
|
-
raw_data: {}
|
|
31
|
-
},
|
|
32
|
-
source: 'test',
|
|
33
|
-
disaster_type: 'earthquake_warning',
|
|
34
|
-
receive_time: new Date().toISOString(),
|
|
35
|
-
push_count: 0,
|
|
36
|
-
raw_data: {}
|
|
37
|
-
};
|
|
38
|
-
// We can use the service's pusher to format and send,
|
|
39
|
-
// but pusher broadcasts to configured groups.
|
|
40
|
-
// Here we might want to send to the current session.
|
|
41
|
-
// Let's manually format it using a simplified version or expose pusher's formatter.
|
|
42
|
-
// For simplicity, let's just send a text message here.
|
|
43
13
|
const msg = [
|
|
44
14
|
'【测试消息】',
|
|
45
15
|
'这是一个测试用的地震预警消息。',
|
|
@@ -3,6 +3,7 @@ import { DisasterEvent } from '../models';
|
|
|
3
3
|
export declare class FanStudioHandler extends BaseDataHandler {
|
|
4
4
|
constructor();
|
|
5
5
|
parseMessage(data: any): DisasterEvent | null;
|
|
6
|
+
private detectSource;
|
|
6
7
|
private parseEarthquakeWarning;
|
|
7
8
|
private parseEarthquakeInfo;
|
|
8
9
|
private parseWeather;
|
|
@@ -13,18 +13,15 @@ class FanStudioHandler extends base_1.BaseDataHandler {
|
|
|
13
13
|
const msgData = data.Data || data.data || data;
|
|
14
14
|
if (!msgData)
|
|
15
15
|
return null;
|
|
16
|
-
//
|
|
16
|
+
// Detect data source based on message content
|
|
17
|
+
const source = this.detectSource(msgData);
|
|
17
18
|
// Earthquake Warning (CEA, CWA, JMA EEW)
|
|
18
|
-
if (msgData.epiIntensity !== undefined || msgData.magnitude !== undefined && msgData.isFinal !== undefined) {
|
|
19
|
-
return this.parseEarthquakeWarning(msgData);
|
|
19
|
+
if (msgData.epiIntensity !== undefined || (msgData.magnitude !== undefined && msgData.isFinal !== undefined)) {
|
|
20
|
+
return this.parseEarthquakeWarning(msgData, source);
|
|
20
21
|
}
|
|
21
|
-
// Earthquake Info (CENC, USGS, JMA Info)
|
|
22
|
-
// But FanStudio might normalize them.
|
|
23
|
-
// Let's look at CENC/USGS fields from previous analysis if possible, or infer.
|
|
24
|
-
// CENC usually has "cenc_earthquake" or similar if it was tagged, but here we just have raw data.
|
|
25
|
-
// If it has 'eventId' and 'magnitude' but no 'epiIntensity', it might be earthquake info.
|
|
22
|
+
// Earthquake Info (CENC, USGS, JMA Info)
|
|
26
23
|
if (msgData.eventId && msgData.magnitude !== undefined && msgData.epiIntensity === undefined) {
|
|
27
|
-
return this.parseEarthquakeInfo(msgData);
|
|
24
|
+
return this.parseEarthquakeInfo(msgData, source);
|
|
28
25
|
}
|
|
29
26
|
// Weather
|
|
30
27
|
if (msgData.headline && msgData.description) {
|
|
@@ -41,13 +38,49 @@ class FanStudioHandler extends base_1.BaseDataHandler {
|
|
|
41
38
|
return null;
|
|
42
39
|
}
|
|
43
40
|
}
|
|
44
|
-
|
|
45
|
-
//
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
41
|
+
detectSource(data) {
|
|
42
|
+
// Check for explicit type field
|
|
43
|
+
if (data.type) {
|
|
44
|
+
const typeMap = {
|
|
45
|
+
'cenc_eew': models_1.DataSource.FAN_STUDIO_CEA,
|
|
46
|
+
'cwa_eew': models_1.DataSource.FAN_STUDIO_CWA,
|
|
47
|
+
'jma_eew': models_1.DataSource.FAN_STUDIO_JMA,
|
|
48
|
+
'cenc_eq': models_1.DataSource.FAN_STUDIO_CENC,
|
|
49
|
+
'usgs_eq': models_1.DataSource.FAN_STUDIO_USGS,
|
|
50
|
+
'weather': models_1.DataSource.FAN_STUDIO_WEATHER,
|
|
51
|
+
'tsunami': models_1.DataSource.FAN_STUDIO_TSUNAMI,
|
|
52
|
+
};
|
|
53
|
+
if (typeMap[data.type])
|
|
54
|
+
return typeMap[data.type];
|
|
55
|
+
}
|
|
56
|
+
// Check for source field
|
|
57
|
+
if (data.source) {
|
|
58
|
+
const sourceStr = String(data.source).toLowerCase();
|
|
59
|
+
if (sourceStr.includes('cenc'))
|
|
60
|
+
return models_1.DataSource.FAN_STUDIO_CENC;
|
|
61
|
+
if (sourceStr.includes('cwa') || sourceStr.includes('taiwan'))
|
|
62
|
+
return models_1.DataSource.FAN_STUDIO_CWA;
|
|
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;
|
|
75
|
+
}
|
|
76
|
+
// Check for USGS fields
|
|
77
|
+
if (data.net === 'us' || data.properties?.net === 'us') {
|
|
78
|
+
return models_1.DataSource.FAN_STUDIO_USGS;
|
|
79
|
+
}
|
|
80
|
+
// Default to CEA for Chinese earthquake warnings
|
|
81
|
+
return models_1.DataSource.FAN_STUDIO_CEA;
|
|
82
|
+
}
|
|
83
|
+
parseEarthquakeWarning(data, source) {
|
|
51
84
|
const earthquake = {
|
|
52
85
|
id: data.id || '',
|
|
53
86
|
event_id: data.eventId || data.id || '',
|
|
@@ -59,11 +92,12 @@ class FanStudioHandler extends base_1.BaseDataHandler {
|
|
|
59
92
|
depth: Number(data.depth),
|
|
60
93
|
magnitude: Number(data.magnitude),
|
|
61
94
|
intensity: Number(data.epiIntensity),
|
|
95
|
+
scale: data.scale !== undefined ? Number(data.scale) : undefined,
|
|
62
96
|
place_name: data.placeName || '',
|
|
63
97
|
province: data.province,
|
|
64
98
|
updates: data.updates || 1,
|
|
65
99
|
is_final: data.isFinal || false,
|
|
66
|
-
is_cancel: false,
|
|
100
|
+
is_cancel: data.isCancel || false,
|
|
67
101
|
raw_data: data
|
|
68
102
|
};
|
|
69
103
|
return {
|
|
@@ -76,12 +110,13 @@ class FanStudioHandler extends base_1.BaseDataHandler {
|
|
|
76
110
|
raw_data: data
|
|
77
111
|
};
|
|
78
112
|
}
|
|
79
|
-
parseEarthquakeInfo(data) {
|
|
80
|
-
//
|
|
113
|
+
parseEarthquakeInfo(data, source) {
|
|
114
|
+
// Determine if USGS or CENC based on source detection
|
|
115
|
+
const finalSource = source === models_1.DataSource.FAN_STUDIO_CEA ? models_1.DataSource.FAN_STUDIO_CENC : source;
|
|
81
116
|
const earthquake = {
|
|
82
117
|
id: data.id || '',
|
|
83
118
|
event_id: data.eventId || data.id || '',
|
|
84
|
-
source:
|
|
119
|
+
source: finalSource,
|
|
85
120
|
disaster_type: models_1.DisasterType.EARTHQUAKE,
|
|
86
121
|
shock_time: this.parseDateTime(data.shockTime) || new Date().toISOString(),
|
|
87
122
|
latitude: Number(data.latitude) || 0,
|
|
@@ -97,7 +132,7 @@ class FanStudioHandler extends base_1.BaseDataHandler {
|
|
|
97
132
|
return {
|
|
98
133
|
id: earthquake.id,
|
|
99
134
|
data: earthquake,
|
|
100
|
-
source:
|
|
135
|
+
source: finalSource,
|
|
101
136
|
disaster_type: models_1.DisasterType.EARTHQUAKE,
|
|
102
137
|
receive_time: new Date().toISOString(),
|
|
103
138
|
push_count: 0,
|
|
@@ -115,7 +150,7 @@ class FanStudioHandler extends base_1.BaseDataHandler {
|
|
|
115
150
|
effective_time: this.parseDateTime(data.effectiveTime) || new Date().toISOString(),
|
|
116
151
|
disaster_type: models_1.DisasterType.WEATHER_ALARM,
|
|
117
152
|
issue_time: this.parseDateTime(data.issueTime),
|
|
118
|
-
affected_areas: [],
|
|
153
|
+
affected_areas: data.affectedAreas || [],
|
|
119
154
|
raw_data: data
|
|
120
155
|
};
|
|
121
156
|
return {
|
package/lib/handlers/p2p.js
CHANGED
|
@@ -33,8 +33,30 @@ class P2PHandler extends base_1.BaseDataHandler {
|
|
|
33
33
|
const areas = data.areas || [];
|
|
34
34
|
if (data.cancelled) {
|
|
35
35
|
this.logger.info(`[${this.sourceId}] EEW Cancelled`);
|
|
36
|
-
//
|
|
37
|
-
|
|
36
|
+
// Return a cancelled event so it can be broadcasted
|
|
37
|
+
const earthquake = {
|
|
38
|
+
id: data.id || '',
|
|
39
|
+
event_id: issueInfo.eventId || data.id || '',
|
|
40
|
+
source: models_1.DataSource.P2P_EEW,
|
|
41
|
+
disaster_type: models_1.DisasterType.EARTHQUAKE_WARNING,
|
|
42
|
+
shock_time: new Date().toISOString(),
|
|
43
|
+
latitude: 0,
|
|
44
|
+
longitude: 0,
|
|
45
|
+
place_name: '取消',
|
|
46
|
+
updates: 1,
|
|
47
|
+
is_final: true,
|
|
48
|
+
is_cancel: true,
|
|
49
|
+
raw_data: data
|
|
50
|
+
};
|
|
51
|
+
return {
|
|
52
|
+
id: earthquake.id,
|
|
53
|
+
data: earthquake,
|
|
54
|
+
source: models_1.DataSource.P2P_EEW,
|
|
55
|
+
disaster_type: models_1.DisasterType.EARTHQUAKE_WARNING,
|
|
56
|
+
receive_time: new Date().toISOString(),
|
|
57
|
+
push_count: 0,
|
|
58
|
+
raw_data: data
|
|
59
|
+
};
|
|
38
60
|
}
|
|
39
61
|
let maxScale = -1;
|
|
40
62
|
if (earthquakeInfo.maxScale !== undefined)
|
|
@@ -65,7 +87,7 @@ class P2PHandler extends base_1.BaseDataHandler {
|
|
|
65
87
|
is_cancel: data.cancelled || false,
|
|
66
88
|
is_training: data.test || false,
|
|
67
89
|
serial: issueInfo.serial,
|
|
68
|
-
updates: 1,
|
|
90
|
+
updates: 1,
|
|
69
91
|
raw_data: data
|
|
70
92
|
};
|
|
71
93
|
return {
|
|
@@ -90,7 +112,7 @@ class P2PHandler extends base_1.BaseDataHandler {
|
|
|
90
112
|
const scale = maxScale !== -1 ? this.convertP2PScale(maxScale) : undefined;
|
|
91
113
|
let depth = Number(hypocenter.depth);
|
|
92
114
|
if (isNaN(depth))
|
|
93
|
-
depth = 0;
|
|
115
|
+
depth = 0;
|
|
94
116
|
const earthquake = {
|
|
95
117
|
id: data.id || '',
|
|
96
118
|
event_id: data.id || '',
|
|
@@ -122,9 +144,56 @@ class P2PHandler extends base_1.BaseDataHandler {
|
|
|
122
144
|
};
|
|
123
145
|
}
|
|
124
146
|
parseTsunami(data) {
|
|
125
|
-
//
|
|
126
|
-
//
|
|
127
|
-
|
|
147
|
+
// P2P Tsunami format (code 552)
|
|
148
|
+
// Reference: https://www.p2pquake.net/json_api_v2/
|
|
149
|
+
const issueInfo = data.issue || {};
|
|
150
|
+
const areas = data.areas || [];
|
|
151
|
+
// Determine tsunami level from areas
|
|
152
|
+
let maxGrade = '';
|
|
153
|
+
const gradeOrder = ['Warning', 'Watch', 'Advisory', 'Unknown'];
|
|
154
|
+
for (const area of areas) {
|
|
155
|
+
const grade = area.grade || '';
|
|
156
|
+
if (!maxGrade || gradeOrder.indexOf(grade) < gradeOrder.indexOf(maxGrade)) {
|
|
157
|
+
maxGrade = grade;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
// Map grade to Chinese
|
|
161
|
+
const gradeMap = {
|
|
162
|
+
'MajorWarning': '大津波警报',
|
|
163
|
+
'Warning': '津波警报',
|
|
164
|
+
'Watch': '津波注意报',
|
|
165
|
+
'Advisory': '津波予报',
|
|
166
|
+
'Unknown': '未知'
|
|
167
|
+
};
|
|
168
|
+
const forecasts = areas.map((a) => ({
|
|
169
|
+
name: a.name || '',
|
|
170
|
+
grade: gradeMap[a.grade] || a.grade || '',
|
|
171
|
+
immediate: a.immediate || false,
|
|
172
|
+
firstHeight: a.firstHeight,
|
|
173
|
+
maxHeight: a.maxHeight
|
|
174
|
+
}));
|
|
175
|
+
const tsunami = {
|
|
176
|
+
id: data.id || `tsunami_${Date.now()}`,
|
|
177
|
+
code: String(data.code),
|
|
178
|
+
source: models_1.DataSource.P2P_TSUNAMI,
|
|
179
|
+
title: issueInfo.type === 'Focus' ? '津波情報(各地の満潮時刻・津波到達予想時刻)' : '津波予報',
|
|
180
|
+
level: gradeMap[maxGrade] || maxGrade || 'Unknown',
|
|
181
|
+
disaster_type: models_1.DisasterType.TSUNAMI,
|
|
182
|
+
org_unit: '気象庁',
|
|
183
|
+
issue_time: this.parseDateTime(issueInfo.time),
|
|
184
|
+
forecasts: forecasts,
|
|
185
|
+
monitoring_stations: [],
|
|
186
|
+
raw_data: data
|
|
187
|
+
};
|
|
188
|
+
return {
|
|
189
|
+
id: tsunami.id,
|
|
190
|
+
data: tsunami,
|
|
191
|
+
source: models_1.DataSource.P2P_TSUNAMI,
|
|
192
|
+
disaster_type: models_1.DisasterType.TSUNAMI,
|
|
193
|
+
receive_time: new Date().toISOString(),
|
|
194
|
+
push_count: 0,
|
|
195
|
+
raw_data: data
|
|
196
|
+
};
|
|
128
197
|
}
|
|
129
198
|
convertP2PScale(scale) {
|
|
130
199
|
const mapping = {
|
package/lib/handlers/wolfx.d.ts
CHANGED
package/lib/handlers/wolfx.js
CHANGED
|
@@ -16,12 +16,38 @@ class WolfxHandler extends base_1.BaseDataHandler {
|
|
|
16
16
|
else if (type === 'cenc_eew') {
|
|
17
17
|
return this.parseCENCEEW(data);
|
|
18
18
|
}
|
|
19
|
+
else if (type === 'cwa_eew') {
|
|
20
|
+
return this.parseCWAEEW(data);
|
|
21
|
+
}
|
|
19
22
|
else if (type === 'jma_eqlist') {
|
|
20
23
|
return this.parseJMAEqList(data);
|
|
21
24
|
}
|
|
22
25
|
else if (type === 'cenc_eqlist') {
|
|
23
|
-
|
|
24
|
-
|
|
26
|
+
return this.parseCENCEqList(data);
|
|
27
|
+
}
|
|
28
|
+
// If no type field, try to detect from data structure
|
|
29
|
+
if (data.EventID && data.OriginTime) {
|
|
30
|
+
// Likely EEW data
|
|
31
|
+
if (data.Hypocenter && data.Hypocenter.includes('日本')) {
|
|
32
|
+
return this.parseJMAEEW(data);
|
|
33
|
+
}
|
|
34
|
+
else if (data.HypoCenter) {
|
|
35
|
+
return this.parseCENCEEW(data);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
// Check for eqlist structure
|
|
39
|
+
if (data.No1 || Object.keys(data).some(k => k.startsWith('No'))) {
|
|
40
|
+
// Detect based on location content
|
|
41
|
+
const firstKey = Object.keys(data).find(k => k.startsWith('No'));
|
|
42
|
+
if (firstKey && data[firstKey]) {
|
|
43
|
+
const loc = data[firstKey].location || '';
|
|
44
|
+
if (loc.includes('中国') || loc.includes('省') || loc.includes('市')) {
|
|
45
|
+
return this.parseCENCEqList(data);
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
return this.parseJMAEqList(data);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
25
51
|
}
|
|
26
52
|
return null;
|
|
27
53
|
}
|
|
@@ -87,9 +113,36 @@ class WolfxHandler extends base_1.BaseDataHandler {
|
|
|
87
113
|
raw_data: data
|
|
88
114
|
};
|
|
89
115
|
}
|
|
116
|
+
parseCWAEEW(data) {
|
|
117
|
+
const earthquake = {
|
|
118
|
+
id: data.EventID || data.ID || '',
|
|
119
|
+
event_id: data.EventID || '',
|
|
120
|
+
source: models_1.DataSource.WOLFX_CWA_EEW,
|
|
121
|
+
disaster_type: models_1.DisasterType.EARTHQUAKE_WARNING,
|
|
122
|
+
shock_time: this.parseDateTime(data.OriginTime) || new Date().toISOString(),
|
|
123
|
+
latitude: Number(data.Latitude) || 0,
|
|
124
|
+
longitude: Number(data.Longitude) || 0,
|
|
125
|
+
depth: Number(data.Depth),
|
|
126
|
+
magnitude: Number(data.Magnitude),
|
|
127
|
+
intensity: Number(data.MaxIntensity),
|
|
128
|
+
place_name: data.HypoCenter || data.Location || '台湾',
|
|
129
|
+
updates: Number(data.ReportNum) || 1,
|
|
130
|
+
is_final: data.isFinal || false,
|
|
131
|
+
is_cancel: false,
|
|
132
|
+
raw_data: data
|
|
133
|
+
};
|
|
134
|
+
return {
|
|
135
|
+
id: earthquake.id,
|
|
136
|
+
data: earthquake,
|
|
137
|
+
source: models_1.DataSource.WOLFX_CWA_EEW,
|
|
138
|
+
disaster_type: models_1.DisasterType.EARTHQUAKE_WARNING,
|
|
139
|
+
receive_time: new Date().toISOString(),
|
|
140
|
+
push_count: 0,
|
|
141
|
+
raw_data: data
|
|
142
|
+
};
|
|
143
|
+
}
|
|
90
144
|
parseJMAEqList(data) {
|
|
91
|
-
// Find the latest earthquake (usually No1 or similar key
|
|
92
|
-
// The original code iterated and found the one with 'No' prefix
|
|
145
|
+
// Find the latest earthquake (usually No1 or similar key)
|
|
93
146
|
let eqInfo = null;
|
|
94
147
|
for (const key in data) {
|
|
95
148
|
if (key.startsWith('No') && typeof data[key] === 'object') {
|
|
@@ -130,6 +183,47 @@ class WolfxHandler extends base_1.BaseDataHandler {
|
|
|
130
183
|
raw_data: data
|
|
131
184
|
};
|
|
132
185
|
}
|
|
186
|
+
parseCENCEqList(data) {
|
|
187
|
+
// Find the latest earthquake
|
|
188
|
+
let eqInfo = null;
|
|
189
|
+
for (const key in data) {
|
|
190
|
+
if (key.startsWith('No') && typeof data[key] === 'object') {
|
|
191
|
+
eqInfo = data[key];
|
|
192
|
+
break;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
if (!eqInfo)
|
|
196
|
+
return null;
|
|
197
|
+
let depth = Number(eqInfo.depth);
|
|
198
|
+
if (isNaN(depth) && typeof eqInfo.depth === 'string') {
|
|
199
|
+
depth = Number(eqInfo.depth.replace(/[^0-9.]/g, ''));
|
|
200
|
+
}
|
|
201
|
+
const earthquake = {
|
|
202
|
+
id: eqInfo.md5 || eqInfo.id || '',
|
|
203
|
+
event_id: eqInfo.md5 || eqInfo.id || '',
|
|
204
|
+
source: models_1.DataSource.WOLFX_CENC_EQ,
|
|
205
|
+
disaster_type: models_1.DisasterType.EARTHQUAKE,
|
|
206
|
+
shock_time: this.parseDateTime(eqInfo.time) || new Date().toISOString(),
|
|
207
|
+
latitude: Number(eqInfo.latitude) || 0,
|
|
208
|
+
longitude: Number(eqInfo.longitude) || 0,
|
|
209
|
+
depth: depth || 0,
|
|
210
|
+
magnitude: Number(eqInfo.magnitude),
|
|
211
|
+
place_name: eqInfo.location || '',
|
|
212
|
+
updates: 1,
|
|
213
|
+
is_final: true,
|
|
214
|
+
is_cancel: false,
|
|
215
|
+
raw_data: data
|
|
216
|
+
};
|
|
217
|
+
return {
|
|
218
|
+
id: earthquake.id,
|
|
219
|
+
data: earthquake,
|
|
220
|
+
source: models_1.DataSource.WOLFX_CENC_EQ,
|
|
221
|
+
disaster_type: models_1.DisasterType.EARTHQUAKE,
|
|
222
|
+
receive_time: new Date().toISOString(),
|
|
223
|
+
push_count: 0,
|
|
224
|
+
raw_data: data
|
|
225
|
+
};
|
|
226
|
+
}
|
|
133
227
|
parseJMAScale(scaleStr) {
|
|
134
228
|
if (!scaleStr)
|
|
135
229
|
return undefined;
|
package/lib/index.d.ts
CHANGED
|
@@ -7,51 +7,19 @@ export declare const inject: {
|
|
|
7
7
|
export interface Config {
|
|
8
8
|
enabled: boolean;
|
|
9
9
|
target_groups: string[];
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
china_cenc_earthquake: boolean;
|
|
16
|
-
japan_jma_eew: boolean;
|
|
17
|
-
usgs_earthquake: boolean;
|
|
18
|
-
china_weather_alarm: boolean;
|
|
19
|
-
china_tsunami: boolean;
|
|
20
|
-
};
|
|
21
|
-
p2p_earthquake: {
|
|
22
|
-
enabled: boolean;
|
|
23
|
-
japan_jma_eew: boolean;
|
|
24
|
-
japan_jma_earthquake: boolean;
|
|
25
|
-
japan_jma_tsunami: boolean;
|
|
26
|
-
};
|
|
27
|
-
wolfx: {
|
|
28
|
-
enabled: boolean;
|
|
29
|
-
japan_jma_eew: boolean;
|
|
30
|
-
china_cenc_eew: boolean;
|
|
31
|
-
taiwan_cwa_eew: boolean;
|
|
32
|
-
japan_jma_earthquake: boolean;
|
|
33
|
-
china_cenc_earthquake: boolean;
|
|
34
|
-
};
|
|
35
|
-
global_quake: {
|
|
36
|
-
enabled: boolean;
|
|
37
|
-
};
|
|
10
|
+
data_types: {
|
|
11
|
+
earthquake_warning: boolean;
|
|
12
|
+
earthquake_info: boolean;
|
|
13
|
+
tsunami_warning: boolean;
|
|
14
|
+
weather_alarm: boolean;
|
|
38
15
|
};
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
};
|
|
45
|
-
scale_filter: {
|
|
46
|
-
enabled: boolean;
|
|
47
|
-
min_magnitude: number;
|
|
48
|
-
min_scale: number;
|
|
49
|
-
};
|
|
50
|
-
magnitude_only_filter: {
|
|
51
|
-
enabled: boolean;
|
|
52
|
-
min_magnitude: number;
|
|
53
|
-
};
|
|
16
|
+
regions: {
|
|
17
|
+
china: boolean;
|
|
18
|
+
taiwan: boolean;
|
|
19
|
+
japan: boolean;
|
|
20
|
+
global: boolean;
|
|
54
21
|
};
|
|
22
|
+
source_priority: 'auto' | 'wolfx' | 'fanstudio' | 'p2p';
|
|
55
23
|
}
|
|
56
24
|
export declare const Config: Schema<Config>;
|
|
57
25
|
export declare function apply(ctx: Context, config: Config): void;
|
package/lib/index.js
CHANGED
|
@@ -12,52 +12,25 @@ exports.inject = {
|
|
|
12
12
|
};
|
|
13
13
|
exports.Config = koishi_1.Schema.object({
|
|
14
14
|
enabled: koishi_1.Schema.boolean().default(true).description('启用灾害预警插件'),
|
|
15
|
-
target_groups: koishi_1.Schema.array(koishi_1.Schema.string()).default([]).description('
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
enabled: koishi_1.Schema.boolean().default(false).description('启用Wolfx数据源'),
|
|
35
|
-
japan_jma_eew: koishi_1.Schema.boolean().default(true).description('日本気象庁:緊急地震速報'),
|
|
36
|
-
china_cenc_eew: koishi_1.Schema.boolean().default(true).description('中国地震台网(CENC):地震预警'),
|
|
37
|
-
taiwan_cwa_eew: koishi_1.Schema.boolean().default(true).description('台湾中央气象署:地震预警'),
|
|
38
|
-
japan_jma_earthquake: koishi_1.Schema.boolean().default(true).description('日本気象庁(JMA):地震情報'),
|
|
39
|
-
china_cenc_earthquake: koishi_1.Schema.boolean().default(true).description('中国地震台网(CENC):地震测定'),
|
|
40
|
-
}).description('Wolfx API 数据源'),
|
|
41
|
-
global_quake: koishi_1.Schema.object({
|
|
42
|
-
enabled: koishi_1.Schema.boolean().default(false).description('启用Global Quake数据源'),
|
|
43
|
-
}).description('Global Quake 服务器推送'),
|
|
44
|
-
}).description('数据源配置'),
|
|
45
|
-
earthquake_filters: koishi_1.Schema.object({
|
|
46
|
-
intensity_filter: koishi_1.Schema.object({
|
|
47
|
-
enabled: koishi_1.Schema.boolean().default(true).description('启用烈度过滤器'),
|
|
48
|
-
min_magnitude: koishi_1.Schema.number().default(2.0).description('最小震级'),
|
|
49
|
-
min_intensity: koishi_1.Schema.number().default(4.0).description('最小烈度'),
|
|
50
|
-
}).description('基于震级和烈度的地震过滤器'),
|
|
51
|
-
scale_filter: koishi_1.Schema.object({
|
|
52
|
-
enabled: koishi_1.Schema.boolean().default(true).description('启用震度过滤器'),
|
|
53
|
-
min_magnitude: koishi_1.Schema.number().default(2.0).description('最小震级'),
|
|
54
|
-
min_scale: koishi_1.Schema.number().default(1.0).description('最小震度'),
|
|
55
|
-
}).description('基于震级和震度的地震过滤器'),
|
|
56
|
-
magnitude_only_filter: koishi_1.Schema.object({
|
|
57
|
-
enabled: koishi_1.Schema.boolean().default(true).description('启用仅震级过滤器'),
|
|
58
|
-
min_magnitude: koishi_1.Schema.number().default(4.5).description('最小震级'),
|
|
59
|
-
}).description('USGS震级过滤器'),
|
|
60
|
-
}).description('地震信息过滤器配置'),
|
|
15
|
+
target_groups: koishi_1.Schema.array(koishi_1.Schema.string()).default([]).description('推送目标群号列表,格式: 平台:群号(如 onebot:123456)'),
|
|
16
|
+
data_types: koishi_1.Schema.object({
|
|
17
|
+
earthquake_warning: koishi_1.Schema.boolean().default(true).description('地震预警(实时速报,震前预警)'),
|
|
18
|
+
earthquake_info: koishi_1.Schema.boolean().default(true).description('地震信息(震后测定报告)'),
|
|
19
|
+
tsunami_warning: koishi_1.Schema.boolean().default(true).description('海啸预警'),
|
|
20
|
+
weather_alarm: koishi_1.Schema.boolean().default(false).description('气象预警(中国)'),
|
|
21
|
+
}).description('接收的灾害类型'),
|
|
22
|
+
regions: koishi_1.Schema.object({
|
|
23
|
+
china: koishi_1.Schema.boolean().default(true).description('中国大陆'),
|
|
24
|
+
taiwan: koishi_1.Schema.boolean().default(true).description('台湾'),
|
|
25
|
+
japan: koishi_1.Schema.boolean().default(true).description('日本'),
|
|
26
|
+
global: koishi_1.Schema.boolean().default(false).description('全球(USGS/GlobalQuake)'),
|
|
27
|
+
}).description('接收的地区'),
|
|
28
|
+
source_priority: koishi_1.Schema.union([
|
|
29
|
+
koishi_1.Schema.const('auto').description('自动选择最佳数据源'),
|
|
30
|
+
koishi_1.Schema.const('wolfx').description('优先使用 Wolfx API'),
|
|
31
|
+
koishi_1.Schema.const('fanstudio').description('优先使用 FAN Studio'),
|
|
32
|
+
koishi_1.Schema.const('p2p').description('优先使用 P2P地震情報'),
|
|
33
|
+
]).default('auto').description('数据源优先级'),
|
|
61
34
|
});
|
|
62
35
|
function apply(ctx, config) {
|
|
63
36
|
const service = new service_1.DisasterWarningService(ctx, config);
|
package/lib/pusher.d.ts
CHANGED
package/lib/pusher.js
CHANGED
|
@@ -4,6 +4,13 @@ exports.MessagePushManager = void 0;
|
|
|
4
4
|
const koishi_1 = require("koishi");
|
|
5
5
|
const models_1 = require("./models");
|
|
6
6
|
const logger = new koishi_1.Logger('disaster-pusher');
|
|
7
|
+
// Hardcoded filter thresholds - no user configuration needed
|
|
8
|
+
const FILTER_THRESHOLDS = {
|
|
9
|
+
MIN_MAGNITUDE_ABSOLUTE: 3.0, // Always ignore earthquakes below M3.0
|
|
10
|
+
MIN_MAGNITUDE_FOR_PUSH: 4.0, // Push if magnitude >= 4.0
|
|
11
|
+
MIN_INTENSITY_FOR_PUSH: 4.0, // Or push if intensity >= 4.0 (China)
|
|
12
|
+
MIN_SCALE_FOR_PUSH: 4.0, // Or push if shindo scale >= 4 (Japan)
|
|
13
|
+
};
|
|
7
14
|
class MessagePushManager {
|
|
8
15
|
constructor(ctx, config) {
|
|
9
16
|
this.ctx = ctx;
|
|
@@ -21,39 +28,33 @@ class MessagePushManager {
|
|
|
21
28
|
await this.broadcast(message);
|
|
22
29
|
}
|
|
23
30
|
shouldFilter(event) {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
if (data.magnitude !== undefined && data.magnitude >= minMag)
|
|
52
|
-
magPass = true;
|
|
53
|
-
if (data.scale >= minScale)
|
|
54
|
-
scalePass = true;
|
|
55
|
-
if (!magPass && !scalePass)
|
|
56
|
-
return true;
|
|
31
|
+
// Only filter earthquake events
|
|
32
|
+
if (event.disaster_type !== models_1.DisasterType.EARTHQUAKE && event.disaster_type !== models_1.DisasterType.EARTHQUAKE_WARNING) {
|
|
33
|
+
return false; // Don't filter tsunami/weather
|
|
34
|
+
}
|
|
35
|
+
const data = event.data;
|
|
36
|
+
const { MIN_MAGNITUDE_ABSOLUTE, MIN_MAGNITUDE_FOR_PUSH, MIN_INTENSITY_FOR_PUSH, MIN_SCALE_FOR_PUSH } = FILTER_THRESHOLDS;
|
|
37
|
+
// Always filter out very small earthquakes
|
|
38
|
+
if (data.magnitude !== undefined && data.magnitude < MIN_MAGNITUDE_ABSOLUTE) {
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
// Pass if magnitude is significant
|
|
42
|
+
if (data.magnitude !== undefined && data.magnitude >= MIN_MAGNITUDE_FOR_PUSH) {
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
// Pass if intensity is significant (Chinese sources)
|
|
46
|
+
if (data.intensity !== undefined && data.intensity >= MIN_INTENSITY_FOR_PUSH) {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
// Pass if scale is significant (Japanese sources)
|
|
50
|
+
if (data.scale !== undefined && data.scale >= MIN_SCALE_FOR_PUSH) {
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
// If magnitude is between 3.0-4.0 and no significant intensity/scale, filter out
|
|
54
|
+
if (data.magnitude !== undefined && data.magnitude >= MIN_MAGNITUDE_ABSOLUTE && data.magnitude < MIN_MAGNITUDE_FOR_PUSH) {
|
|
55
|
+
// Only filter if we don't have intensity/scale data that would make it significant
|
|
56
|
+
if (data.intensity === undefined && data.scale === undefined) {
|
|
57
|
+
return true;
|
|
57
58
|
}
|
|
58
59
|
}
|
|
59
60
|
return false;
|
|
@@ -72,42 +73,42 @@ class MessagePushManager {
|
|
|
72
73
|
}
|
|
73
74
|
}
|
|
74
75
|
formatEarthquake(data) {
|
|
75
|
-
const type = data.disaster_type === models_1.DisasterType.EARTHQUAKE_WARNING ? '地震预警' : '地震信息';
|
|
76
|
+
const type = data.disaster_type === models_1.DisasterType.EARTHQUAKE_WARNING ? '🚨 地震预警' : '📋 地震信息';
|
|
76
77
|
const finalStr = data.is_final ? '【最终报】' : `【第${data.updates}报】`;
|
|
77
78
|
const cancelStr = data.is_cancel ? '【已取消】' : '';
|
|
78
79
|
let msg = `${cancelStr}${type} ${finalStr}\n`;
|
|
79
|
-
msg +=
|
|
80
|
-
msg +=
|
|
81
|
-
msg +=
|
|
82
|
-
msg +=
|
|
80
|
+
msg += `📍 震源:${data.place_name}\n`;
|
|
81
|
+
msg += `🕐 时间:${this.formatTime(data.shock_time)}\n`;
|
|
82
|
+
msg += `📊 震级:M${data.magnitude?.toFixed(1) || '未知'}\n`;
|
|
83
|
+
msg += `📏 深度:${data.depth !== undefined ? data.depth + 'km' : '未知'}\n`;
|
|
83
84
|
if (data.intensity !== undefined) {
|
|
84
|
-
msg +=
|
|
85
|
+
msg += `🔥 最大烈度:${data.intensity.toFixed(1)}\n`;
|
|
85
86
|
}
|
|
86
87
|
if (data.scale !== undefined) {
|
|
87
|
-
msg +=
|
|
88
|
+
msg += `🎚️ 最大震度:${this.formatScale(data.scale)}\n`;
|
|
88
89
|
}
|
|
89
|
-
msg +=
|
|
90
|
+
msg += `📡 数据源:${this.formatSource(data.source)}`;
|
|
90
91
|
return msg;
|
|
91
92
|
}
|
|
92
93
|
formatTsunami(data) {
|
|
93
|
-
let msg =
|
|
94
|
-
msg +=
|
|
95
|
-
msg +=
|
|
94
|
+
let msg = `🌊 【海啸预警】${data.title}\n`;
|
|
95
|
+
msg += `⚠️ 级别:${data.level}\n`;
|
|
96
|
+
msg += `🏛️ 发布单位:${data.org_unit}\n`;
|
|
96
97
|
if (data.forecasts && data.forecasts.length > 0) {
|
|
97
|
-
msg +=
|
|
98
|
+
msg += `📍 预报区域:\n`;
|
|
98
99
|
data.forecasts.slice(0, 5).forEach((f) => {
|
|
99
|
-
msg +=
|
|
100
|
+
msg += ` • ${f.name || f.areaName}: ${f.grade || f.level}\n`;
|
|
100
101
|
});
|
|
101
102
|
if (data.forecasts.length > 5)
|
|
102
|
-
msg +=
|
|
103
|
+
msg += ` ...等${data.forecasts.length}个区域\n`;
|
|
103
104
|
}
|
|
104
105
|
return msg;
|
|
105
106
|
}
|
|
106
107
|
formatWeather(data) {
|
|
107
|
-
let msg =
|
|
108
|
-
msg +=
|
|
109
|
-
msg +=
|
|
110
|
-
msg +=
|
|
108
|
+
let msg = `⛈️ 【气象预警】${data.headline}\n`;
|
|
109
|
+
msg += `📋 类型:${data.type}\n`;
|
|
110
|
+
msg += `🕐 发布时间:${this.formatTime(data.issue_time || data.effective_time)}\n`;
|
|
111
|
+
msg += `📝 详情:${data.description}\n`;
|
|
111
112
|
return msg;
|
|
112
113
|
}
|
|
113
114
|
formatTime(isoStr) {
|
|
@@ -119,29 +120,35 @@ class MessagePushManager {
|
|
|
119
120
|
}
|
|
120
121
|
}
|
|
121
122
|
formatScale(scale) {
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
123
|
+
const scaleMap = {
|
|
124
|
+
1.0: '1', 2.0: '2', 3.0: '3', 4.0: '4',
|
|
125
|
+
4.5: '5弱', 5.0: '5强', 5.5: '6弱', 6.0: '6强', 7.0: '7'
|
|
126
|
+
};
|
|
127
|
+
return scaleMap[scale] || scale.toString();
|
|
128
|
+
}
|
|
129
|
+
formatSource(source) {
|
|
130
|
+
const sourceMap = {
|
|
131
|
+
'fan_studio_cea': '中国地震预警网',
|
|
132
|
+
'fan_studio_cwa': '台湾中央气象署',
|
|
133
|
+
'fan_studio_cenc': '中国地震台网',
|
|
134
|
+
'fan_studio_jma': '日本气象厅',
|
|
135
|
+
'fan_studio_usgs': 'USGS',
|
|
136
|
+
'fan_studio_weather': '中国气象局',
|
|
137
|
+
'fan_studio_tsunami': '海啸预警中心',
|
|
138
|
+
'p2p_eew': 'P2P地震情報',
|
|
139
|
+
'p2p_earthquake': 'P2P地震情報',
|
|
140
|
+
'p2p_tsunami': '日本气象厅',
|
|
141
|
+
'wolfx_jma_eew': 'Wolfx-JMA',
|
|
142
|
+
'wolfx_cenc_eew': 'Wolfx-CENC',
|
|
143
|
+
'wolfx_cwa_eew': 'Wolfx-CWA',
|
|
144
|
+
'wolfx_jma_eq': 'Wolfx-JMA',
|
|
145
|
+
'wolfx_cenc_eq': 'Wolfx-CENC',
|
|
146
|
+
'global_quake': 'GlobalQuake'
|
|
147
|
+
};
|
|
148
|
+
return sourceMap[source] || source;
|
|
141
149
|
}
|
|
142
150
|
async broadcast(message) {
|
|
143
151
|
for (const groupId of this.config.target_groups) {
|
|
144
|
-
// Default to onebot if no platform specified
|
|
145
152
|
const channelId = groupId.includes(':') ? groupId : `onebot:${groupId}`;
|
|
146
153
|
try {
|
|
147
154
|
await this.ctx.broadcast([channelId], message);
|
package/lib/service.d.ts
CHANGED
|
@@ -7,15 +7,16 @@ export declare class DisasterWarningService {
|
|
|
7
7
|
private pusher;
|
|
8
8
|
private ctx;
|
|
9
9
|
private handlers;
|
|
10
|
+
private wolfxHandlers;
|
|
10
11
|
constructor(ctx: Context, config: Config);
|
|
11
12
|
start(): Promise<void>;
|
|
12
13
|
stop(): Promise<void>;
|
|
13
|
-
private
|
|
14
|
+
private connectBasedOnConfig;
|
|
14
15
|
private connectWebSocket;
|
|
15
16
|
private handleEvent;
|
|
16
17
|
private shouldPushEvent;
|
|
17
18
|
private connectFanStudio;
|
|
18
19
|
private connectP2P;
|
|
19
|
-
private
|
|
20
|
+
private connectWolfxSource;
|
|
20
21
|
private connectGlobalQuake;
|
|
21
22
|
}
|
package/lib/service.js
CHANGED
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.DisasterWarningService = void 0;
|
|
4
4
|
const koishi_1 = require("koishi");
|
|
5
5
|
const ws_1 = require("ws");
|
|
6
|
+
const models_1 = require("./models");
|
|
6
7
|
const handlers_1 = require("./handlers");
|
|
7
8
|
const pusher_1 = require("./pusher");
|
|
8
9
|
const logger = new koishi_1.Logger('disaster-warning');
|
|
@@ -10,21 +11,26 @@ class DisasterWarningService {
|
|
|
10
11
|
constructor(ctx, config) {
|
|
11
12
|
this.connections = {};
|
|
12
13
|
this.reconnectTimers = {};
|
|
14
|
+
this.wolfxHandlers = {};
|
|
13
15
|
this.ctx = ctx;
|
|
14
16
|
this.config = config;
|
|
15
17
|
this.pusher = new pusher_1.MessagePushManager(ctx, config);
|
|
16
18
|
this.handlers = {
|
|
17
19
|
fanStudio: new handlers_1.FanStudioHandler(),
|
|
18
20
|
p2p: new handlers_1.P2PHandler(),
|
|
19
|
-
wolfx: new handlers_1.WolfxHandler('wolfx'),
|
|
20
21
|
globalQuake: new handlers_1.GlobalQuakeHandler()
|
|
21
22
|
};
|
|
23
|
+
// Initialize Wolfx handlers
|
|
24
|
+
const wolfxKeys = ['jma_eew', 'cenc_eew', 'cwa_eew', 'jma_eqlist', 'cenc_eqlist'];
|
|
25
|
+
for (const key of wolfxKeys) {
|
|
26
|
+
this.wolfxHandlers[key] = new handlers_1.WolfxHandler(`wolfx_${key}`);
|
|
27
|
+
}
|
|
22
28
|
}
|
|
23
29
|
async start() {
|
|
24
30
|
if (!this.config.enabled)
|
|
25
31
|
return;
|
|
26
32
|
logger.info('Disaster Warning Service starting...');
|
|
27
|
-
this.
|
|
33
|
+
this.connectBasedOnConfig();
|
|
28
34
|
}
|
|
29
35
|
async stop() {
|
|
30
36
|
logger.info('Disaster Warning Service stopping...');
|
|
@@ -35,17 +41,51 @@ class DisasterWarningService {
|
|
|
35
41
|
clearTimeout(this.reconnectTimers[key]);
|
|
36
42
|
}
|
|
37
43
|
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
44
|
+
connectBasedOnConfig() {
|
|
45
|
+
const { regions, data_types, source_priority } = this.config;
|
|
46
|
+
// Determine which connections to make based on regions and source priority
|
|
47
|
+
const needsJapan = regions.japan;
|
|
48
|
+
const needsChina = regions.china;
|
|
49
|
+
const needsTaiwan = regions.taiwan;
|
|
50
|
+
const needsGlobal = regions.global;
|
|
51
|
+
// Connect to appropriate sources based on priority
|
|
52
|
+
if (source_priority === 'auto' || source_priority === 'wolfx') {
|
|
53
|
+
// Wolfx is best for real-time EEW
|
|
54
|
+
if (needsJapan && data_types.earthquake_warning) {
|
|
55
|
+
this.connectWolfxSource('jma_eew', 'wss://ws-api.wolfx.jp/jma_eew');
|
|
56
|
+
}
|
|
57
|
+
if (needsChina && data_types.earthquake_warning) {
|
|
58
|
+
this.connectWolfxSource('cenc_eew', 'wss://ws-api.wolfx.jp/cenc_eew');
|
|
59
|
+
}
|
|
60
|
+
if (needsTaiwan && data_types.earthquake_warning) {
|
|
61
|
+
this.connectWolfxSource('cwa_eew', 'wss://ws-api.wolfx.jp/cwa_eew');
|
|
62
|
+
}
|
|
63
|
+
if (needsJapan && data_types.earthquake_info) {
|
|
64
|
+
this.connectWolfxSource('jma_eqlist', 'wss://ws-api.wolfx.jp/jma_eqlist');
|
|
65
|
+
}
|
|
66
|
+
if (needsChina && data_types.earthquake_info) {
|
|
67
|
+
this.connectWolfxSource('cenc_eqlist', 'wss://ws-api.wolfx.jp/cenc_eqlist');
|
|
68
|
+
}
|
|
41
69
|
}
|
|
42
|
-
if (
|
|
43
|
-
|
|
70
|
+
if (source_priority === 'auto' || source_priority === 'p2p') {
|
|
71
|
+
// P2P is good for Japan data including tsunami
|
|
72
|
+
if (needsJapan) {
|
|
73
|
+
if (data_types.earthquake_warning || data_types.earthquake_info || data_types.tsunami_warning) {
|
|
74
|
+
this.connectP2P();
|
|
75
|
+
}
|
|
76
|
+
}
|
|
44
77
|
}
|
|
45
|
-
if (
|
|
46
|
-
|
|
78
|
+
if (source_priority === 'auto' || source_priority === 'fanstudio') {
|
|
79
|
+
// FAN Studio has Chinese weather and tsunami
|
|
80
|
+
const needsFanStudio = (needsChina && data_types.weather_alarm) ||
|
|
81
|
+
(needsChina && data_types.tsunami_warning) ||
|
|
82
|
+
(needsGlobal && data_types.earthquake_info); // USGS via FanStudio
|
|
83
|
+
if (needsFanStudio) {
|
|
84
|
+
this.connectFanStudio();
|
|
85
|
+
}
|
|
47
86
|
}
|
|
48
|
-
|
|
87
|
+
// Global Quake for global coverage
|
|
88
|
+
if (needsGlobal && data_types.earthquake_warning) {
|
|
49
89
|
this.connectGlobalQuake();
|
|
50
90
|
}
|
|
51
91
|
}
|
|
@@ -87,47 +127,45 @@ class DisasterWarningService {
|
|
|
87
127
|
await this.pusher.pushEvent(event);
|
|
88
128
|
}
|
|
89
129
|
shouldPushEvent(event) {
|
|
90
|
-
const
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
return true;
|
|
130
|
-
}
|
|
130
|
+
const { data_types, regions } = this.config;
|
|
131
|
+
// Check disaster type
|
|
132
|
+
const isEarthquakeWarning = event.disaster_type === models_1.DisasterType.EARTHQUAKE_WARNING;
|
|
133
|
+
const isEarthquakeInfo = event.disaster_type === models_1.DisasterType.EARTHQUAKE;
|
|
134
|
+
const isTsunami = event.disaster_type === models_1.DisasterType.TSUNAMI;
|
|
135
|
+
const isWeather = event.disaster_type === models_1.DisasterType.WEATHER_ALARM;
|
|
136
|
+
if (isEarthquakeWarning && !data_types.earthquake_warning)
|
|
137
|
+
return false;
|
|
138
|
+
if (isEarthquakeInfo && !data_types.earthquake_info)
|
|
139
|
+
return false;
|
|
140
|
+
if (isTsunami && !data_types.tsunami_warning)
|
|
141
|
+
return false;
|
|
142
|
+
if (isWeather && !data_types.weather_alarm)
|
|
143
|
+
return false;
|
|
144
|
+
// Check region based on data source
|
|
145
|
+
const source = event.source;
|
|
146
|
+
const isJapanSource = [
|
|
147
|
+
models_1.DataSource.P2P_EEW, models_1.DataSource.P2P_EARTHQUAKE, models_1.DataSource.P2P_TSUNAMI,
|
|
148
|
+
models_1.DataSource.WOLFX_JMA_EEW, models_1.DataSource.WOLFX_JMA_EQ, models_1.DataSource.FAN_STUDIO_JMA
|
|
149
|
+
].includes(source);
|
|
150
|
+
const isChinaSource = [
|
|
151
|
+
models_1.DataSource.FAN_STUDIO_CEA, models_1.DataSource.FAN_STUDIO_CENC, models_1.DataSource.FAN_STUDIO_WEATHER,
|
|
152
|
+
models_1.DataSource.FAN_STUDIO_TSUNAMI, models_1.DataSource.WOLFX_CENC_EEW, models_1.DataSource.WOLFX_CENC_EQ
|
|
153
|
+
].includes(source);
|
|
154
|
+
const isTaiwanSource = [
|
|
155
|
+
models_1.DataSource.FAN_STUDIO_CWA, models_1.DataSource.WOLFX_CWA_EEW
|
|
156
|
+
].includes(source);
|
|
157
|
+
const isGlobalSource = [
|
|
158
|
+
models_1.DataSource.FAN_STUDIO_USGS, models_1.DataSource.GLOBAL_QUAKE
|
|
159
|
+
].includes(source);
|
|
160
|
+
if (isJapanSource && !regions.japan)
|
|
161
|
+
return false;
|
|
162
|
+
if (isChinaSource && !regions.china)
|
|
163
|
+
return false;
|
|
164
|
+
if (isTaiwanSource && !regions.taiwan)
|
|
165
|
+
return false;
|
|
166
|
+
if (isGlobalSource && !regions.global)
|
|
167
|
+
return false;
|
|
168
|
+
return true;
|
|
131
169
|
}
|
|
132
170
|
connectFanStudio() {
|
|
133
171
|
const url = "wss://ws.fanstudio.tech/all";
|
|
@@ -143,23 +181,12 @@ class DisasterWarningService {
|
|
|
143
181
|
this.handleEvent(event);
|
|
144
182
|
});
|
|
145
183
|
}
|
|
146
|
-
|
|
147
|
-
const
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
"china_cenc_earthquake": "wss://ws-api.wolfx.jp/cenc_eqlist",
|
|
153
|
-
};
|
|
154
|
-
for (const [key, url] of Object.entries(wolfx_sources)) {
|
|
155
|
-
if (this.config.data_sources.wolfx[key]) {
|
|
156
|
-
this.connectWebSocket(`wolfx_${key}`, url, (data) => {
|
|
157
|
-
const handler = new handlers_1.WolfxHandler(`wolfx_${key}`);
|
|
158
|
-
const event = handler.parseMessage(data);
|
|
159
|
-
this.handleEvent(event);
|
|
160
|
-
});
|
|
161
|
-
}
|
|
162
|
-
}
|
|
184
|
+
connectWolfxSource(key, url) {
|
|
185
|
+
const handler = this.wolfxHandlers[key];
|
|
186
|
+
this.connectWebSocket(`wolfx_${key}`, url, (data) => {
|
|
187
|
+
const event = handler.parseMessage(data);
|
|
188
|
+
this.handleEvent(event);
|
|
189
|
+
});
|
|
163
190
|
}
|
|
164
191
|
connectGlobalQuake() {
|
|
165
192
|
const url = "wss://gqm.aloys233.top/ws";
|
package/package.json
CHANGED
|
@@ -1,19 +1,30 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "koishi-plugin-disaster-warning",
|
|
3
|
+
"version": "0.0.8",
|
|
3
4
|
"description": "Koishi 灾害预警插件,支持多数据源(地震、海啸、气象预警)",
|
|
4
|
-
"
|
|
5
|
+
"contributors": [
|
|
6
|
+
"lumia.wang <fenglian19980510@gmail.com>"
|
|
7
|
+
],
|
|
5
8
|
"main": "lib/index.js",
|
|
6
9
|
"typings": "lib/index.d.ts",
|
|
7
10
|
"files": [
|
|
8
11
|
"lib",
|
|
9
12
|
"dist"
|
|
10
13
|
],
|
|
14
|
+
"homepage": "https://github.com/lumia1998/koishi-plugin-disaster-warning",
|
|
15
|
+
"bugs": {
|
|
16
|
+
"url": "https://github.com/lumia1998/koishi-plugin-disaster-warning/issues"
|
|
17
|
+
},
|
|
18
|
+
"repository": {
|
|
19
|
+
"type": "git",
|
|
20
|
+
"url": "git+https://github.com/lumia1998/koishi-plugin-disaster-warning.git"
|
|
21
|
+
},
|
|
11
22
|
"license": "AGPL-3.0",
|
|
12
23
|
"scripts": {
|
|
13
|
-
"build": "tsc"
|
|
14
|
-
"lint": "eslint src --ext .ts"
|
|
24
|
+
"build": "tsc"
|
|
15
25
|
},
|
|
16
26
|
"keywords": [
|
|
27
|
+
"chatbot",
|
|
17
28
|
"koishi",
|
|
18
29
|
"plugin",
|
|
19
30
|
"disaster",
|
|
@@ -23,14 +34,12 @@
|
|
|
23
34
|
"peerDependencies": {
|
|
24
35
|
"koishi": "^4.18.0"
|
|
25
36
|
},
|
|
26
|
-
"dependencies": {
|
|
27
|
-
"ws": "^8.18.0"
|
|
28
|
-
},
|
|
29
37
|
"devDependencies": {
|
|
30
38
|
"@types/node": "^20.0.0",
|
|
31
39
|
"@types/ws": "^8.5.10",
|
|
32
40
|
"koishi": "^4.18.0",
|
|
33
|
-
"typescript": "^5.0.0"
|
|
41
|
+
"typescript": "^5.0.0",
|
|
42
|
+
"ws": "^8.18.0"
|
|
34
43
|
},
|
|
35
44
|
"koishi": {
|
|
36
45
|
"description": {
|