@askeleven/sms-guard 0.1.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/LICENSE +21 -0
- package/README.md +267 -0
- package/bin/cli.js +212 -0
- package/package.json +59 -0
- package/src/data/countries.js +78 -0
- package/src/data/gsm.js +53 -0
- package/src/data/nanp.js +271 -0
- package/src/data/shorteners.js +10 -0
- package/src/index.js +420 -0
- package/src/mcp.js +289 -0
- package/src/phone.js +213 -0
- package/src/report.js +165 -0
- package/src/segments.js +139 -0
- package/src/split.js +127 -0
- package/src/version.js +6 -0
- package/types/data/countries.d.ts +2 -0
- package/types/data/gsm.d.ts +17 -0
- package/types/data/nanp.d.ts +17 -0
- package/types/data/shorteners.d.ts +5 -0
- package/types/index.d.ts +240 -0
- package/types/mcp.d.ts +23 -0
- package/types/phone.d.ts +121 -0
- package/types/report.d.ts +22 -0
- package/types/segments.d.ts +115 -0
- package/types/split.d.ts +46 -0
- package/types/version.d.ts +2 -0
package/types/index.d.ts
ADDED
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Checks one text message before it is sent: how many segments it will be, what
|
|
3
|
+
* forces it into UCS-2, whether it lands inside the recipient's allowed hours, and
|
|
4
|
+
* a few things carriers and recipients object to. Runs locally, touches no network.
|
|
5
|
+
*
|
|
6
|
+
* @param {string} text
|
|
7
|
+
* @param {CheckOptions} [options]
|
|
8
|
+
* @returns {Result}
|
|
9
|
+
*/
|
|
10
|
+
export function checkMessage(text: string, options?: CheckOptions): Result;
|
|
11
|
+
/**
|
|
12
|
+
* Shows invisible and whitespace characters by code point, the rest as themselves.
|
|
13
|
+
*
|
|
14
|
+
* @param {Ucs2Character} c
|
|
15
|
+
* @returns {string}
|
|
16
|
+
*/
|
|
17
|
+
export function displayChar(c: Ucs2Character): string;
|
|
18
|
+
export { parseNumber } from "./phone.js";
|
|
19
|
+
/**
|
|
20
|
+
* @typedef {import('./segments.js').Encoding} Encoding
|
|
21
|
+
* @typedef {import('./segments.js').SegmentCount} SegmentCount
|
|
22
|
+
* @typedef {import('./segments.js').Ucs2Character} Ucs2Character
|
|
23
|
+
* @typedef {import('./split.js').Part} Part
|
|
24
|
+
* @typedef {import('./split.js').SplitOptions} SplitOptions
|
|
25
|
+
* @typedef {import('./phone.js').LocalTime} LocalTime
|
|
26
|
+
*
|
|
27
|
+
* @typedef {'keep'|'review'|'fail'} Verdict
|
|
28
|
+
*
|
|
29
|
+
* @typedef {'empty'|'too_many_segments'|'invalid_number'|'quiet_hours'
|
|
30
|
+
* |'timezone_ambiguous'|'timezone_unknown'|'url_shortener'|'no_opt_out'
|
|
31
|
+
* |'ucs2'} ReasonCode
|
|
32
|
+
*
|
|
33
|
+
* @typedef {object} Reason
|
|
34
|
+
* @property {ReasonCode} code Stable, safe to match on.
|
|
35
|
+
* @property {Verdict} verdict What this reason alone would make of the message.
|
|
36
|
+
* @property {string} detail Plain English.
|
|
37
|
+
*
|
|
38
|
+
* @typedef {object} Recipient
|
|
39
|
+
* @property {string} input As given.
|
|
40
|
+
* @property {string | null} number E.164, or null when it could not be read.
|
|
41
|
+
* @property {boolean} assumedNanp Read as +1 because it had no country code and
|
|
42
|
+
* 10 digits (or 11 starting with 1).
|
|
43
|
+
* @property {string | null} region State, province or country, when known.
|
|
44
|
+
* @property {string[]} timezones Every IANA zone the number could be in.
|
|
45
|
+
* @property {string | null} timezone The zone, when there is exactly one.
|
|
46
|
+
* @property {Array<LocalTime & { inWindow: boolean }>} local Local send time in each
|
|
47
|
+
* of the zones.
|
|
48
|
+
* @property {boolean | null} inWindow Whether the send time is inside the window in
|
|
49
|
+
* every zone; null when no zone is known.
|
|
50
|
+
* @property {string | null} nextWindowStart ISO 8601, the first minute after the send
|
|
51
|
+
* time that is inside the window in every zone, when the send time is not.
|
|
52
|
+
*
|
|
53
|
+
* @typedef {object} Facts
|
|
54
|
+
* @property {Encoding} encoding
|
|
55
|
+
* @property {number} characters Unicode code points.
|
|
56
|
+
* @property {number} units Septets for GSM-7, UTF-16 code units for UCS-2.
|
|
57
|
+
* @property {number} segments
|
|
58
|
+
* @property {number} perSegment Units per segment at this length.
|
|
59
|
+
* @property {number} maxSegments
|
|
60
|
+
* @property {Ucs2Character[]} ucs2Characters What forces UCS-2, if anything.
|
|
61
|
+
* @property {string} sendAt ISO 8601.
|
|
62
|
+
* @property {string} window HH:MM-HH:MM, recipient local time.
|
|
63
|
+
* @property {'checked'|'skipped'} timeCheck Whether the send time was checked
|
|
64
|
+
* against the recipient's local time. Skipped with no recipient, or when their
|
|
65
|
+
* time zone is not known.
|
|
66
|
+
* @property {Recipient | null} recipient
|
|
67
|
+
*
|
|
68
|
+
* @typedef {object} Result
|
|
69
|
+
* @property {Verdict} verdict The worst verdict among the reasons; 'keep' with none.
|
|
70
|
+
* @property {Reason[]} reasons
|
|
71
|
+
* @property {Facts} facts
|
|
72
|
+
* @property {string[]} notes What the reader needs to know to trust the result,
|
|
73
|
+
* such as checks that did not run.
|
|
74
|
+
*
|
|
75
|
+
* @typedef {object} CheckOptions
|
|
76
|
+
* @property {string} [to] Recipient phone number, E.164 preferred.
|
|
77
|
+
* @property {Date | string | number} [sendAt] When it will be sent. A string must be
|
|
78
|
+
* ISO 8601 with an offset or Z. Default now.
|
|
79
|
+
* @property {number} [maxSegments] Fail above this many segments. Default 5.
|
|
80
|
+
* @property {string} [window] Allowed local send time, HH:MM-HH:MM, start inclusive,
|
|
81
|
+
* end exclusive. Default 08:00-21:00.
|
|
82
|
+
* @property {boolean} [firstMessage] The first text to this person: look for opt-out
|
|
83
|
+
* wording. Default false.
|
|
84
|
+
* @property {Date | (() => Date)} [now] The clock, for tests.
|
|
85
|
+
*/
|
|
86
|
+
export const DEFAULT_WINDOW: "08:00-21:00";
|
|
87
|
+
export class GuardError extends Error {
|
|
88
|
+
/**
|
|
89
|
+
* @param {'invalid_option'} code
|
|
90
|
+
* @param {string} message
|
|
91
|
+
*/
|
|
92
|
+
constructor(code: "invalid_option", message: string);
|
|
93
|
+
code: "invalid_option";
|
|
94
|
+
}
|
|
95
|
+
export type Encoding = import("./segments.js").Encoding;
|
|
96
|
+
export type SegmentCount = import("./segments.js").SegmentCount;
|
|
97
|
+
export type Ucs2Character = import("./segments.js").Ucs2Character;
|
|
98
|
+
export type Part = import("./split.js").Part;
|
|
99
|
+
export type SplitOptions = import("./split.js").SplitOptions;
|
|
100
|
+
export type LocalTime = import("./phone.js").LocalTime;
|
|
101
|
+
export type Verdict = "keep" | "review" | "fail";
|
|
102
|
+
export type ReasonCode = "empty" | "too_many_segments" | "invalid_number" | "quiet_hours" | "timezone_ambiguous" | "timezone_unknown" | "url_shortener" | "no_opt_out" | "ucs2";
|
|
103
|
+
export type Reason = {
|
|
104
|
+
/**
|
|
105
|
+
* Stable, safe to match on.
|
|
106
|
+
*/
|
|
107
|
+
code: ReasonCode;
|
|
108
|
+
/**
|
|
109
|
+
* What this reason alone would make of the message.
|
|
110
|
+
*/
|
|
111
|
+
verdict: Verdict;
|
|
112
|
+
/**
|
|
113
|
+
* Plain English.
|
|
114
|
+
*/
|
|
115
|
+
detail: string;
|
|
116
|
+
};
|
|
117
|
+
export type Recipient = {
|
|
118
|
+
/**
|
|
119
|
+
* As given.
|
|
120
|
+
*/
|
|
121
|
+
input: string;
|
|
122
|
+
/**
|
|
123
|
+
* E.164, or null when it could not be read.
|
|
124
|
+
*/
|
|
125
|
+
number: string | null;
|
|
126
|
+
/**
|
|
127
|
+
* Read as +1 because it had no country code and
|
|
128
|
+
* 10 digits (or 11 starting with 1).
|
|
129
|
+
*/
|
|
130
|
+
assumedNanp: boolean;
|
|
131
|
+
/**
|
|
132
|
+
* State, province or country, when known.
|
|
133
|
+
*/
|
|
134
|
+
region: string | null;
|
|
135
|
+
/**
|
|
136
|
+
* Every IANA zone the number could be in.
|
|
137
|
+
*/
|
|
138
|
+
timezones: string[];
|
|
139
|
+
/**
|
|
140
|
+
* The zone, when there is exactly one.
|
|
141
|
+
*/
|
|
142
|
+
timezone: string | null;
|
|
143
|
+
/**
|
|
144
|
+
* Local send time in each
|
|
145
|
+
* of the zones.
|
|
146
|
+
*/
|
|
147
|
+
local: Array<LocalTime & {
|
|
148
|
+
inWindow: boolean;
|
|
149
|
+
}>;
|
|
150
|
+
/**
|
|
151
|
+
* Whether the send time is inside the window in
|
|
152
|
+
* every zone; null when no zone is known.
|
|
153
|
+
*/
|
|
154
|
+
inWindow: boolean | null;
|
|
155
|
+
/**
|
|
156
|
+
* ISO 8601, the first minute after the send
|
|
157
|
+
* time that is inside the window in every zone, when the send time is not.
|
|
158
|
+
*/
|
|
159
|
+
nextWindowStart: string | null;
|
|
160
|
+
};
|
|
161
|
+
export type Facts = {
|
|
162
|
+
encoding: Encoding;
|
|
163
|
+
/**
|
|
164
|
+
* Unicode code points.
|
|
165
|
+
*/
|
|
166
|
+
characters: number;
|
|
167
|
+
/**
|
|
168
|
+
* Septets for GSM-7, UTF-16 code units for UCS-2.
|
|
169
|
+
*/
|
|
170
|
+
units: number;
|
|
171
|
+
segments: number;
|
|
172
|
+
/**
|
|
173
|
+
* Units per segment at this length.
|
|
174
|
+
*/
|
|
175
|
+
perSegment: number;
|
|
176
|
+
maxSegments: number;
|
|
177
|
+
/**
|
|
178
|
+
* What forces UCS-2, if anything.
|
|
179
|
+
*/
|
|
180
|
+
ucs2Characters: Ucs2Character[];
|
|
181
|
+
/**
|
|
182
|
+
* ISO 8601.
|
|
183
|
+
*/
|
|
184
|
+
sendAt: string;
|
|
185
|
+
/**
|
|
186
|
+
* HH:MM-HH:MM, recipient local time.
|
|
187
|
+
*/
|
|
188
|
+
window: string;
|
|
189
|
+
/**
|
|
190
|
+
* Whether the send time was checked
|
|
191
|
+
* against the recipient's local time. Skipped with no recipient, or when their
|
|
192
|
+
* time zone is not known.
|
|
193
|
+
*/
|
|
194
|
+
timeCheck: "checked" | "skipped";
|
|
195
|
+
recipient: Recipient | null;
|
|
196
|
+
};
|
|
197
|
+
export type Result = {
|
|
198
|
+
/**
|
|
199
|
+
* The worst verdict among the reasons; 'keep' with none.
|
|
200
|
+
*/
|
|
201
|
+
verdict: Verdict;
|
|
202
|
+
reasons: Reason[];
|
|
203
|
+
facts: Facts;
|
|
204
|
+
/**
|
|
205
|
+
* What the reader needs to know to trust the result,
|
|
206
|
+
* such as checks that did not run.
|
|
207
|
+
*/
|
|
208
|
+
notes: string[];
|
|
209
|
+
};
|
|
210
|
+
export type CheckOptions = {
|
|
211
|
+
/**
|
|
212
|
+
* Recipient phone number, E.164 preferred.
|
|
213
|
+
*/
|
|
214
|
+
to?: string | undefined;
|
|
215
|
+
/**
|
|
216
|
+
* When it will be sent. A string must be
|
|
217
|
+
* ISO 8601 with an offset or Z. Default now.
|
|
218
|
+
*/
|
|
219
|
+
sendAt?: string | number | Date | undefined;
|
|
220
|
+
/**
|
|
221
|
+
* Fail above this many segments. Default 5.
|
|
222
|
+
*/
|
|
223
|
+
maxSegments?: number | undefined;
|
|
224
|
+
/**
|
|
225
|
+
* Allowed local send time, HH:MM-HH:MM, start inclusive,
|
|
226
|
+
* end exclusive. Default 08:00-21:00.
|
|
227
|
+
*/
|
|
228
|
+
window?: string | undefined;
|
|
229
|
+
/**
|
|
230
|
+
* The first text to this person: look for opt-out
|
|
231
|
+
* wording. Default false.
|
|
232
|
+
*/
|
|
233
|
+
firstMessage?: boolean | undefined;
|
|
234
|
+
/**
|
|
235
|
+
* The clock, for tests.
|
|
236
|
+
*/
|
|
237
|
+
now?: Date | (() => Date) | undefined;
|
|
238
|
+
};
|
|
239
|
+
export { encodingOf, countSegments, ucs2Characters, LIMITS } from "./segments.js";
|
|
240
|
+
export { splitMessage, DEFAULT_MAX_SEGMENTS } from "./split.js";
|
package/types/mcp.d.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {object} ServeOptions
|
|
3
|
+
* @property {NodeJS.ReadableStream} [input]
|
|
4
|
+
* @property {NodeJS.WritableStream} [output]
|
|
5
|
+
* @property {Partial<import('./index.js').CheckOptions>} [checkOptions] Overrides for
|
|
6
|
+
* tests, such as a fixed clock.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Serves until the input stream closes.
|
|
10
|
+
*
|
|
11
|
+
* @param {ServeOptions} [options]
|
|
12
|
+
* @returns {Promise<void>}
|
|
13
|
+
*/
|
|
14
|
+
export function serve(options?: ServeOptions): Promise<void>;
|
|
15
|
+
export type ServeOptions = {
|
|
16
|
+
input?: NodeJS.ReadableStream | undefined;
|
|
17
|
+
output?: NodeJS.WritableStream | undefined;
|
|
18
|
+
/**
|
|
19
|
+
* Overrides for
|
|
20
|
+
* tests, such as a fixed clock.
|
|
21
|
+
*/
|
|
22
|
+
checkOptions?: Partial<import("./index.js").CheckOptions> | undefined;
|
|
23
|
+
};
|
package/types/phone.d.ts
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reads a phone number. Accepts E.164 with or without formatting (+1 (305) 555-0100,
|
|
3
|
+
* +44 20 7946 0000). Without a leading +, only a 10-digit number, or an 11-digit
|
|
4
|
+
* number starting with 1, is accepted, and it is read as NANP (US, Canada and the
|
|
5
|
+
* rest of +1). Anything else without a + is refused rather than guessed at.
|
|
6
|
+
*
|
|
7
|
+
* @param {string} raw
|
|
8
|
+
* @returns {{ ok: true, number: ParsedNumber } | { ok: false, error: string }}
|
|
9
|
+
*/
|
|
10
|
+
export function parseNumber(raw: string): {
|
|
11
|
+
ok: true;
|
|
12
|
+
number: ParsedNumber;
|
|
13
|
+
} | {
|
|
14
|
+
ok: false;
|
|
15
|
+
error: string;
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* The time zone, or zones, a number could be in.
|
|
19
|
+
*
|
|
20
|
+
* @param {ParsedNumber} number
|
|
21
|
+
* @returns {ZoneLookup}
|
|
22
|
+
*/
|
|
23
|
+
export function zonesFor(number: ParsedNumber): ZoneLookup;
|
|
24
|
+
/**
|
|
25
|
+
* @typedef {object} Window
|
|
26
|
+
* @property {number} start Minutes after local midnight, inclusive.
|
|
27
|
+
* @property {number} end Minutes after local midnight, exclusive.
|
|
28
|
+
* @property {string} label As given, HH:MM-HH:MM.
|
|
29
|
+
*/
|
|
30
|
+
/**
|
|
31
|
+
* Parses HH:MM-HH:MM. The start is inclusive and the end exclusive, so 08:00-21:00
|
|
32
|
+
* allows 08:00 and 20:59 but not 21:00. A window that wraps midnight (22:00-06:00)
|
|
33
|
+
* is allowed.
|
|
34
|
+
*
|
|
35
|
+
* @param {string} window
|
|
36
|
+
* @returns {Window | null}
|
|
37
|
+
*/
|
|
38
|
+
export function parseWindow(window: string): Window | null;
|
|
39
|
+
/**
|
|
40
|
+
* @param {number} minutes Minutes after local midnight.
|
|
41
|
+
* @param {Window} window
|
|
42
|
+
* @returns {boolean}
|
|
43
|
+
*/
|
|
44
|
+
export function inWindow(minutes: number, window: Window): boolean;
|
|
45
|
+
/**
|
|
46
|
+
* The wall-clock time in a zone, from the runtime's own time zone data.
|
|
47
|
+
*
|
|
48
|
+
* @param {Date} date
|
|
49
|
+
* @param {string} timezone
|
|
50
|
+
* @returns {LocalTime}
|
|
51
|
+
*/
|
|
52
|
+
export function localTime(date: Date, timezone: string): LocalTime;
|
|
53
|
+
/**
|
|
54
|
+
* The first minute at or after `from` when it is inside the window in every one of
|
|
55
|
+
* the zones, or null if there is none in the next two days.
|
|
56
|
+
*
|
|
57
|
+
* @param {Date} from
|
|
58
|
+
* @param {string[]} zones
|
|
59
|
+
* @param {Window} window
|
|
60
|
+
* @returns {Date | null}
|
|
61
|
+
*/
|
|
62
|
+
export function nextWindowStart(from: Date, zones: string[], window: Window): Date | null;
|
|
63
|
+
export type Window = {
|
|
64
|
+
/**
|
|
65
|
+
* Minutes after local midnight, inclusive.
|
|
66
|
+
*/
|
|
67
|
+
start: number;
|
|
68
|
+
/**
|
|
69
|
+
* Minutes after local midnight, exclusive.
|
|
70
|
+
*/
|
|
71
|
+
end: number;
|
|
72
|
+
/**
|
|
73
|
+
* As given, HH:MM-HH:MM.
|
|
74
|
+
*/
|
|
75
|
+
label: string;
|
|
76
|
+
};
|
|
77
|
+
export type ParsedNumber = {
|
|
78
|
+
/**
|
|
79
|
+
* The number as +<digits>.
|
|
80
|
+
*/
|
|
81
|
+
e164: string;
|
|
82
|
+
/**
|
|
83
|
+
* '1' for NANP, otherwise as matched in the country
|
|
84
|
+
* table, or an empty string when no row matches.
|
|
85
|
+
*/
|
|
86
|
+
countryCode: string;
|
|
87
|
+
/**
|
|
88
|
+
* True when the number had no + and was read as a
|
|
89
|
+
* US or Canadian number because it had 10 digits (or 11 starting with 1).
|
|
90
|
+
*/
|
|
91
|
+
assumedNanp: boolean;
|
|
92
|
+
};
|
|
93
|
+
export type ZoneLookup = {
|
|
94
|
+
/**
|
|
95
|
+
* State, province or country, when known.
|
|
96
|
+
*/
|
|
97
|
+
region: string | null;
|
|
98
|
+
/**
|
|
99
|
+
* IANA zones. None means unknown; more than one, ambiguous.
|
|
100
|
+
*/
|
|
101
|
+
zones: string[];
|
|
102
|
+
/**
|
|
103
|
+
* What the zones were worked out from, in plain English.
|
|
104
|
+
*/
|
|
105
|
+
basis: string;
|
|
106
|
+
};
|
|
107
|
+
export type LocalTime = {
|
|
108
|
+
timezone: string;
|
|
109
|
+
/**
|
|
110
|
+
* Local wall-clock time, YYYY-MM-DDTHH:MM.
|
|
111
|
+
*/
|
|
112
|
+
time: string;
|
|
113
|
+
/**
|
|
114
|
+
* Such as "Sun 19:30 EDT".
|
|
115
|
+
*/
|
|
116
|
+
label: string;
|
|
117
|
+
/**
|
|
118
|
+
* Minutes after local midnight.
|
|
119
|
+
*/
|
|
120
|
+
minutes: number;
|
|
121
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @param {Result} result
|
|
3
|
+
* @param {{ colour?: boolean, width?: number }} [options]
|
|
4
|
+
* @returns {string}
|
|
5
|
+
*/
|
|
6
|
+
export function formatText(result: Result, options?: {
|
|
7
|
+
colour?: boolean;
|
|
8
|
+
width?: number;
|
|
9
|
+
}): string;
|
|
10
|
+
/**
|
|
11
|
+
* @param {Part[]} parts
|
|
12
|
+
* @param {{ maxSegments: number, colour?: boolean }} options
|
|
13
|
+
* @returns {string}
|
|
14
|
+
*/
|
|
15
|
+
export function formatSplit(parts: Part[], options: {
|
|
16
|
+
maxSegments: number;
|
|
17
|
+
colour?: boolean;
|
|
18
|
+
}): string;
|
|
19
|
+
export type Result = import("./index.js").Result;
|
|
20
|
+
export type ReasonCode = import("./index.js").ReasonCode;
|
|
21
|
+
export type Verdict = import("./index.js").Verdict;
|
|
22
|
+
export type Part = import("./split.js").Part;
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GSM-7 when every character is in the default alphabet or its extension table,
|
|
3
|
+
* UCS-2 otherwise. One character outside both is enough to switch the whole message.
|
|
4
|
+
*
|
|
5
|
+
* @param {string} text
|
|
6
|
+
* @returns {Encoding}
|
|
7
|
+
*/
|
|
8
|
+
export function encodingOf(text: string): Encoding;
|
|
9
|
+
/**
|
|
10
|
+
* What one character costs in the given encoding.
|
|
11
|
+
*
|
|
12
|
+
* @param {string} char One code point.
|
|
13
|
+
* @param {Encoding} encoding
|
|
14
|
+
* @returns {number}
|
|
15
|
+
*/
|
|
16
|
+
export function unitCost(char: string, encoding: Encoding): number;
|
|
17
|
+
/**
|
|
18
|
+
* Counts segments the way the network does. A GSM-7 escape pair, or a UTF-16
|
|
19
|
+
* surrogate pair, is never cut across two parts: when it does not fit in what is left
|
|
20
|
+
* of a part, the part is sent short and the pair starts the next one.
|
|
21
|
+
*
|
|
22
|
+
* @param {string} text
|
|
23
|
+
* @returns {SegmentCount}
|
|
24
|
+
*/
|
|
25
|
+
export function countSegments(text: string): SegmentCount;
|
|
26
|
+
/**
|
|
27
|
+
* The characters that force a message into UCS-2, each once, in order of first
|
|
28
|
+
* appearance, with a GSM-7 replacement where one reads the same.
|
|
29
|
+
*
|
|
30
|
+
* @param {string} text
|
|
31
|
+
* @returns {Ucs2Character[]}
|
|
32
|
+
*/
|
|
33
|
+
export function ucs2Characters(text: string): Ucs2Character[];
|
|
34
|
+
/**
|
|
35
|
+
* Applies the typographic replacements, and nothing else.
|
|
36
|
+
*
|
|
37
|
+
* @param {string} text
|
|
38
|
+
* @returns {string}
|
|
39
|
+
*/
|
|
40
|
+
export function replaceTypographic(text: string): string;
|
|
41
|
+
/**
|
|
42
|
+
* @param {string} char
|
|
43
|
+
* @returns {string}
|
|
44
|
+
*/
|
|
45
|
+
export function codePointOf(char: string): string;
|
|
46
|
+
/**
|
|
47
|
+
* @typedef {'GSM-7'|'UCS-2'} Encoding
|
|
48
|
+
*
|
|
49
|
+
* @typedef {object} SegmentCount
|
|
50
|
+
* @property {Encoding} encoding
|
|
51
|
+
* @property {number} characters Unicode code points, what a person would count.
|
|
52
|
+
* @property {number} units What the network counts: septets for GSM-7 (an extension
|
|
53
|
+
* character is two), UTF-16 code units for UCS-2 (an emoji is usually two).
|
|
54
|
+
* @property {number} segments Parts the message is sent as. 0 for an empty message.
|
|
55
|
+
* @property {number} perSegment Units that fit in each part: 160 or 70 alone, 153 or
|
|
56
|
+
* 67 once a message is split, since the rest of each part carries the header that
|
|
57
|
+
* joins them back together.
|
|
58
|
+
*
|
|
59
|
+
* @typedef {object} Ucs2Character
|
|
60
|
+
* @property {string} char
|
|
61
|
+
* @property {string} codePoint As U+XXXX.
|
|
62
|
+
* @property {number} count Times it appears.
|
|
63
|
+
* @property {string | null} replacement A GSM-7 equivalent, offered only for
|
|
64
|
+
* typographic characters such as curly quotes, dashes and odd spaces.
|
|
65
|
+
*/
|
|
66
|
+
/** Units per message: [single, per part once concatenated]. */
|
|
67
|
+
export const LIMITS: {
|
|
68
|
+
'GSM-7': {
|
|
69
|
+
single: number;
|
|
70
|
+
part: number;
|
|
71
|
+
};
|
|
72
|
+
'UCS-2': {
|
|
73
|
+
single: number;
|
|
74
|
+
part: number;
|
|
75
|
+
};
|
|
76
|
+
};
|
|
77
|
+
export type Encoding = "GSM-7" | "UCS-2";
|
|
78
|
+
export type SegmentCount = {
|
|
79
|
+
encoding: Encoding;
|
|
80
|
+
/**
|
|
81
|
+
* Unicode code points, what a person would count.
|
|
82
|
+
*/
|
|
83
|
+
characters: number;
|
|
84
|
+
/**
|
|
85
|
+
* What the network counts: septets for GSM-7 (an extension
|
|
86
|
+
* character is two), UTF-16 code units for UCS-2 (an emoji is usually two).
|
|
87
|
+
*/
|
|
88
|
+
units: number;
|
|
89
|
+
/**
|
|
90
|
+
* Parts the message is sent as. 0 for an empty message.
|
|
91
|
+
*/
|
|
92
|
+
segments: number;
|
|
93
|
+
/**
|
|
94
|
+
* Units that fit in each part: 160 or 70 alone, 153 or
|
|
95
|
+
* 67 once a message is split, since the rest of each part carries the header that
|
|
96
|
+
* joins them back together.
|
|
97
|
+
*/
|
|
98
|
+
perSegment: number;
|
|
99
|
+
};
|
|
100
|
+
export type Ucs2Character = {
|
|
101
|
+
char: string;
|
|
102
|
+
/**
|
|
103
|
+
* As U+XXXX.
|
|
104
|
+
*/
|
|
105
|
+
codePoint: string;
|
|
106
|
+
/**
|
|
107
|
+
* Times it appears.
|
|
108
|
+
*/
|
|
109
|
+
count: number;
|
|
110
|
+
/**
|
|
111
|
+
* A GSM-7 equivalent, offered only for
|
|
112
|
+
* typographic characters such as curly quotes, dashes and odd spaces.
|
|
113
|
+
*/
|
|
114
|
+
replacement: string | null;
|
|
115
|
+
};
|
package/types/split.d.ts
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Splits a message into parts that each fit within maxSegments, to be sent in order
|
|
3
|
+
* as separate texts. Breaks at a blank line where it can, then a line, then a
|
|
4
|
+
* sentence, then a word, and only cuts inside a word when a single word is too long.
|
|
5
|
+
* Parts are packed greedily, so there are as few as the breaks allow. Whitespace at
|
|
6
|
+
* each break is dropped. A GSM-7 escape pair or a surrogate pair is never cut, since
|
|
7
|
+
* the text is only ever cut between whole characters.
|
|
8
|
+
*
|
|
9
|
+
* @param {string} text
|
|
10
|
+
* @param {SplitOptions} [options]
|
|
11
|
+
* @returns {Part[]} Empty for a message with nothing but whitespace.
|
|
12
|
+
*/
|
|
13
|
+
export function splitMessage(text: string, options?: SplitOptions): Part[];
|
|
14
|
+
/**
|
|
15
|
+
* @typedef {import('./segments.js').Encoding} Encoding
|
|
16
|
+
*
|
|
17
|
+
* @typedef {object} Part
|
|
18
|
+
* @property {string} text
|
|
19
|
+
* @property {Encoding} encoding Worked out for this part alone: a part with no
|
|
20
|
+
* accented letters can go back to GSM-7.
|
|
21
|
+
* @property {number} characters
|
|
22
|
+
* @property {number} units
|
|
23
|
+
* @property {number} segments
|
|
24
|
+
*
|
|
25
|
+
* @typedef {object} SplitOptions
|
|
26
|
+
* @property {number} [maxSegments] Most segments any one part may use. Default 5.
|
|
27
|
+
*/
|
|
28
|
+
export const DEFAULT_MAX_SEGMENTS: 5;
|
|
29
|
+
export type Encoding = import("./segments.js").Encoding;
|
|
30
|
+
export type Part = {
|
|
31
|
+
text: string;
|
|
32
|
+
/**
|
|
33
|
+
* Worked out for this part alone: a part with no
|
|
34
|
+
* accented letters can go back to GSM-7.
|
|
35
|
+
*/
|
|
36
|
+
encoding: Encoding;
|
|
37
|
+
characters: number;
|
|
38
|
+
units: number;
|
|
39
|
+
segments: number;
|
|
40
|
+
};
|
|
41
|
+
export type SplitOptions = {
|
|
42
|
+
/**
|
|
43
|
+
* Most segments any one part may use. Default 5.
|
|
44
|
+
*/
|
|
45
|
+
maxSegments?: number | undefined;
|
|
46
|
+
};
|