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 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
- // Identify type based on fields
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) - usually has 'type' or specific fields
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
- parseEarthquakeWarning(data) {
45
- // Determine source more specifically if possible, otherwise default to CEA
46
- // This is a simplification. In reality we might need to check specific fields to distinguish CEA/CWA/JMA
47
- let source = models_1.DataSource.FAN_STUDIO_CEA;
48
- // If it has 'scale' instead of 'intensity', it might be JMA
49
- // If it has 'province' like '台湾', it might be CWA
50
- // For now, let's map based on some heuristics or default to CEA
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, // TODO: Check for cancel signal
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
- // CENC or USGS
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: models_1.DataSource.FAN_STUDIO_CENC, // Default to CENC
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: models_1.DataSource.FAN_STUDIO_CENC,
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: [], // TODO: Parse affected areas if available
153
+ affected_areas: data.affectedAreas || [],
119
154
  raw_data: data
120
155
  };
121
156
  return {
@@ -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
- // We might want to handle cancellation
37
- return null;
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, // P2P doesn't explicitly send update count in the same way, but serial might be it
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; // Or undefined
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
- // TODO: Implement Tsunami parsing if needed
126
- // For now return null or basic implementation
127
- return null;
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 = {
@@ -5,6 +5,8 @@ export declare class WolfxHandler extends BaseDataHandler {
5
5
  parseMessage(data: any): DisasterEvent | null;
6
6
  private parseJMAEEW;
7
7
  private parseCENCEEW;
8
+ private parseCWAEEW;
8
9
  private parseJMAEqList;
10
+ private parseCENCEqList;
9
11
  private parseJMAScale;
10
12
  }
@@ -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
- // TODO: Implement CENC EqList
24
- return null;
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, or we iterate)
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
- data_sources: {
11
- fan_studio: {
12
- enabled: boolean;
13
- china_earthquake_warning: boolean;
14
- taiwan_cwa_earthquake: boolean;
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
- earthquake_filters: {
40
- intensity_filter: {
41
- enabled: boolean;
42
- min_magnitude: number;
43
- min_intensity: number;
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
- data_sources: koishi_1.Schema.object({
17
- fan_studio: koishi_1.Schema.object({
18
- enabled: koishi_1.Schema.boolean().default(true).description('启用FAN Studio数据源'),
19
- china_earthquake_warning: koishi_1.Schema.boolean().default(true).description('中国地震网地震预警'),
20
- taiwan_cwa_earthquake: koishi_1.Schema.boolean().default(true).description('台湾中央气象署:强震即时警报'),
21
- china_cenc_earthquake: koishi_1.Schema.boolean().default(false).description('中国地震台网(CENC):地震测定'),
22
- japan_jma_eew: koishi_1.Schema.boolean().default(false).description('日本气象厅(JMA):紧急地震速报'),
23
- usgs_earthquake: koishi_1.Schema.boolean().default(false).description('美国地质调查局(USGS):地震测定'),
24
- china_weather_alarm: koishi_1.Schema.boolean().default(false).description('中国气象局:气象预警'),
25
- china_tsunami: koishi_1.Schema.boolean().default(false).description('自然资源部海啸预警中心:海啸预警信息'),
26
- }).description('FAN Studio WebSocket 数据源'),
27
- p2p_earthquake: koishi_1.Schema.object({
28
- enabled: koishi_1.Schema.boolean().default(false).description('启用P2P地震情報数据源'),
29
- japan_jma_eew: koishi_1.Schema.boolean().default(true).description('日本気象庁:緊急地震速報'),
30
- japan_jma_earthquake: koishi_1.Schema.boolean().default(true).description('日本気象庁(JMA):地震情報'),
31
- japan_jma_tsunami: koishi_1.Schema.boolean().default(true).description('日本気象庁:津波予報'),
32
- }).description('P2P地震情報 WebSocket 数据源'),
33
- wolfx: koishi_1.Schema.object({
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
@@ -13,5 +13,6 @@ export declare class MessagePushManager {
13
13
  private formatWeather;
14
14
  private formatTime;
15
15
  private formatScale;
16
+ private formatSource;
16
17
  private broadcast;
17
18
  }
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
- if (event.disaster_type === models_1.DisasterType.EARTHQUAKE || event.disaster_type === models_1.DisasterType.EARTHQUAKE_WARNING) {
25
- const data = event.data;
26
- const filters = this.config.earthquake_filters;
27
- // Intensity Filter
28
- if (filters.intensity_filter.enabled) {
29
- const minMag = filters.intensity_filter.min_magnitude;
30
- const minInt = filters.intensity_filter.min_intensity;
31
- // Pass if magnitude OR intensity condition is met (OR logic as per schema hint)
32
- // Wait, schema hint says "Satisfy magnitude requirement OR satisfy intensity requirement"
33
- // Usually it means if (mag >= minMag || int >= minInt) -> Pass
34
- let magPass = false;
35
- let intPass = false;
36
- if (data.magnitude !== undefined && data.magnitude >= minMag)
37
- magPass = true;
38
- if (data.intensity !== undefined && data.intensity >= minInt)
39
- intPass = true;
40
- // If neither is met (and relevant fields exist), filter out
41
- // If fields are missing, we might be lenient or strict. Let's be lenient if one is missing but other passes.
42
- if (!magPass && !intPass)
43
- return true;
44
- }
45
- // Scale Filter (Japan)
46
- if (filters.scale_filter.enabled && data.scale !== undefined) {
47
- const minMag = filters.scale_filter.min_magnitude;
48
- const minScale = filters.scale_filter.min_scale;
49
- let magPass = false;
50
- let scalePass = false;
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 += `震源:${data.place_name}\n`;
80
- msg += `时间:${this.formatTime(data.shock_time)}\n`;
81
- msg += `震级:M${data.magnitude?.toFixed(1) || '未知'}\n`;
82
- msg += `深度:${data.depth !== undefined ? data.depth + 'km' : '未知'}\n`;
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 += `最大烈度:${data.intensity.toFixed(1)}\n`;
85
+ msg += `🔥 最大烈度:${data.intensity.toFixed(1)}\n`;
85
86
  }
86
87
  if (data.scale !== undefined) {
87
- msg += `最大震度:${this.formatScale(data.scale)}\n`;
88
+ msg += `🎚️ 最大震度:${this.formatScale(data.scale)}\n`;
88
89
  }
89
- msg += `数据源:${data.source}`;
90
+ msg += `📡 数据源:${this.formatSource(data.source)}`;
90
91
  return msg;
91
92
  }
92
93
  formatTsunami(data) {
93
- let msg = `【海啸预警】${data.title}\n`;
94
- msg += `级别:${data.level}\n`;
95
- msg += `发布单位:${data.org_unit}\n`;
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 += `预报区域:\n`;
98
+ msg += `📍 预报区域:\n`;
98
99
  data.forecasts.slice(0, 5).forEach((f) => {
99
- msg += `- ${f.name || f.areaName}: ${f.grade || f.level}\n`;
100
+ msg += ` • ${f.name || f.areaName}: ${f.grade || f.level}\n`;
100
101
  });
101
102
  if (data.forecasts.length > 5)
102
- msg += `...等${data.forecasts.length}个区域\n`;
103
+ msg += ` ...等${data.forecasts.length}个区域\n`;
103
104
  }
104
105
  return msg;
105
106
  }
106
107
  formatWeather(data) {
107
- let msg = `【气象预警】${data.headline}\n`;
108
- msg += `类型:${data.type}\n`;
109
- msg += `发布时间:${this.formatTime(data.issue_time || data.effective_time)}\n`;
110
- msg += `详情:${data.description}\n`;
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
- // Convert numeric scale back to JMA string (e.g. 5.5 -> 5强)
123
- if (scale === 4.5)
124
- return '5弱';
125
- if (scale === 5.0)
126
- return '5强'; // Wait, 5.0 is 5强 in my logic?
127
- // In p2p.ts: 50 -> 5.0.
128
- // Usually 5- is 5 Lower, 5+ is 5 Upper.
129
- // Let's stick to simple formatting or check logic.
130
- // 5.0 -> 5强, 4.5 -> 5弱.
131
- // 5.5 -> 6弱, 6.0 -> 6强.
132
- if (scale === 4.5)
133
- return '5弱';
134
- if (scale === 5.0)
135
- return '5强';
136
- if (scale === 5.5)
137
- return '6弱';
138
- if (scale === 6.0)
139
- return '6强';
140
- return scale.toString();
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 connectAll;
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 connectWolfx;
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.connectAll();
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
- connectAll() {
39
- if (this.config.data_sources.fan_studio.enabled) {
40
- this.connectFanStudio();
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 (this.config.data_sources.p2p_earthquake.enabled) {
43
- this.connectP2P();
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 (this.config.data_sources.wolfx.enabled) {
46
- this.connectWolfx();
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
- if (this.config.data_sources.global_quake.enabled) {
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 ds = this.config.data_sources;
91
- switch (event.source) {
92
- // Fan Studio
93
- case 'fan_studio_cea': // DataSource.FAN_STUDIO_CEA
94
- return ds.fan_studio.china_earthquake_warning;
95
- case 'fan_studio_cwa': // DataSource.FAN_STUDIO_CWA
96
- return ds.fan_studio.taiwan_cwa_earthquake;
97
- case 'fan_studio_cenc': // DataSource.FAN_STUDIO_CENC
98
- return ds.fan_studio.china_cenc_earthquake;
99
- case 'fan_studio_jma': // DataSource.FAN_STUDIO_JMA
100
- return ds.fan_studio.japan_jma_eew;
101
- case 'fan_studio_usgs': // DataSource.FAN_STUDIO_USGS
102
- return ds.fan_studio.usgs_earthquake;
103
- case 'fan_studio_weather': // DataSource.FAN_STUDIO_WEATHER
104
- return ds.fan_studio.china_weather_alarm;
105
- case 'fan_studio_tsunami': // DataSource.FAN_STUDIO_TSUNAMI
106
- return ds.fan_studio.china_tsunami;
107
- // P2P
108
- case 'p2p_eew': // DataSource.P2P_EEW
109
- return ds.p2p_earthquake.japan_jma_eew;
110
- case 'p2p_earthquake': // DataSource.P2P_EARTHQUAKE
111
- return ds.p2p_earthquake.japan_jma_earthquake;
112
- case 'p2p_tsunami': // DataSource.P2P_TSUNAMI
113
- return ds.p2p_earthquake.japan_jma_tsunami;
114
- // Wolfx
115
- case 'wolfx_jma_eew': // DataSource.WOLFX_JMA_EEW
116
- return ds.wolfx.japan_jma_eew;
117
- case 'wolfx_cenc_eew': // DataSource.WOLFX_CENC_EEW
118
- return ds.wolfx.china_cenc_eew;
119
- case 'wolfx_cwa_eew': // DataSource.WOLFX_CWA_EEW
120
- return ds.wolfx.taiwan_cwa_eew;
121
- case 'wolfx_jma_eq': // DataSource.WOLFX_JMA_EQ
122
- return ds.wolfx.japan_jma_earthquake;
123
- case 'wolfx_cenc_eq': // DataSource.WOLFX_CENC_EQ
124
- return ds.wolfx.china_cenc_earthquake;
125
- // Global Quake
126
- case 'global_quake': // DataSource.GLOBAL_QUAKE
127
- return ds.global_quake.enabled;
128
- default:
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
- connectWolfx() {
147
- const wolfx_sources = {
148
- "japan_jma_eew": "wss://ws-api.wolfx.jp/jma_eew",
149
- "china_cenc_eew": "wss://ws-api.wolfx.jp/cenc_eew",
150
- "taiwan_cwa_eew": "wss://ws-api.wolfx.jp/cwa_eew",
151
- "japan_jma_earthquake": "wss://ws-api.wolfx.jp/jma_eqlist",
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
- "version": "0.0.6",
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": {