@sayue_ltr/fleq 1.50.1 → 2.0.0
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/CHANGELOG.md +174 -0
- package/README.md +42 -6
- package/dist/config.js +37 -4
- package/dist/dmdata/rest-client.js +58 -3
- package/dist/dmdata/telegram-parser.js +115 -64
- package/dist/dmdata/ws-client.js +49 -18
- package/dist/engine/cli/cli-run.js +88 -3
- package/dist/engine/cli/cli.js +12 -0
- package/dist/engine/eew/eew-tracker.js +41 -15
- package/dist/engine/filter/compile-filter.js +21 -0
- package/dist/engine/filter/compiler.js +188 -0
- package/dist/engine/filter/errors.js +41 -0
- package/dist/engine/filter/field-registry.js +78 -0
- package/dist/engine/filter/index.js +15 -0
- package/dist/engine/filter/parser.js +137 -0
- package/dist/engine/filter/rank-maps.js +34 -0
- package/dist/engine/filter/tokenizer.js +121 -0
- package/dist/engine/filter/type-checker.js +104 -0
- package/dist/engine/filter/types.js +2 -0
- package/dist/engine/filter-template/pipeline-controller.js +73 -0
- package/dist/engine/filter-template/pipeline.js +16 -0
- package/dist/engine/messages/display-callbacks.js +7 -0
- package/dist/engine/messages/message-router.js +114 -182
- package/dist/engine/messages/summary-tracker.js +106 -0
- package/dist/engine/messages/telegram-stats.js +103 -0
- package/dist/engine/messages/volcano-route-handler.js +122 -0
- package/dist/engine/monitor/monitor.js +52 -4
- package/dist/engine/monitor/shutdown.js +1 -0
- package/dist/engine/notification/notifier.js +21 -4
- package/dist/engine/notification/sound-player.js +398 -36
- package/dist/engine/presentation/diff-store.js +158 -0
- package/dist/engine/presentation/diff-types.js +2 -0
- package/dist/engine/presentation/events/from-earthquake.js +53 -0
- package/dist/engine/presentation/events/from-eew.js +72 -0
- package/dist/engine/presentation/events/from-lg-observation.js +58 -0
- package/dist/engine/presentation/events/from-nankai-trough.js +39 -0
- package/dist/engine/presentation/events/from-raw.js +35 -0
- package/dist/engine/presentation/events/from-seismic-text.js +37 -0
- package/dist/engine/presentation/events/from-tsunami.js +51 -0
- package/dist/engine/presentation/events/from-volcano.js +88 -0
- package/dist/engine/presentation/events/to-presentation-event.js +32 -0
- package/dist/engine/presentation/level-helpers.js +118 -0
- package/dist/engine/presentation/processors/process-earthquake.js +36 -0
- package/dist/engine/presentation/processors/process-eew.js +105 -0
- package/dist/engine/presentation/processors/process-lg-observation.js +30 -0
- package/dist/engine/presentation/processors/process-message.js +53 -0
- package/dist/engine/presentation/processors/process-nankai-trough.js +30 -0
- package/dist/engine/presentation/processors/process-raw.js +22 -0
- package/dist/engine/presentation/processors/process-seismic-text.js +30 -0
- package/dist/engine/presentation/processors/process-tsunami.js +42 -0
- package/dist/engine/presentation/processors/process-volcano.js +41 -0
- package/dist/engine/presentation/types.js +2 -0
- package/dist/engine/startup/config-resolver.js +2 -0
- package/dist/engine/template/compile-template.js +18 -0
- package/dist/engine/template/compiler.js +105 -0
- package/dist/engine/template/field-accessor.js +31 -0
- package/dist/engine/template/filters.js +100 -0
- package/dist/engine/template/index.js +5 -0
- package/dist/engine/template/parser.js +185 -0
- package/dist/engine/template/tokenizer.js +96 -0
- package/dist/engine/template/types.js +2 -0
- package/dist/types.js +3 -2
- package/dist/ui/display-adapter.js +60 -0
- package/dist/ui/earthquake-formatter.js +22 -5
- package/dist/ui/eew-formatter.js +25 -10
- package/dist/ui/formatter.js +116 -32
- package/dist/ui/minimap/grid-layout.js +91 -0
- package/dist/ui/minimap/index.js +16 -0
- package/dist/ui/minimap/minimap-renderer.js +277 -0
- package/dist/ui/minimap/pref-mapping.js +82 -0
- package/dist/ui/minimap/types.js +2 -0
- package/dist/ui/night-overlay.js +56 -0
- package/dist/ui/repl-handlers/command-definitions.js +327 -0
- package/dist/ui/repl-handlers/index.js +11 -0
- package/dist/ui/repl-handlers/info-handlers.js +633 -0
- package/dist/ui/repl-handlers/operation-handlers.js +233 -0
- package/dist/ui/repl-handlers/settings-handlers.js +927 -0
- package/dist/ui/repl-handlers/types.js +10 -0
- package/dist/ui/repl.js +81 -1752
- package/dist/ui/statistics-formatter.js +258 -0
- package/dist/ui/status-line.js +80 -0
- package/dist/ui/summary/index.js +5 -0
- package/dist/ui/summary/summary-line.js +18 -0
- package/dist/ui/summary/summary-model.js +31 -0
- package/dist/ui/summary/token-builders.js +317 -0
- package/dist/ui/summary/types.js +2 -0
- package/dist/ui/summary/width-fit.js +41 -0
- package/dist/ui/summary-interval-formatter.js +72 -0
- package/dist/ui/test-samples.js +6 -0
- package/dist/ui/theme.js +43 -5
- package/dist/ui/tip-shuffler.js +81 -0
- package/dist/ui/volcano-formatter.js +15 -13
- package/dist/ui/waiting-tips-eew.js +63 -0
- package/dist/ui/waiting-tips-info-systems.js +81 -0
- package/dist/ui/waiting-tips-seismology.js +97 -0
- package/dist/ui/waiting-tips-tsunami.js +72 -0
- package/dist/ui/waiting-tips-weather.js +189 -0
- package/dist/ui/waiting-tips.js +420 -249
- package/package.json +1 -1
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.TelegramStats = void 0;
|
|
4
|
+
exports.routeToCategory = routeToCategory;
|
|
5
|
+
/** Route → StatsCategory 変換 */
|
|
6
|
+
function routeToCategory(route) {
|
|
7
|
+
switch (route) {
|
|
8
|
+
case "eew": return "eew";
|
|
9
|
+
case "earthquake":
|
|
10
|
+
case "seismicText":
|
|
11
|
+
case "lgObservation": return "earthquake";
|
|
12
|
+
case "tsunami": return "tsunami";
|
|
13
|
+
case "volcano": return "volcano";
|
|
14
|
+
case "nankaiTrough": return "nankaiTrough";
|
|
15
|
+
default: return "other";
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
/** 最大震度 headType → priority マッピング */
|
|
19
|
+
const MAX_INT_PRIORITY = {
|
|
20
|
+
VXSE53: 3,
|
|
21
|
+
VXSE61: 2,
|
|
22
|
+
VXSE51: 1,
|
|
23
|
+
};
|
|
24
|
+
/** Set/Map のサイズ上限 */
|
|
25
|
+
const MAX_EVENT_ENTRIES = 1000;
|
|
26
|
+
/** 上限超過時に削除するエントリ数 (バッチ削除で頻繁な削除を回避) */
|
|
27
|
+
const EVICT_BATCH_SIZE = 100;
|
|
28
|
+
/** Set のサイズ上限を適用する。超過時は挿入順で古い方から削除する。 */
|
|
29
|
+
function evictOldestFromSet(set, maxSize) {
|
|
30
|
+
if (set.size <= maxSize)
|
|
31
|
+
return;
|
|
32
|
+
let toRemove = set.size - maxSize + EVICT_BATCH_SIZE;
|
|
33
|
+
for (const item of set) {
|
|
34
|
+
if (toRemove <= 0)
|
|
35
|
+
break;
|
|
36
|
+
set.delete(item);
|
|
37
|
+
toRemove--;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
/** Map のサイズ上限を適用する。超過時は挿入順で古い方から削除する。 */
|
|
41
|
+
function evictOldestFromMap(map, maxSize) {
|
|
42
|
+
if (map.size <= maxSize)
|
|
43
|
+
return;
|
|
44
|
+
let toRemove = map.size - maxSize + EVICT_BATCH_SIZE;
|
|
45
|
+
for (const key of map.keys()) {
|
|
46
|
+
if (toRemove <= 0)
|
|
47
|
+
break;
|
|
48
|
+
map.delete(key);
|
|
49
|
+
toRemove--;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
/** セッション中の電文受信統計を管理する */
|
|
53
|
+
class TelegramStats {
|
|
54
|
+
startTime;
|
|
55
|
+
countByType = new Map();
|
|
56
|
+
categoryByType = new Map();
|
|
57
|
+
eewEventIds = new Set();
|
|
58
|
+
earthquakeMaxIntByEvent = new Map();
|
|
59
|
+
constructor(startTime) {
|
|
60
|
+
this.startTime = startTime ?? new Date();
|
|
61
|
+
}
|
|
62
|
+
/** headType カウント加算。EEW の場合は eventId を Set に追加 */
|
|
63
|
+
record(rec) {
|
|
64
|
+
this.countByType.set(rec.headType, (this.countByType.get(rec.headType) ?? 0) + 1);
|
|
65
|
+
// headType → category の対応は固定なので初回のみ登録する
|
|
66
|
+
if (!this.categoryByType.has(rec.headType)) {
|
|
67
|
+
this.categoryByType.set(rec.headType, rec.category);
|
|
68
|
+
}
|
|
69
|
+
if (rec.category === "eew" && rec.eventId != null) {
|
|
70
|
+
this.eewEventIds.add(rec.eventId);
|
|
71
|
+
evictOldestFromSet(this.eewEventIds, MAX_EVENT_ENTRIES);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* 地震イベントの代表最大震度を更新する。
|
|
76
|
+
* 認識する headType: VXSE53 (priority 3), VXSE61 (priority 2), VXSE51 (priority 1)。
|
|
77
|
+
* 未知の headType は priority 0 として扱う。
|
|
78
|
+
*/
|
|
79
|
+
updateMaxInt(eventId, maxInt, headType) {
|
|
80
|
+
const priority = MAX_INT_PRIORITY[headType] ?? 0;
|
|
81
|
+
const existing = this.earthquakeMaxIntByEvent.get(eventId);
|
|
82
|
+
if (existing == null || priority >= existing.priority) {
|
|
83
|
+
this.earthquakeMaxIntByEvent.set(eventId, { maxInt, priority });
|
|
84
|
+
evictOldestFromMap(this.earthquakeMaxIntByEvent, MAX_EVENT_ENTRIES);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
/** 表示用の読み取り専用スナップショットを返す */
|
|
88
|
+
getSnapshot() {
|
|
89
|
+
let totalCount = 0;
|
|
90
|
+
for (const count of this.countByType.values()) {
|
|
91
|
+
totalCount += count;
|
|
92
|
+
}
|
|
93
|
+
return {
|
|
94
|
+
startTime: new Date(this.startTime),
|
|
95
|
+
countByType: new Map(this.countByType),
|
|
96
|
+
categoryByType: new Map(this.categoryByType),
|
|
97
|
+
eewEventCount: this.eewEventIds.size,
|
|
98
|
+
earthquakeMaxIntByEvent: new Map([...this.earthquakeMaxIntByEvent.entries()].map(([k, v]) => [k, v.maxInt])),
|
|
99
|
+
totalCount,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
exports.TelegramStats = TelegramStats;
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* 火山電文のルーティング処理を一元管理するハンドラ。
|
|
4
|
+
*
|
|
5
|
+
* 火山は VFVO53 アグリゲータによるバッチ集約があるため、
|
|
6
|
+
* 他ドメインの processMessage() → outcome → display の線形フローとは異なる。
|
|
7
|
+
* このハンドラが火山の パース → キャッシュ → 集約 → 通知 → 表示 を担当する。
|
|
8
|
+
*/
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.VolcanoRouteHandler = void 0;
|
|
11
|
+
const volcano_parser_1 = require("../../dmdata/volcano-parser");
|
|
12
|
+
const volcano_vfvo53_aggregator_1 = require("./volcano-vfvo53-aggregator");
|
|
13
|
+
const volcano_presentation_1 = require("../notification/volcano-presentation");
|
|
14
|
+
const process_volcano_1 = require("../presentation/processors/process-volcano");
|
|
15
|
+
// ── 定数 ──
|
|
16
|
+
const VOLCANO_CACHE_TTL_MS = 10 * 60 * 1000; // 10分
|
|
17
|
+
// ── 本体 ──
|
|
18
|
+
class VolcanoRouteHandler {
|
|
19
|
+
volcanoState;
|
|
20
|
+
notifier;
|
|
21
|
+
runDisplayPipeline;
|
|
22
|
+
display;
|
|
23
|
+
aggregator;
|
|
24
|
+
msgCache = new Map();
|
|
25
|
+
constructor(deps) {
|
|
26
|
+
this.volcanoState = deps.volcanoState;
|
|
27
|
+
this.notifier = deps.notifier;
|
|
28
|
+
this.runDisplayPipeline = deps.runDisplayPipeline;
|
|
29
|
+
this.display = deps.display;
|
|
30
|
+
this.aggregator = new volcano_vfvo53_aggregator_1.VolcanoVfvo53Aggregator((info, opts) => this.emitSingle(info, opts), (batch, opts) => this.emitBatch(batch, opts));
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* 火山電文を処理する。
|
|
34
|
+
* @returns パース成功なら ParsedVolcanoInfo (統計記録用)、失敗なら null。
|
|
35
|
+
*/
|
|
36
|
+
handle(msg) {
|
|
37
|
+
this.pruneMsgCache();
|
|
38
|
+
const volcanoInfo = (0, volcano_parser_1.parseVolcanoTelegram)(msg);
|
|
39
|
+
if (!volcanoInfo)
|
|
40
|
+
return null;
|
|
41
|
+
this.msgCache.set(volcanoInfo.volcanoCode, { msg, cachedAt: Date.now() });
|
|
42
|
+
this.aggregator.handle(volcanoInfo);
|
|
43
|
+
return volcanoInfo;
|
|
44
|
+
}
|
|
45
|
+
/** 保留中の火山バッファを flush してリソースを破棄する */
|
|
46
|
+
flushAndDispose() {
|
|
47
|
+
this.aggregator.flushAndDispose();
|
|
48
|
+
}
|
|
49
|
+
// ── private: emit callbacks ──
|
|
50
|
+
emitSingle(info, opts) {
|
|
51
|
+
const cacheEntry = this.msgCache.get(info.volcanoCode);
|
|
52
|
+
const cachedMsg = cacheEntry?.msg;
|
|
53
|
+
const outcome = cachedMsg
|
|
54
|
+
? (0, process_volcano_1.buildVolcanoOutcome)(cachedMsg, info, this.volcanoState)
|
|
55
|
+
: null;
|
|
56
|
+
const presentation = (0, volcano_presentation_1.resolveVolcanoPresentation)(info, this.volcanoState);
|
|
57
|
+
this.volcanoState.update(info);
|
|
58
|
+
// 通知は filter 非適用
|
|
59
|
+
if (opts?.notify !== false) {
|
|
60
|
+
this.notifier.notifyVolcano(info, presentation);
|
|
61
|
+
}
|
|
62
|
+
// PresentationEvent パイプライン
|
|
63
|
+
if (outcome) {
|
|
64
|
+
this.runDisplayPipeline(outcome, () => this.display?.displayVolcano(info, presentation));
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
// msg キャッシュがない場合はフォールバック表示
|
|
68
|
+
this.display?.displayVolcano(info, presentation);
|
|
69
|
+
}
|
|
70
|
+
this.msgCache.delete(info.volcanoCode);
|
|
71
|
+
}
|
|
72
|
+
emitBatch(batch, opts) {
|
|
73
|
+
const presentation = (0, volcano_presentation_1.resolveVolcanoBatchPresentation)(batch);
|
|
74
|
+
if (opts.notify) {
|
|
75
|
+
this.notifier.notifyVolcanoBatch(batch, presentation);
|
|
76
|
+
}
|
|
77
|
+
const firstItem = batch.items[0];
|
|
78
|
+
const cacheEntry = firstItem ? this.msgCache.get(firstItem.volcanoCode) : undefined;
|
|
79
|
+
const cachedMsg = cacheEntry?.msg;
|
|
80
|
+
if (cachedMsg) {
|
|
81
|
+
const batchOutcome = {
|
|
82
|
+
domain: "volcano",
|
|
83
|
+
msg: cachedMsg,
|
|
84
|
+
headType: cachedMsg.head.type,
|
|
85
|
+
statsCategory: "volcano",
|
|
86
|
+
parsed: batch.items,
|
|
87
|
+
isBatch: true,
|
|
88
|
+
volcanoPresentation: presentation,
|
|
89
|
+
batchReportDateTime: batch.reportDateTime,
|
|
90
|
+
batchIsTest: batch.isTest,
|
|
91
|
+
stats: {
|
|
92
|
+
shouldRecord: false,
|
|
93
|
+
},
|
|
94
|
+
presentation: {
|
|
95
|
+
frameLevel: presentation.frameLevel,
|
|
96
|
+
soundLevel: presentation.soundLevel,
|
|
97
|
+
notifyCategory: "volcano",
|
|
98
|
+
},
|
|
99
|
+
};
|
|
100
|
+
this.runDisplayPipeline(batchOutcome, () => this.display?.displayVolcanoBatch(batch, presentation));
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
this.display?.displayVolcanoBatch(batch, presentation);
|
|
104
|
+
}
|
|
105
|
+
this.cleanupBatchCache(batch);
|
|
106
|
+
}
|
|
107
|
+
// ── private: cache management ──
|
|
108
|
+
pruneMsgCache() {
|
|
109
|
+
const now = Date.now();
|
|
110
|
+
for (const [key, entry] of this.msgCache) {
|
|
111
|
+
if (now - entry.cachedAt > VOLCANO_CACHE_TTL_MS) {
|
|
112
|
+
this.msgCache.delete(key);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
cleanupBatchCache(batch) {
|
|
117
|
+
for (const item of batch.items) {
|
|
118
|
+
this.msgCache.delete(item.volcanoCode);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
exports.VolcanoRouteHandler = VolcanoRouteHandler;
|
|
@@ -47,14 +47,21 @@ const formatter_1 = require("../../ui/formatter");
|
|
|
47
47
|
const repl_coordinator_1 = require("./repl-coordinator");
|
|
48
48
|
const shutdown_1 = require("./shutdown");
|
|
49
49
|
const log = __importStar(require("../../logger"));
|
|
50
|
-
|
|
51
|
-
|
|
50
|
+
const summary_interval_formatter_1 = require("../../ui/summary-interval-formatter");
|
|
51
|
+
const summary_tracker_1 = require("../messages/summary-tracker");
|
|
52
|
+
async function startMonitor(config, pipelineController) {
|
|
53
|
+
// display adapter は遅延ロードで ui 依存を monitor 側に限定する
|
|
54
|
+
const { createDisplayAdapter } = await Promise.resolve().then(() => __importStar(require("../../ui/display-adapter")));
|
|
55
|
+
const display = createDisplayAdapter();
|
|
56
|
+
const pipeline = pipelineController?.getPipeline();
|
|
57
|
+
const { handler: routeMessage, eewLogger, notifier, tsunamiState, volcanoState, stats, summaryTracker, flushAndDisposeVolcanoBuffer } = (0, message_router_1.createMessageHandler)({ pipeline: pipeline ?? undefined, display });
|
|
52
58
|
// EEW ログ設定を反映
|
|
53
59
|
eewLogger.setEnabled(config.eewLog);
|
|
54
60
|
eewLogger.setFields(config.eewLogFields);
|
|
55
61
|
let disconnectedAt = null;
|
|
56
62
|
let isFirstConnection = true;
|
|
57
63
|
let replHandler = null;
|
|
64
|
+
let summaryTimerControl = null;
|
|
58
65
|
const manager = new multi_connection_manager_1.MultiConnectionManager(config, {
|
|
59
66
|
onData: (msg) => {
|
|
60
67
|
(0, repl_coordinator_1.withReplDisplay)(replHandler, () => routeMessage(msg));
|
|
@@ -69,7 +76,7 @@ async function startMonitor(config) {
|
|
|
69
76
|
}
|
|
70
77
|
log.info(chalk_1.default.green("リアルタイム受信中..."));
|
|
71
78
|
if (isFirstConnection) {
|
|
72
|
-
log.info(chalk_1.default.gray("
|
|
79
|
+
log.info(chalk_1.default.gray("commands (短縮: cmds) でコマンド一覧を表示"));
|
|
73
80
|
isFirstConnection = false;
|
|
74
81
|
}
|
|
75
82
|
(0, repl_coordinator_1.updateReplConnectionState)(replHandler, true);
|
|
@@ -88,11 +95,15 @@ async function startMonitor(config) {
|
|
|
88
95
|
getReplHandler: () => replHandler,
|
|
89
96
|
resetTerminalTitle: cli_run_1.resetTerminalTitle,
|
|
90
97
|
flushAndDisposeVolcanoBuffer,
|
|
98
|
+
stopSummaryTimer: () => summaryTimerControl?.stop(),
|
|
91
99
|
});
|
|
92
100
|
// REPL ハンドラ (遅延ロード)
|
|
93
101
|
const { ReplHandler } = await Promise.resolve().then(() => __importStar(require("../../ui/repl")));
|
|
94
|
-
replHandler = new ReplHandler(config, manager, notifier, eewLogger, shutdown, [tsunamiState, volcanoState], [tsunamiState, volcanoState]);
|
|
102
|
+
replHandler = new ReplHandler(config, manager, notifier, eewLogger, shutdown, stats, [tsunamiState, volcanoState], [tsunamiState, volcanoState], pipelineController, summaryTracker);
|
|
95
103
|
(0, shutdown_1.registerShutdownSignals)(shutdown);
|
|
104
|
+
// 定期要約タイマー
|
|
105
|
+
summaryTimerControl = createSummaryTimerControl(config, summaryTracker, () => replHandler);
|
|
106
|
+
replHandler.setSummaryTimerControl(summaryTimerControl);
|
|
96
107
|
// REPL を先に起動 (接続中もコマンド入力可能にする)
|
|
97
108
|
replHandler.start();
|
|
98
109
|
// 起動時: 最新の津波・火山警報状態を復元 (WebSocket 接続前に実行)
|
|
@@ -116,3 +127,40 @@ async function startMonitor(config) {
|
|
|
116
127
|
log.info("retry コマンドで再接続を試みることができます。");
|
|
117
128
|
}
|
|
118
129
|
}
|
|
130
|
+
/** 定期要約タイマーの制御オブジェクトを生成する。初期値が設定済みなら自動起動する。 */
|
|
131
|
+
function createSummaryTimerControl(config, tracker, getReplHandler) {
|
|
132
|
+
let timer = null;
|
|
133
|
+
function showOutput(intervalMinutes) {
|
|
134
|
+
const snapshot = tracker.getSnapshot();
|
|
135
|
+
const output = (0, summary_interval_formatter_1.formatSummaryInterval)(snapshot, intervalMinutes, true);
|
|
136
|
+
(0, repl_coordinator_1.withReplDisplay)(getReplHandler(), () => {
|
|
137
|
+
console.log(output);
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
const control = {
|
|
141
|
+
start(intervalMinutes) {
|
|
142
|
+
// 既存タイマーを停止してから再起動
|
|
143
|
+
control.stop();
|
|
144
|
+
const intervalMs = intervalMinutes * 60_000;
|
|
145
|
+
timer = setInterval(() => showOutput(intervalMinutes), intervalMs);
|
|
146
|
+
timer.unref();
|
|
147
|
+
},
|
|
148
|
+
stop() {
|
|
149
|
+
if (timer != null) {
|
|
150
|
+
clearInterval(timer);
|
|
151
|
+
timer = null;
|
|
152
|
+
}
|
|
153
|
+
},
|
|
154
|
+
isRunning() {
|
|
155
|
+
return timer != null;
|
|
156
|
+
},
|
|
157
|
+
showNow() {
|
|
158
|
+
showOutput(summary_tracker_1.WINDOW_MINUTES);
|
|
159
|
+
},
|
|
160
|
+
};
|
|
161
|
+
// 初期値が設定されていれば自動起動
|
|
162
|
+
if (config.summaryInterval != null) {
|
|
163
|
+
control.start(config.summaryInterval);
|
|
164
|
+
}
|
|
165
|
+
return control;
|
|
166
|
+
}
|
|
@@ -34,6 +34,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.Notifier = exports.NOTIFY_CATEGORY_LABELS = void 0;
|
|
37
|
+
exports.clearIconPathCache = clearIconPathCache;
|
|
37
38
|
exports.resolveIconPath = resolveIconPath;
|
|
38
39
|
const path = __importStar(require("path"));
|
|
39
40
|
const fs = __importStar(require("fs"));
|
|
@@ -55,12 +56,24 @@ const CATEGORY_ICON_PREFIX = {
|
|
|
55
56
|
lgObservation: "lg-observation",
|
|
56
57
|
volcano: "volcano",
|
|
57
58
|
};
|
|
59
|
+
/** resolveIconPath の結果キャッシュ。キー: "{category}:{level|''}" */
|
|
60
|
+
const iconPathCache = new Map();
|
|
61
|
+
/**
|
|
62
|
+
* resolveIconPath のキャッシュをクリアする (テスト用)。
|
|
63
|
+
*/
|
|
64
|
+
function clearIconPathCache() {
|
|
65
|
+
iconPathCache.clear();
|
|
66
|
+
}
|
|
58
67
|
/**
|
|
59
68
|
* カテゴリとレベルからアイコンパスを解決する。
|
|
60
69
|
* 3段フォールバック: {prefix}-{level}.png → {prefix}.png → default.png
|
|
61
|
-
* いずれも見つからなければ undefined
|
|
70
|
+
* いずれも見つからなければ undefined を返す。結果はキャッシュして再利用する。
|
|
62
71
|
*/
|
|
63
72
|
function resolveIconPath(category, level) {
|
|
73
|
+
const cacheKey = `${category}:${level ?? ""}`;
|
|
74
|
+
if (iconPathCache.has(cacheKey)) {
|
|
75
|
+
return iconPathCache.get(cacheKey);
|
|
76
|
+
}
|
|
64
77
|
const prefix = CATEGORY_ICON_PREFIX[category];
|
|
65
78
|
const candidates = [];
|
|
66
79
|
if (level) {
|
|
@@ -70,9 +83,11 @@ function resolveIconPath(category, level) {
|
|
|
70
83
|
candidates.push(path.join(ICONS_DIR, "default.png"));
|
|
71
84
|
for (const candidate of candidates) {
|
|
72
85
|
if (fs.existsSync(candidate)) {
|
|
86
|
+
iconPathCache.set(cacheKey, candidate);
|
|
73
87
|
return candidate;
|
|
74
88
|
}
|
|
75
89
|
}
|
|
90
|
+
iconPathCache.set(cacheKey, undefined);
|
|
76
91
|
return undefined;
|
|
77
92
|
}
|
|
78
93
|
/** 通知アプリ名 */
|
|
@@ -158,10 +173,12 @@ class Notifier {
|
|
|
158
173
|
notifyEew(info, result) {
|
|
159
174
|
if (!this.settings.eew)
|
|
160
175
|
return;
|
|
161
|
-
//
|
|
162
|
-
|
|
176
|
+
// 抑制された報は通知しない
|
|
177
|
+
if (result.isSuppressed)
|
|
178
|
+
return;
|
|
179
|
+
// 通知条件: 第1報 / 警報昇格 / 取消報 / 最終報
|
|
163
180
|
const isFinal = info.nextAdvisory != null;
|
|
164
|
-
if (!result.isNew && !isUpgradeToWarning && !result.isCancelled && !isFinal) {
|
|
181
|
+
if (!result.isNew && !result.isUpgradeToWarning && !result.isCancelled && !isFinal) {
|
|
165
182
|
return;
|
|
166
183
|
}
|
|
167
184
|
if (result.isCancelled) {
|