@zhin.js/plugin-earthquake 0.0.0-stage → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/README.md +115 -2
- package/commands//345/234/260/351/234/207/index.js +25 -0
- package/commands//345/234/260/351/234/207//350/256/242/351/230/205/index.js +51 -0
- package/commands//345/234/260/351/234/207//350/256/242/351/230/205/347/212/266/346/200/201/index.js +41 -0
- package/commands//345/234/260/351/234/207//351/200/200/350/256/242/index.js +19 -0
- package/lib/config.d.ts +58 -0
- package/lib/config.d.ts.map +1 -0
- package/lib/config.js +138 -0
- package/lib/config.js.map +1 -0
- package/lib/conversation.d.ts +14 -0
- package/lib/conversation.d.ts.map +1 -0
- package/lib/conversation.js +54 -0
- package/lib/conversation.js.map +1 -0
- package/lib/dedupe.d.ts +60 -0
- package/lib/dedupe.d.ts.map +1 -0
- package/lib/dedupe.js +122 -0
- package/lib/dedupe.js.map +1 -0
- package/lib/filters.d.ts +26 -0
- package/lib/filters.d.ts.map +1 -0
- package/lib/filters.js +47 -0
- package/lib/filters.js.map +1 -0
- package/lib/format.d.ts +14 -0
- package/lib/format.d.ts.map +1 -0
- package/lib/format.js +66 -0
- package/lib/format.js.map +1 -0
- package/lib/http.d.ts +71 -0
- package/lib/http.d.ts.map +1 -0
- package/lib/http.js +138 -0
- package/lib/http.js.map +1 -0
- package/lib/index.d.ts +22 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +14 -0
- package/lib/index.js.map +1 -0
- package/lib/permission.d.ts +11 -0
- package/lib/permission.d.ts.map +1 -0
- package/lib/permission.js +11 -0
- package/lib/permission.js.map +1 -0
- package/lib/query-text.d.ts +11 -0
- package/lib/query-text.d.ts.map +1 -0
- package/lib/query-text.js +37 -0
- package/lib/query-text.js.map +1 -0
- package/lib/rate-limit.d.ts +17 -0
- package/lib/rate-limit.d.ts.map +1 -0
- package/lib/rate-limit.js +41 -0
- package/lib/rate-limit.js.map +1 -0
- package/lib/runtime.d.ts +4 -0
- package/lib/runtime.d.ts.map +1 -0
- package/lib/runtime.js +4 -0
- package/lib/runtime.js.map +1 -0
- package/lib/service.d.ts +77 -0
- package/lib/service.d.ts.map +1 -0
- package/lib/service.js +229 -0
- package/lib/service.js.map +1 -0
- package/lib/sources/cenc.d.ts +37 -0
- package/lib/sources/cenc.d.ts.map +1 -0
- package/lib/sources/cenc.js +110 -0
- package/lib/sources/cenc.js.map +1 -0
- package/lib/sources/feed-source.d.ts +51 -0
- package/lib/sources/feed-source.d.ts.map +1 -0
- package/lib/sources/feed-source.js +115 -0
- package/lib/sources/feed-source.js.map +1 -0
- package/lib/sources/parse-helpers.d.ts +10 -0
- package/lib/sources/parse-helpers.d.ts.map +1 -0
- package/lib/sources/parse-helpers.js +36 -0
- package/lib/sources/parse-helpers.js.map +1 -0
- package/lib/sources/usgs.d.ts +14 -0
- package/lib/sources/usgs.d.ts.map +1 -0
- package/lib/sources/usgs.js +81 -0
- package/lib/sources/usgs.js.map +1 -0
- package/lib/subscribe-args.d.ts +9 -0
- package/lib/subscribe-args.d.ts.map +1 -0
- package/lib/subscribe-args.js +40 -0
- package/lib/subscribe-args.js.map +1 -0
- package/lib/subscriptions.d.ts +36 -0
- package/lib/subscriptions.d.ts.map +1 -0
- package/lib/subscriptions.js +122 -0
- package/lib/subscriptions.js.map +1 -0
- package/lib/types.d.ts +36 -0
- package/lib/types.d.ts.map +1 -0
- package/lib/types.js +2 -0
- package/lib/types.js.map +1 -0
- package/package.json +105 -3
- package/plugin.js +71 -0
- package/schema.json +153 -0
- package/skills/earthquake/SKILL.md +37 -0
- package/skills/earthquake/tools/recent_earthquakes/index.js +19 -0
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One upstream feed with its own cache and health bookkeeping.
|
|
3
|
+
*
|
|
4
|
+
* - Mirrors: `urls` are tried in order each round; the first one that answers
|
|
5
|
+
* with a parseable body wins. A failing primary falls back to the next mirror.
|
|
6
|
+
* - Conditional GET: unchanged feeds answer 304 and the cached events are reused.
|
|
7
|
+
* - Cooldown: after a failed round the source is skipped for a growing period
|
|
8
|
+
* (30 s, 60 s, 120 s ... capped at 10 min), or longer when the server sent a
|
|
9
|
+
* Retry-After. Other sources keep serving in the meantime.
|
|
10
|
+
* - A failed round never throws past `fetchEvents`' caller contract: it rejects
|
|
11
|
+
* with a readable Error, and the cache from the last good round is kept.
|
|
12
|
+
*/
|
|
13
|
+
import type { EarthquakeSourceId } from '../config.js';
|
|
14
|
+
import { type HttpDependencies, type RetryPolicy } from '../http.js';
|
|
15
|
+
import type { SourceEarthquake } from '../types.js';
|
|
16
|
+
export declare const SOURCE_COOLDOWN_BASE_MS = 30000;
|
|
17
|
+
export declare const SOURCE_COOLDOWN_MAX_MS: number;
|
|
18
|
+
/** Longest Retry-After we will honour as a cooldown, to recover from bogus headers. */
|
|
19
|
+
export declare const SOURCE_RETRY_AFTER_CAP_MS: number;
|
|
20
|
+
export interface FeedSourceOptions {
|
|
21
|
+
readonly id: EarthquakeSourceId;
|
|
22
|
+
readonly label: string;
|
|
23
|
+
/** Primary URL first, then mirrors. At least one entry. */
|
|
24
|
+
readonly urls: readonly string[];
|
|
25
|
+
readonly parse: (body: string) => SourceEarthquake[];
|
|
26
|
+
readonly policy: RetryPolicy;
|
|
27
|
+
readonly deps?: HttpDependencies;
|
|
28
|
+
}
|
|
29
|
+
export interface FeedSourceHealth {
|
|
30
|
+
readonly id: EarthquakeSourceId;
|
|
31
|
+
readonly label: string;
|
|
32
|
+
readonly consecutiveFailures: number;
|
|
33
|
+
/** Epoch ms until which the source is skipped; 0 when healthy. */
|
|
34
|
+
readonly coolingDownUntil: number;
|
|
35
|
+
readonly lastError?: string;
|
|
36
|
+
readonly lastSuccessAt?: number;
|
|
37
|
+
/** URL that served the last good round. */
|
|
38
|
+
readonly activeUrl?: string;
|
|
39
|
+
}
|
|
40
|
+
export declare class FeedSource {
|
|
41
|
+
#private;
|
|
42
|
+
readonly id: EarthquakeSourceId;
|
|
43
|
+
readonly label: string;
|
|
44
|
+
constructor(options: FeedSourceOptions);
|
|
45
|
+
isCoolingDown(): boolean;
|
|
46
|
+
health(): FeedSourceHealth;
|
|
47
|
+
/** Events from the last good round, if any; used as a stale fallback. */
|
|
48
|
+
cachedEvents(): readonly SourceEarthquake[] | undefined;
|
|
49
|
+
fetchEvents(): Promise<SourceEarthquake[]>;
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=feed-source.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"feed-source.d.ts","sourceRoot":"","sources":["../../src/sources/feed-source.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AACvD,OAAO,EAAkE,KAAK,gBAAgB,EAAE,KAAK,WAAW,EAAE,MAAM,YAAY,CAAC;AACrI,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAEpD,eAAO,MAAM,uBAAuB,QAAS,CAAC;AAC9C,eAAO,MAAM,sBAAsB,QAAc,CAAC;AAClD,uFAAuF;AACvF,eAAO,MAAM,yBAAyB,QAAc,CAAC;AAErD,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,EAAE,EAAE,kBAAkB,CAAC;IAChC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,2DAA2D;IAC3D,QAAQ,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC,QAAQ,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,gBAAgB,EAAE,CAAC;IACrD,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC;IAC7B,QAAQ,CAAC,IAAI,CAAC,EAAE,gBAAgB,CAAC;CAClC;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,EAAE,EAAE,kBAAkB,CAAC;IAChC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,mBAAmB,EAAE,MAAM,CAAC;IACrC,kEAAkE;IAClE,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAClC,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChC,2CAA2C;IAC3C,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED,qBAAa,UAAU;;IACrB,QAAQ,CAAC,EAAE,EAAE,kBAAkB,CAAC;IAChC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;gBAYX,OAAO,EAAE,iBAAiB;IAOtC,aAAa,IAAI,OAAO;IAIxB,MAAM,IAAI,gBAAgB;IAY1B,yEAAyE;IACzE,YAAY,IAAI,SAAS,gBAAgB,EAAE,GAAG,SAAS;IAIjD,WAAW,IAAI,OAAO,CAAC,gBAAgB,EAAE,CAAC;CA6DjD"}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { getTextWithRetry, HttpRequestError } from '../http.js';
|
|
2
|
+
export const SOURCE_COOLDOWN_BASE_MS = 30_000;
|
|
3
|
+
export const SOURCE_COOLDOWN_MAX_MS = 10 * 60_000;
|
|
4
|
+
/** Longest Retry-After we will honour as a cooldown, to recover from bogus headers. */
|
|
5
|
+
export const SOURCE_RETRY_AFTER_CAP_MS = 60 * 60_000;
|
|
6
|
+
export class FeedSource {
|
|
7
|
+
id;
|
|
8
|
+
label;
|
|
9
|
+
#options;
|
|
10
|
+
#now;
|
|
11
|
+
#validators = new Map();
|
|
12
|
+
#bodies = new Map();
|
|
13
|
+
#cachedEvents;
|
|
14
|
+
#activeUrl;
|
|
15
|
+
#consecutiveFailures = 0;
|
|
16
|
+
#coolingDownUntil = 0;
|
|
17
|
+
#lastError;
|
|
18
|
+
#lastSuccessAt;
|
|
19
|
+
constructor(options) {
|
|
20
|
+
this.id = options.id;
|
|
21
|
+
this.label = options.label;
|
|
22
|
+
this.#options = options;
|
|
23
|
+
this.#now = options.deps?.now ?? Date.now;
|
|
24
|
+
}
|
|
25
|
+
isCoolingDown() {
|
|
26
|
+
return this.#now() < this.#coolingDownUntil;
|
|
27
|
+
}
|
|
28
|
+
health() {
|
|
29
|
+
return {
|
|
30
|
+
id: this.id,
|
|
31
|
+
label: this.label,
|
|
32
|
+
consecutiveFailures: this.#consecutiveFailures,
|
|
33
|
+
coolingDownUntil: this.isCoolingDown() ? this.#coolingDownUntil : 0,
|
|
34
|
+
lastError: this.#lastError,
|
|
35
|
+
lastSuccessAt: this.#lastSuccessAt,
|
|
36
|
+
activeUrl: this.#activeUrl,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
/** Events from the last good round, if any; used as a stale fallback. */
|
|
40
|
+
cachedEvents() {
|
|
41
|
+
return this.#cachedEvents;
|
|
42
|
+
}
|
|
43
|
+
async fetchEvents() {
|
|
44
|
+
try {
|
|
45
|
+
const events = await this.#fetchOnce();
|
|
46
|
+
this.#recordSuccess();
|
|
47
|
+
return events;
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
51
|
+
this.#recordFailure(error, message);
|
|
52
|
+
throw new Error(`${this.label}:${message}`, { cause: error });
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
async #fetchOnce() {
|
|
56
|
+
const failures = [];
|
|
57
|
+
for (const url of this.#options.urls) {
|
|
58
|
+
try {
|
|
59
|
+
const events = await this.#fetchUrl(url);
|
|
60
|
+
this.#cachedEvents = events;
|
|
61
|
+
this.#activeUrl = url;
|
|
62
|
+
return events;
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
failures.push(error);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
throw combineFailures(failures, this.#options.urls);
|
|
69
|
+
}
|
|
70
|
+
async #fetchUrl(url) {
|
|
71
|
+
const cached = this.#bodies.get(url);
|
|
72
|
+
const result = await getTextWithRetry(url, this.#options.policy, {
|
|
73
|
+
validators: cached ? this.#validators.get(url) : undefined,
|
|
74
|
+
deps: this.#options.deps,
|
|
75
|
+
});
|
|
76
|
+
if (result.notModified && cached) {
|
|
77
|
+
return cached;
|
|
78
|
+
}
|
|
79
|
+
// Parse before touching the cache so a malformed body keeps the last good data.
|
|
80
|
+
const events = this.#options.parse(result.body);
|
|
81
|
+
this.#bodies.set(url, events);
|
|
82
|
+
this.#validators.set(url, result.validators);
|
|
83
|
+
return events;
|
|
84
|
+
}
|
|
85
|
+
#recordSuccess() {
|
|
86
|
+
this.#consecutiveFailures = 0;
|
|
87
|
+
this.#coolingDownUntil = 0;
|
|
88
|
+
this.#lastError = undefined;
|
|
89
|
+
this.#lastSuccessAt = this.#now();
|
|
90
|
+
}
|
|
91
|
+
#recordFailure(error, message) {
|
|
92
|
+
this.#consecutiveFailures += 1;
|
|
93
|
+
this.#lastError = message;
|
|
94
|
+
const exponent = Math.min(this.#consecutiveFailures - 1, 10);
|
|
95
|
+
let cooldown = Math.min(SOURCE_COOLDOWN_MAX_MS, SOURCE_COOLDOWN_BASE_MS * 2 ** exponent);
|
|
96
|
+
const cause = error instanceof HttpRequestError ? error : undefined;
|
|
97
|
+
if (cause?.retryAfterMs !== undefined) {
|
|
98
|
+
cooldown = Math.max(cooldown, Math.min(cause.retryAfterMs, SOURCE_RETRY_AFTER_CAP_MS));
|
|
99
|
+
}
|
|
100
|
+
this.#coolingDownUntil = this.#now() + cooldown;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
/** One error for a round in which every URL failed; keeps the longest Retry-After. */
|
|
104
|
+
function combineFailures(failures, urls) {
|
|
105
|
+
const messages = failures.map((failure) => (failure instanceof Error ? failure.message : String(failure)));
|
|
106
|
+
const retryAfters = failures
|
|
107
|
+
.map((failure) => (failure instanceof HttpRequestError ? failure.retryAfterMs : undefined))
|
|
108
|
+
.filter((value) => value !== undefined);
|
|
109
|
+
const message = urls.length > 1 ? `全部 ${urls.length} 个地址失败:${messages.join(';')}` : messages[0] ?? '请求失败';
|
|
110
|
+
if (retryAfters.length > 0) {
|
|
111
|
+
return new HttpRequestError(message, { retryable: true, retryAfterMs: Math.max(...retryAfters), cause: failures[0] });
|
|
112
|
+
}
|
|
113
|
+
return new Error(message, { cause: failures[0] });
|
|
114
|
+
}
|
|
115
|
+
//# sourceMappingURL=feed-source.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"feed-source.js","sourceRoot":"","sources":["../../src/sources/feed-source.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** Small, total helpers for tolerant parsing of untrusted JSON. */
|
|
2
|
+
export declare function isRecord(value: unknown): value is Record<string, unknown>;
|
|
3
|
+
/** Accepts numbers and numeric strings; returns undefined for anything else. */
|
|
4
|
+
export declare function toFiniteNumber(value: unknown): number | undefined;
|
|
5
|
+
export declare function toTrimmedString(value: unknown): string | undefined;
|
|
6
|
+
export declare function isValidLatitude(value: number): boolean;
|
|
7
|
+
export declare function isValidLongitude(value: number): boolean;
|
|
8
|
+
/** Magnitudes outside this range are treated as corrupt data. */
|
|
9
|
+
export declare function isPlausibleMagnitude(value: number): boolean;
|
|
10
|
+
//# sourceMappingURL=parse-helpers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parse-helpers.d.ts","sourceRoot":"","sources":["../../src/sources/parse-helpers.ts"],"names":[],"mappings":"AAAA,mEAAmE;AAEnE,wBAAgB,QAAQ,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAEzE;AAED,gFAAgF;AAChF,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CASjE;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CASlE;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAEtD;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAEvD;AAED,iEAAiE;AACjE,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAE3D"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/** Small, total helpers for tolerant parsing of untrusted JSON. */
|
|
2
|
+
export function isRecord(value) {
|
|
3
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
4
|
+
}
|
|
5
|
+
/** Accepts numbers and numeric strings; returns undefined for anything else. */
|
|
6
|
+
export function toFiniteNumber(value) {
|
|
7
|
+
if (typeof value === 'number') {
|
|
8
|
+
return Number.isFinite(value) ? value : undefined;
|
|
9
|
+
}
|
|
10
|
+
if (typeof value === 'string' && value.trim() !== '') {
|
|
11
|
+
const parsed = Number(value);
|
|
12
|
+
return Number.isFinite(parsed) ? parsed : undefined;
|
|
13
|
+
}
|
|
14
|
+
return undefined;
|
|
15
|
+
}
|
|
16
|
+
export function toTrimmedString(value) {
|
|
17
|
+
if (typeof value === 'string') {
|
|
18
|
+
const trimmed = value.trim();
|
|
19
|
+
return trimmed || undefined;
|
|
20
|
+
}
|
|
21
|
+
if (typeof value === 'number' && Number.isFinite(value)) {
|
|
22
|
+
return String(value);
|
|
23
|
+
}
|
|
24
|
+
return undefined;
|
|
25
|
+
}
|
|
26
|
+
export function isValidLatitude(value) {
|
|
27
|
+
return value >= -90 && value <= 90;
|
|
28
|
+
}
|
|
29
|
+
export function isValidLongitude(value) {
|
|
30
|
+
return value >= -180 && value <= 180;
|
|
31
|
+
}
|
|
32
|
+
/** Magnitudes outside this range are treated as corrupt data. */
|
|
33
|
+
export function isPlausibleMagnitude(value) {
|
|
34
|
+
return value >= -2 && value <= 10.5;
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=parse-helpers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parse-helpers.js","sourceRoot":"","sources":["../../src/sources/parse-helpers.ts"],"names":[],"mappings":"AAAA,mEAAmE;AAEnE,MAAM,UAAU,QAAQ,CAAC,KAAc;IACrC,OAAO,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;AAC9E,CAAC;AAED,gFAAgF;AAChF,MAAM,UAAU,cAAc,CAAC,KAAc;IAC3C,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC9B,OAAO,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;IACpD,CAAC;IACD,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;QACrD,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;QAC7B,OAAO,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC;IACtD,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,KAAc;IAC5C,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC9B,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC;QAC7B,OAAO,OAAO,IAAI,SAAS,CAAC;IAC9B,CAAC;IACD,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;QACxD,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC;IACvB,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,KAAa;IAC3C,OAAO,KAAK,IAAI,CAAC,EAAE,IAAI,KAAK,IAAI,EAAE,CAAC;AACrC,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,KAAa;IAC5C,OAAO,KAAK,IAAI,CAAC,GAAG,IAAI,KAAK,IAAI,GAAG,CAAC;AACvC,CAAC;AAED,iEAAiE;AACjE,MAAM,UAAU,oBAAoB,CAAC,KAAa;IAChD,OAAO,KAAK,IAAI,CAAC,CAAC,IAAI,KAAK,IAAI,IAAI,CAAC;AACtC,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* USGS real-time GeoJSON summary feeds.
|
|
3
|
+
* Docs: https://earthquake.usgs.gov/earthquakes/feed/v1.0/geojson.php
|
|
4
|
+
*
|
|
5
|
+
* Only `properties.type === "earthquake"` features are kept (quarry blasts and
|
|
6
|
+
* explosions are dropped). Features with missing or out-of-range fields are
|
|
7
|
+
* skipped individually.
|
|
8
|
+
*/
|
|
9
|
+
import type { UsgsFeed } from '../config.js';
|
|
10
|
+
import type { SourceEarthquake } from '../types.js';
|
|
11
|
+
export declare function usgsFeedUrl(feed: UsgsFeed): string;
|
|
12
|
+
/** Parses a USGS GeoJSON FeatureCollection. Throws only when there is no feature list at all. */
|
|
13
|
+
export declare function parseUsgsFeed(body: string): SourceEarthquake[];
|
|
14
|
+
//# sourceMappingURL=usgs.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"usgs.d.ts","sourceRoot":"","sources":["../../src/sources/usgs.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAC7C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAUpD,wBAAgB,WAAW,CAAC,IAAI,EAAE,QAAQ,GAAG,MAAM,CAElD;AA2CD,iGAAiG;AACjG,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,gBAAgB,EAAE,CAuB9D"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { isPlausibleMagnitude, isRecord, isValidLatitude, isValidLongitude, toFiniteNumber, toTrimmedString, } from './parse-helpers.js';
|
|
2
|
+
export function usgsFeedUrl(feed) {
|
|
3
|
+
return `https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/${feed}.geojson`;
|
|
4
|
+
}
|
|
5
|
+
/** Blasts, explosions and other non-earthquake events are not errors, just irrelevant. */
|
|
6
|
+
function isEarthquakeFeature(feature) {
|
|
7
|
+
if (!isRecord(feature) || !isRecord(feature.properties))
|
|
8
|
+
return true;
|
|
9
|
+
const eventType = toTrimmedString(feature.properties.type);
|
|
10
|
+
return eventType === undefined || eventType === 'earthquake';
|
|
11
|
+
}
|
|
12
|
+
function parseUsgsFeature(feature) {
|
|
13
|
+
if (!isRecord(feature))
|
|
14
|
+
return undefined;
|
|
15
|
+
const properties = feature.properties;
|
|
16
|
+
const geometry = feature.geometry;
|
|
17
|
+
if (!isRecord(properties) || !isRecord(geometry))
|
|
18
|
+
return undefined;
|
|
19
|
+
const eventType = toTrimmedString(properties.type);
|
|
20
|
+
if (eventType !== undefined && eventType !== 'earthquake')
|
|
21
|
+
return undefined;
|
|
22
|
+
const coordinates = geometry.coordinates;
|
|
23
|
+
if (!Array.isArray(coordinates) || coordinates.length < 2)
|
|
24
|
+
return undefined;
|
|
25
|
+
const longitude = toFiniteNumber(coordinates[0]);
|
|
26
|
+
const latitude = toFiniteNumber(coordinates[1]);
|
|
27
|
+
const depth = toFiniteNumber(coordinates[2]);
|
|
28
|
+
const magnitude = toFiniteNumber(properties.mag);
|
|
29
|
+
const time = toFiniteNumber(properties.time);
|
|
30
|
+
const id = toTrimmedString(feature.id) ?? toTrimmedString(properties.code);
|
|
31
|
+
const location = toTrimmedString(properties.place) ?? toTrimmedString(properties.title);
|
|
32
|
+
if (!id || !location || time === undefined)
|
|
33
|
+
return undefined;
|
|
34
|
+
if (latitude === undefined || !isValidLatitude(latitude))
|
|
35
|
+
return undefined;
|
|
36
|
+
if (longitude === undefined || !isValidLongitude(longitude))
|
|
37
|
+
return undefined;
|
|
38
|
+
if (magnitude === undefined || !isPlausibleMagnitude(magnitude))
|
|
39
|
+
return undefined;
|
|
40
|
+
const url = toTrimmedString(properties.url);
|
|
41
|
+
return {
|
|
42
|
+
source: 'usgs',
|
|
43
|
+
id,
|
|
44
|
+
time,
|
|
45
|
+
latitude,
|
|
46
|
+
longitude,
|
|
47
|
+
depthKm: depth !== undefined && depth >= 0 ? depth : undefined,
|
|
48
|
+
magnitude,
|
|
49
|
+
location,
|
|
50
|
+
url: url?.startsWith('https://') ? url : undefined,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
/** Parses a USGS GeoJSON FeatureCollection. Throws only when there is no feature list at all. */
|
|
54
|
+
export function parseUsgsFeed(body) {
|
|
55
|
+
let payload;
|
|
56
|
+
try {
|
|
57
|
+
payload = JSON.parse(body);
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
throw new Error('USGS 返回的不是有效 JSON', { cause: error });
|
|
61
|
+
}
|
|
62
|
+
if (!isRecord(payload) || !Array.isArray(payload.features)) {
|
|
63
|
+
throw new Error('USGS 返回的数据格式无法识别');
|
|
64
|
+
}
|
|
65
|
+
const events = [];
|
|
66
|
+
let earthquakeFeatures = 0;
|
|
67
|
+
for (const feature of payload.features) {
|
|
68
|
+
if (!isEarthquakeFeature(feature))
|
|
69
|
+
continue;
|
|
70
|
+
earthquakeFeatures += 1;
|
|
71
|
+
const event = parseUsgsFeature(feature);
|
|
72
|
+
if (event)
|
|
73
|
+
events.push(event);
|
|
74
|
+
}
|
|
75
|
+
if (earthquakeFeatures > 0 && events.length === 0) {
|
|
76
|
+
// Every earthquake feature is unreadable: the schema probably changed.
|
|
77
|
+
throw new Error('USGS 返回的记录全部无法解析');
|
|
78
|
+
}
|
|
79
|
+
return events;
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=usgs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"usgs.js","sourceRoot":"","sources":["../../src/sources/usgs.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export interface SubscribeArgs {
|
|
2
|
+
readonly minMagnitude?: number;
|
|
3
|
+
readonly regions: readonly string[];
|
|
4
|
+
}
|
|
5
|
+
export declare function parseMagnitudeToken(token: string): number | undefined;
|
|
6
|
+
export declare function parseSubscribeArgs(tokens: readonly string[]): SubscribeArgs | {
|
|
7
|
+
readonly error: string;
|
|
8
|
+
};
|
|
9
|
+
//# sourceMappingURL=subscribe-args.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"subscribe-args.d.ts","sourceRoot":"","sources":["../src/subscribe-args.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;CACrC;AAID,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAMrE;AAED,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,GAAG,aAAa,GAAG;IAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAsBxG"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parses `地震 订阅 [最小震级] [地区...]` arguments.
|
|
3
|
+
* The first token may be a magnitude such as `5`, `5.5`, `M5` or `5级`; the rest are regions.
|
|
4
|
+
*/
|
|
5
|
+
import { MAX_REGIONS_PER_SUBSCRIPTION } from './config.js';
|
|
6
|
+
const MAGNITUDE_TOKEN = /^(?:m)?(\d{1,2}(?:\.\d)?)(?:级)?$/iu;
|
|
7
|
+
export function parseMagnitudeToken(token) {
|
|
8
|
+
const match = MAGNITUDE_TOKEN.exec(token.trim());
|
|
9
|
+
if (!match)
|
|
10
|
+
return undefined;
|
|
11
|
+
const value = Number(match[1]);
|
|
12
|
+
if (!Number.isFinite(value) || value < 0 || value > 10)
|
|
13
|
+
return undefined;
|
|
14
|
+
return value;
|
|
15
|
+
}
|
|
16
|
+
export function parseSubscribeArgs(tokens) {
|
|
17
|
+
const cleaned = tokens.map((token) => token.trim()).filter((token) => token.length > 0);
|
|
18
|
+
let minMagnitude;
|
|
19
|
+
const regions = [];
|
|
20
|
+
for (const [index, token] of cleaned.entries()) {
|
|
21
|
+
const magnitude = parseMagnitudeToken(token);
|
|
22
|
+
if (index === 0 && magnitude !== undefined) {
|
|
23
|
+
minMagnitude = magnitude;
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
if (magnitude !== undefined) {
|
|
27
|
+
return { error: '最小震级请写在地区之前,例如:地震 订阅 5 四川 云南' };
|
|
28
|
+
}
|
|
29
|
+
if (token.length > 20) {
|
|
30
|
+
return { error: `地区关键词过长:${token.slice(0, 20)}…` };
|
|
31
|
+
}
|
|
32
|
+
if (!regions.includes(token))
|
|
33
|
+
regions.push(token);
|
|
34
|
+
}
|
|
35
|
+
if (regions.length > MAX_REGIONS_PER_SUBSCRIPTION) {
|
|
36
|
+
return { error: `最多设置 ${MAX_REGIONS_PER_SUBSCRIPTION} 个地区关键词` };
|
|
37
|
+
}
|
|
38
|
+
return { minMagnitude, regions };
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=subscribe-args.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"subscribe-args.js","sourceRoot":"","sources":["../src/subscribe-args.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,4BAA4B,EAAE,MAAM,aAAa,CAAC;AAO3D,MAAM,eAAe,GAAG,oCAAoC,CAAC;AAE7D,MAAM,UAAU,mBAAmB,CAAC,KAAa;IAC/C,MAAM,KAAK,GAAG,eAAe,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC;IACjD,IAAI,CAAC,KAAK;QAAE,OAAO,SAAS,CAAC;IAC7B,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IAC/B,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,KAAK,GAAG,CAAC,IAAI,KAAK,GAAG,EAAE;QAAE,OAAO,SAAS,CAAC;IACzE,OAAO,KAAK,CAAC;AACf,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,MAAyB;IAC1D,MAAM,OAAO,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IACxF,IAAI,YAAgC,CAAC;IACrC,MAAM,OAAO,GAAa,EAAE,CAAC;IAC7B,KAAK,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,IAAI,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC;QAC/C,MAAM,SAAS,GAAG,mBAAmB,CAAC,KAAK,CAAC,CAAC;QAC7C,IAAI,KAAK,KAAK,CAAC,IAAI,SAAS,KAAK,SAAS,EAAE,CAAC;YAC3C,YAAY,GAAG,SAAS,CAAC;YACzB,SAAS;QACX,CAAC;QACD,IAAI,SAAS,KAAK,SAAS,EAAE,CAAC;YAC5B,OAAO,EAAE,KAAK,EAAE,8BAA8B,EAAE,CAAC;QACnD,CAAC;QACD,IAAI,KAAK,CAAC,MAAM,GAAG,EAAE,EAAE,CAAC;YACtB,OAAO,EAAE,KAAK,EAAE,WAAW,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,EAAE,CAAC;QACrD,CAAC;QACD,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC;YAAE,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACpD,CAAC;IACD,IAAI,OAAO,CAAC,MAAM,GAAG,4BAA4B,EAAE,CAAC;QAClD,OAAO,EAAE,KAAK,EAAE,QAAQ,4BAA4B,SAAS,EAAE,CAAC;IAClE,CAAC;IACD,OAAO,EAAE,YAAY,EAAE,OAAO,EAAE,CAAC;AACnC,CAAC"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export type ConversationKind = 'private' | 'group' | 'channel';
|
|
2
|
+
export interface EarthquakeSubscription {
|
|
3
|
+
readonly adapter: string;
|
|
4
|
+
readonly endpointKey: string;
|
|
5
|
+
readonly conversationKind: ConversationKind;
|
|
6
|
+
readonly conversationId: string;
|
|
7
|
+
readonly minMagnitude: number;
|
|
8
|
+
readonly regions: readonly string[];
|
|
9
|
+
readonly createdBy: string;
|
|
10
|
+
readonly createdAt: string;
|
|
11
|
+
}
|
|
12
|
+
/** Minimal key-value surface; `PluginStateStore` from the database host satisfies it. */
|
|
13
|
+
export interface KeyValueState {
|
|
14
|
+
get(key: string): Promise<unknown>;
|
|
15
|
+
set(key: string, value: never): Promise<void>;
|
|
16
|
+
}
|
|
17
|
+
export declare const SUBSCRIPTIONS_STATE_KEY = "subscriptions/v1";
|
|
18
|
+
export declare function subscriptionKey(target: Pick<EarthquakeSubscription, 'adapter' | 'endpointKey' | 'conversationKind' | 'conversationId'>): string;
|
|
19
|
+
export declare function createMemoryState(): KeyValueState;
|
|
20
|
+
export declare class SubscriptionStore {
|
|
21
|
+
#private;
|
|
22
|
+
constructor(state: KeyValueState, options: {
|
|
23
|
+
readonly maxSubscriptions: number;
|
|
24
|
+
});
|
|
25
|
+
list(): Promise<EarthquakeSubscription[]>;
|
|
26
|
+
get(key: string): Promise<EarthquakeSubscription | undefined>;
|
|
27
|
+
/** Creates or replaces this conversation's subscription. */
|
|
28
|
+
upsert(subscription: EarthquakeSubscription): Promise<{
|
|
29
|
+
readonly created: boolean;
|
|
30
|
+
}>;
|
|
31
|
+
remove(key: string): Promise<boolean>;
|
|
32
|
+
}
|
|
33
|
+
export declare class SubscriptionLimitError extends Error {
|
|
34
|
+
constructor(limit: number);
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=subscriptions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"subscriptions.d.ts","sourceRoot":"","sources":["../src/subscriptions.ts"],"names":[],"mappings":"AAUA,MAAM,MAAM,gBAAgB,GAAG,SAAS,GAAG,OAAO,GAAG,SAAS,CAAC;AAE/D,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,gBAAgB,EAAE,gBAAgB,CAAC;IAC5C,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AAED,yFAAyF;AACzF,MAAM,WAAW,aAAa;IAC5B,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACnC,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC/C;AAED,eAAO,MAAM,uBAAuB,qBAAqB,CAAC;AAE1D,wBAAgB,eAAe,CAAC,MAAM,EAAE,IAAI,CAAC,sBAAsB,EAAE,SAAS,GAAG,aAAa,GAAG,kBAAkB,GAAG,gBAAgB,CAAC,GAAG,MAAM,CAE/I;AA6BD,wBAAgB,iBAAiB,IAAI,aAAa,CAUjD;AAED,qBAAa,iBAAiB;;gBAKhB,KAAK,EAAE,aAAa,EAAE,OAAO,EAAE;QAAE,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAA;KAAE;IAK1E,IAAI,IAAI,OAAO,CAAC,sBAAsB,EAAE,CAAC;IAWzC,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,sBAAsB,GAAG,SAAS,CAAC;IAKnE,4DAA4D;IACtD,MAAM,CAAC,YAAY,EAAE,sBAAsB,GAAG,OAAO,CAAC;QAAE,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAA;KAAE,CAAC;IAkBpF,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;CAoB5C;AAED,qBAAa,sBAAuB,SAAQ,KAAK;gBACnC,KAAK,EAAE,MAAM;CAI1B"}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Conversation subscriptions, one per conversation.
|
|
3
|
+
*
|
|
4
|
+
* Persisted as a single JSON document in the plugin's database state when a
|
|
5
|
+
* database is configured, otherwise kept in memory (lost on restart). Mutations
|
|
6
|
+
* are serialised so concurrent commands cannot lose each other's writes, and
|
|
7
|
+
* malformed stored entries are dropped on load instead of breaking the poller.
|
|
8
|
+
*/
|
|
9
|
+
import { isRecord, toFiniteNumber, toTrimmedString } from './sources/parse-helpers.js';
|
|
10
|
+
export const SUBSCRIPTIONS_STATE_KEY = 'subscriptions/v1';
|
|
11
|
+
export function subscriptionKey(target) {
|
|
12
|
+
return [target.adapter, target.endpointKey, target.conversationKind, target.conversationId].join('\u0000');
|
|
13
|
+
}
|
|
14
|
+
function isConversationKind(value) {
|
|
15
|
+
return value === 'private' || value === 'group' || value === 'channel';
|
|
16
|
+
}
|
|
17
|
+
function parseStoredSubscription(value) {
|
|
18
|
+
if (!isRecord(value))
|
|
19
|
+
return undefined;
|
|
20
|
+
const adapter = toTrimmedString(value.adapter);
|
|
21
|
+
const endpointKey = toTrimmedString(value.endpointKey);
|
|
22
|
+
const conversationId = toTrimmedString(value.conversationId);
|
|
23
|
+
const minMagnitude = toFiniteNumber(value.minMagnitude);
|
|
24
|
+
if (!adapter || !endpointKey || !conversationId || minMagnitude === undefined)
|
|
25
|
+
return undefined;
|
|
26
|
+
if (!isConversationKind(value.conversationKind))
|
|
27
|
+
return undefined;
|
|
28
|
+
const regions = Array.isArray(value.regions)
|
|
29
|
+
? value.regions.filter((region) => typeof region === 'string' && region.trim() !== '')
|
|
30
|
+
: [];
|
|
31
|
+
return {
|
|
32
|
+
adapter,
|
|
33
|
+
endpointKey,
|
|
34
|
+
conversationKind: value.conversationKind,
|
|
35
|
+
conversationId,
|
|
36
|
+
minMagnitude,
|
|
37
|
+
regions,
|
|
38
|
+
createdBy: toTrimmedString(value.createdBy) ?? '',
|
|
39
|
+
createdAt: toTrimmedString(value.createdAt) ?? '',
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
export function createMemoryState() {
|
|
43
|
+
const values = new Map();
|
|
44
|
+
return {
|
|
45
|
+
async get(key) {
|
|
46
|
+
return structuredClone(values.get(key));
|
|
47
|
+
},
|
|
48
|
+
async set(key, value) {
|
|
49
|
+
values.set(key, structuredClone(value));
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
export class SubscriptionStore {
|
|
54
|
+
#state;
|
|
55
|
+
#maxSubscriptions;
|
|
56
|
+
#queue = Promise.resolve();
|
|
57
|
+
constructor(state, options) {
|
|
58
|
+
this.#state = state;
|
|
59
|
+
this.#maxSubscriptions = options.maxSubscriptions;
|
|
60
|
+
}
|
|
61
|
+
async list() {
|
|
62
|
+
const stored = await this.#state.get(SUBSCRIPTIONS_STATE_KEY);
|
|
63
|
+
if (!Array.isArray(stored))
|
|
64
|
+
return [];
|
|
65
|
+
const subscriptions = [];
|
|
66
|
+
for (const entry of stored) {
|
|
67
|
+
const parsed = parseStoredSubscription(entry);
|
|
68
|
+
if (parsed)
|
|
69
|
+
subscriptions.push(parsed);
|
|
70
|
+
}
|
|
71
|
+
return subscriptions;
|
|
72
|
+
}
|
|
73
|
+
async get(key) {
|
|
74
|
+
const all = await this.list();
|
|
75
|
+
return all.find((subscription) => subscriptionKey(subscription) === key);
|
|
76
|
+
}
|
|
77
|
+
/** Creates or replaces this conversation's subscription. */
|
|
78
|
+
async upsert(subscription) {
|
|
79
|
+
return this.#serialize(async () => {
|
|
80
|
+
const all = await this.list();
|
|
81
|
+
const key = subscriptionKey(subscription);
|
|
82
|
+
const index = all.findIndex((existing) => subscriptionKey(existing) === key);
|
|
83
|
+
if (index === -1 && all.length >= this.#maxSubscriptions) {
|
|
84
|
+
throw new SubscriptionLimitError(this.#maxSubscriptions);
|
|
85
|
+
}
|
|
86
|
+
if (index === -1) {
|
|
87
|
+
all.push(subscription);
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
all[index] = subscription;
|
|
91
|
+
}
|
|
92
|
+
await this.#write(all);
|
|
93
|
+
return { created: index === -1 };
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
async remove(key) {
|
|
97
|
+
return this.#serialize(async () => {
|
|
98
|
+
const all = await this.list();
|
|
99
|
+
const remaining = all.filter((subscription) => subscriptionKey(subscription) !== key);
|
|
100
|
+
if (remaining.length === all.length)
|
|
101
|
+
return false;
|
|
102
|
+
await this.#write(remaining);
|
|
103
|
+
return true;
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
async #write(subscriptions) {
|
|
107
|
+
const plain = subscriptions.map((subscription) => ({ ...subscription, regions: [...subscription.regions] }));
|
|
108
|
+
await this.#state.set(SUBSCRIPTIONS_STATE_KEY, plain);
|
|
109
|
+
}
|
|
110
|
+
#serialize(operation) {
|
|
111
|
+
const next = this.#queue.then(operation, operation);
|
|
112
|
+
this.#queue = next.catch(() => undefined);
|
|
113
|
+
return next;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
export class SubscriptionLimitError extends Error {
|
|
117
|
+
constructor(limit) {
|
|
118
|
+
super(`订阅总数已达上限(${limit}),请联系管理员调整 maxSubscriptions`);
|
|
119
|
+
this.name = 'SubscriptionLimitError';
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
//# sourceMappingURL=subscriptions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"subscriptions.js","sourceRoot":"","sources":["../src/subscriptions.ts"],"names":[],"mappings":"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"}
|
package/lib/types.d.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { EarthquakeSourceId } from './config.js';
|
|
2
|
+
/** One earthquake as reported by a single source, normalised to SI-ish units. */
|
|
3
|
+
export interface SourceEarthquake {
|
|
4
|
+
readonly source: EarthquakeSourceId;
|
|
5
|
+
/** Source-scoped id, stable across polls. */
|
|
6
|
+
readonly id: string;
|
|
7
|
+
/** Origin time, epoch milliseconds (UTC). */
|
|
8
|
+
readonly time: number;
|
|
9
|
+
readonly latitude: number;
|
|
10
|
+
readonly longitude: number;
|
|
11
|
+
/** Hypocentre depth in kilometres; undefined when the source omits it. */
|
|
12
|
+
readonly depthKm?: number;
|
|
13
|
+
readonly magnitude: number;
|
|
14
|
+
/** Human-readable place as published by the source. */
|
|
15
|
+
readonly location: string;
|
|
16
|
+
readonly url?: string;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* The same physical event after cross-source merging.
|
|
20
|
+
* The primary report comes from the most preferred source that saw the event.
|
|
21
|
+
*/
|
|
22
|
+
export interface EarthquakeEvent {
|
|
23
|
+
readonly primary: SourceEarthquake;
|
|
24
|
+
/** Every report merged into this event, primary first. */
|
|
25
|
+
readonly reports: readonly SourceEarthquake[];
|
|
26
|
+
}
|
|
27
|
+
/** Per-source outcome of one fetch round. */
|
|
28
|
+
export interface SourceOutcome {
|
|
29
|
+
readonly source: EarthquakeSourceId;
|
|
30
|
+
readonly ok: boolean;
|
|
31
|
+
/** True when the source was skipped because it is cooling down after failures. */
|
|
32
|
+
readonly skipped?: boolean;
|
|
33
|
+
readonly error?: string;
|
|
34
|
+
readonly eventCount: number;
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAEtD,iFAAiF;AACjF,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,MAAM,EAAE,kBAAkB,CAAC;IACpC,6CAA6C;IAC7C,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,6CAA6C;IAC7C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,0EAA0E;IAC1E,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,uDAAuD;IACvD,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;CACvB;AAED;;;GAGG;AACH,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,OAAO,EAAE,gBAAgB,CAAC;IACnC,0DAA0D;IAC1D,QAAQ,CAAC,OAAO,EAAE,SAAS,gBAAgB,EAAE,CAAC;CAC/C;AAED,6CAA6C;AAC7C,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,MAAM,EAAE,kBAAkB,CAAC;IACpC,QAAQ,CAAC,EAAE,EAAE,OAAO,CAAC;IACrB,kFAAkF;IAClF,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;CAC7B"}
|
package/lib/types.js
ADDED
package/lib/types.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
|