@parseapi/sdk 1.10.0 → 1.10.2
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/README.md +34 -0
- package/dist/index.cjs +282 -4
- package/dist/index.d.cts +231 -2
- package/dist/index.d.ts +231 -2
- package/dist/index.js +278 -4
- package/dist/offline.cjs +288 -0
- package/dist/offline.d.cts +239 -0
- package/dist/offline.d.ts +239 -0
- package/dist/offline.js +260 -0
- package/package.json +12 -2
package/dist/index.js
CHANGED
|
@@ -1,5 +1,262 @@
|
|
|
1
|
+
// src/emoji-catalog.ts
|
|
2
|
+
var EmojiCatalogError = class extends Error {
|
|
3
|
+
constructor(code, message) {
|
|
4
|
+
super(message);
|
|
5
|
+
this.code = code;
|
|
6
|
+
this.name = "EmojiCatalogError";
|
|
7
|
+
}
|
|
8
|
+
};
|
|
9
|
+
var sources = ["cldr", "github", "slack", "discord"];
|
|
10
|
+
var stripColons = (value) => value.trim().replace(/^:+|:+$/g, "");
|
|
11
|
+
var normalizeName = (value) => stripColons(value).toLowerCase().replace(/[\s_-]+/g, "_");
|
|
12
|
+
var normalizeSearch = (value) => normalizeName(value).normalize("NFD").replace(new RegExp("\\p{M}", "gu"), "").replace(/œ/g, "oe").replace(/æ/g, "ae").replace(/ß/g, "ss");
|
|
13
|
+
var legacyName = (value) => stripColons(value).toLowerCase().replace(/\s+/g, "_");
|
|
14
|
+
var filterKey = (value) => value.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_|_$/g, "");
|
|
15
|
+
var versionCompare = (a, b) => {
|
|
16
|
+
const left = a.split(".").map(Number), right = b.split(".").map(Number);
|
|
17
|
+
return (left[0] ?? 0) - (right[0] ?? 0) || (left[1] ?? 0) - (right[1] ?? 0);
|
|
18
|
+
};
|
|
19
|
+
var invalid = (message) => {
|
|
20
|
+
throw new EmojiCatalogError("validation_error", message);
|
|
21
|
+
};
|
|
22
|
+
var EmojiCatalog = class {
|
|
23
|
+
data;
|
|
24
|
+
byGlyph = /* @__PURE__ */ new Map();
|
|
25
|
+
names = /* @__PURE__ */ new Map();
|
|
26
|
+
aliases = /* @__PURE__ */ new Map();
|
|
27
|
+
sourceAliases = /* @__PURE__ */ new Map();
|
|
28
|
+
encodings = /* @__PURE__ */ new Map();
|
|
29
|
+
searchFacts = /* @__PURE__ */ new Map();
|
|
30
|
+
categoryKeys = /* @__PURE__ */ new Set();
|
|
31
|
+
subcategoryKeys = /* @__PURE__ */ new Set();
|
|
32
|
+
filterCache = /* @__PURE__ */ new Map();
|
|
33
|
+
searchReady = false;
|
|
34
|
+
compareNames;
|
|
35
|
+
segmenter;
|
|
36
|
+
constructor(data) {
|
|
37
|
+
if (!data || !Array.isArray(data.emojis) || !data.forms || !data.shortcodes || !data.legacy || !data.metadata?.source_version) {
|
|
38
|
+
throw new EmojiCatalogError("invalid_catalog", "Use the JSON response from GET /emoji/catalog.");
|
|
39
|
+
}
|
|
40
|
+
this.data = structuredClone(data);
|
|
41
|
+
this.compareNames = new Intl.Collator(this.data.localization?.resolved_locale ?? void 0).compare;
|
|
42
|
+
const add = (map, key, glyph) => {
|
|
43
|
+
if (!key) return;
|
|
44
|
+
if (!map.has(key)) map.set(key, /* @__PURE__ */ new Set());
|
|
45
|
+
map.get(key).add(glyph);
|
|
46
|
+
};
|
|
47
|
+
for (const row of this.data.emojis) {
|
|
48
|
+
if (!row?.emoji || !row.deep?.version || !Array.isArray(row.deep.keywords)) {
|
|
49
|
+
throw new EmojiCatalogError("invalid_catalog", "Offline catalogs require the default deep=true export.");
|
|
50
|
+
}
|
|
51
|
+
this.byGlyph.set(row.emoji, row);
|
|
52
|
+
add(this.names, normalizeName(row.name), row.emoji);
|
|
53
|
+
for (const alias of row.shortcodes) add(this.aliases, normalizeName(alias), row.emoji);
|
|
54
|
+
this.categoryKeys.add(filterKey(row.category ?? ""));
|
|
55
|
+
this.subcategoryKeys.add(filterKey(row.deep.subcategory ?? ""));
|
|
56
|
+
}
|
|
57
|
+
for (const source of sources) {
|
|
58
|
+
if (!this.data.shortcodes[source]) throw new EmojiCatalogError("invalid_catalog", `Catalog is missing the ${source} shortcode map.`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/** Build once on first search; shared normalized strings avoid repeated annotation copies. */
|
|
62
|
+
ensureSearch() {
|
|
63
|
+
if (this.searchReady) return;
|
|
64
|
+
const cache = /* @__PURE__ */ new Map(), normal = (value) => {
|
|
65
|
+
let result = cache.get(value);
|
|
66
|
+
if (result === void 0) {
|
|
67
|
+
result = normalizeSearch(value);
|
|
68
|
+
cache.set(value, result);
|
|
69
|
+
}
|
|
70
|
+
return result;
|
|
71
|
+
};
|
|
72
|
+
for (const source of sources) for (const [alias, glyphs] of Object.entries(this.data.shortcodes[source])) {
|
|
73
|
+
const value = normal(alias);
|
|
74
|
+
for (const glyph of glyphs) if (this.byGlyph.has(glyph)) {
|
|
75
|
+
if (!this.sourceAliases.has(glyph)) this.sourceAliases.set(glyph, /* @__PURE__ */ new Set());
|
|
76
|
+
this.sourceAliases.get(glyph).add(value);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
const localizedKeywords = new Set(this.data.search?.localized_keywords ?? []);
|
|
80
|
+
for (const row of this.data.emojis) {
|
|
81
|
+
const native = this.data.search?.native[row.emoji];
|
|
82
|
+
const names = [...new Set([row.name, native?.name].filter((name) => typeof name === "string").map(normal))];
|
|
83
|
+
const primaryShorts = [...new Set(row.shortcodes.map(normal))];
|
|
84
|
+
const shorts = [.../* @__PURE__ */ new Set([...primaryShorts, ...this.sourceAliases.get(row.emoji) ?? []])];
|
|
85
|
+
const keywords = [...new Set([...row.deep?.keywords ?? [], ...native?.keywords ?? []].map(normal))];
|
|
86
|
+
const allNames = [...names, ...shorts];
|
|
87
|
+
this.searchFacts.set(row.emoji, { names, primaryShorts, shorts, keywords, translatedKeywords: this.data.localization?.resolved_locale && localizedKeywords.has(row.emoji) ? (row.deep?.keywords ?? []).map(normal) : [], allNames, blob: [.../* @__PURE__ */ new Set([...allNames, ...keywords])].join(" ") });
|
|
88
|
+
}
|
|
89
|
+
this.sourceAliases.clear();
|
|
90
|
+
this.searchReady = true;
|
|
91
|
+
}
|
|
92
|
+
encoding(source) {
|
|
93
|
+
let map = this.encodings.get(source);
|
|
94
|
+
if (!map) {
|
|
95
|
+
map = /* @__PURE__ */ new Map();
|
|
96
|
+
for (const [alias, glyphs] of Object.entries(this.data.shortcodes[source])) if (glyphs.length === 1 && this.byGlyph.has(glyphs[0]) && !map.has(glyphs[0])) map.set(glyphs[0], `:${stripColons(alias)}:`);
|
|
97
|
+
this.encodings.set(source, map);
|
|
98
|
+
}
|
|
99
|
+
return map;
|
|
100
|
+
}
|
|
101
|
+
/** Evidence for the full server inventory; scope() identifies this downloaded subset. */
|
|
102
|
+
metadata() {
|
|
103
|
+
return structuredClone(this.data.metadata);
|
|
104
|
+
}
|
|
105
|
+
/** Filters, local record count and whether this download contains the full server inventory. */
|
|
106
|
+
scope() {
|
|
107
|
+
return structuredClone({ ...this.data.scope, filters: this.data.scope?.filters ?? {}, total: this.data.total, complete_inventory: this.data.scope?.complete_inventory ?? this.data.total === this.data.metadata.total });
|
|
108
|
+
}
|
|
109
|
+
lookup(input, options = {}) {
|
|
110
|
+
if (typeof input !== "string" || !input.trim() || input.trim().length > 512) invalid("Emoji input must contain 1\u2013512 characters.");
|
|
111
|
+
const value = input.trim(), source = options.shortcodeSource;
|
|
112
|
+
if (source && !sources.includes(source)) invalid("shortcodeSource must be cldr, github, slack or discord.");
|
|
113
|
+
let glyph = this.byGlyph.has(value) ? value : void 0;
|
|
114
|
+
if (!glyph && !source && Object.hasOwn(this.data.legacy, legacyName(value))) glyph = this.data.legacy[legacyName(value)];
|
|
115
|
+
if (!glyph) {
|
|
116
|
+
let form = value;
|
|
117
|
+
if (/^(?:U\+)?[0-9A-F]{4,6}(?:[\s,-]+(?:U\+)?[0-9A-F]{4,6})*$/i.test(value)) {
|
|
118
|
+
const points = value.split(/[\s,-]+/).map((part) => Number.parseInt(part.replace(/^U\+/i, ""), 16));
|
|
119
|
+
if (points.length > 32 || points.some((cp) => cp > 1114111 || cp >= 55296 && cp <= 57343)) invalid("Unicode codepoints must be valid scalar values.");
|
|
120
|
+
form = String.fromCodePoint(...points);
|
|
121
|
+
} else if (/^U\+/i.test(value)) invalid("Use Unicode codepoints such as U+1F680 or U+2764 U+FE0F.");
|
|
122
|
+
const canonical = Object.hasOwn(this.data.forms, form) ? this.data.forms[form] : form;
|
|
123
|
+
if (canonical && this.byGlyph.has(canonical)) glyph = canonical;
|
|
124
|
+
}
|
|
125
|
+
if (!glyph) {
|
|
126
|
+
const candidates = /* @__PURE__ */ new Set();
|
|
127
|
+
for (const key of /* @__PURE__ */ new Set([stripColons(value).toLowerCase(), normalizeName(value)])) {
|
|
128
|
+
for (const name of source ? [source] : sources) {
|
|
129
|
+
const dictionary = this.data.shortcodes[name];
|
|
130
|
+
if (Object.hasOwn(dictionary, key)) {
|
|
131
|
+
for (const candidate of dictionary[key] ?? []) if (this.byGlyph.has(candidate)) candidates.add(candidate);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
if (!source) for (const candidate of this.aliases.get(normalizeName(value)) ?? []) candidates.add(candidate);
|
|
136
|
+
if (!source || !candidates.size && !/^:.*:$/.test(value)) for (const candidate of this.names.get(normalizeName(value)) ?? []) candidates.add(candidate);
|
|
137
|
+
if (candidates.size > 1) invalid("Emoji shortcode is ambiguous. Set shortcodeSource to cldr, github, slack or discord.");
|
|
138
|
+
glyph = candidates.values().next().value;
|
|
139
|
+
}
|
|
140
|
+
const row = glyph ? this.byGlyph.get(glyph) : void 0;
|
|
141
|
+
if (!row) throw new EmojiCatalogError("not_found", "Emoji not found in this downloaded catalog.");
|
|
142
|
+
return this.project(row, options.deep === true);
|
|
143
|
+
}
|
|
144
|
+
search(query, options = {}) {
|
|
145
|
+
if (typeof query !== "string" || !query.trim() || query.trim().length > 256) invalid("Search query must contain 1\u2013256 characters.");
|
|
146
|
+
return this.page(query.trim(), options);
|
|
147
|
+
}
|
|
148
|
+
browse(options = {}) {
|
|
149
|
+
const { q, ...result } = this.page("", options);
|
|
150
|
+
return result;
|
|
151
|
+
}
|
|
152
|
+
/** Unknown and ambiguous inputs remain literal; positions refer to untouched input. */
|
|
153
|
+
convert(text, options) {
|
|
154
|
+
if (!options || !["to_shortcodes", "to_emoji"].includes(options.direction)) invalid("Direction must be to_shortcodes or to_emoji.");
|
|
155
|
+
if (!sources.includes(options.shortcodeSource)) invalid("shortcodeSource must be cldr, github, slack or discord.");
|
|
156
|
+
const parsed = this.parse(text), replacements = [], unresolved = [];
|
|
157
|
+
if (options.direction === "to_shortcodes") {
|
|
158
|
+
const encodings = this.encoding(options.shortcodeSource);
|
|
159
|
+
for (const row of parsed.emojis) {
|
|
160
|
+
const shortcode = encodings.get(row.emoji);
|
|
161
|
+
for (const position of row.positions) if (shortcode) replacements.push({ emoji: row.emoji, shortcode, ...position });
|
|
162
|
+
else unresolved.push({ emoji: row.emoji, ...position, reason: "unavailable" });
|
|
163
|
+
}
|
|
164
|
+
} else for (const match of text.matchAll(/:[^:\s]{1,128}:/gu)) {
|
|
165
|
+
const shortcode = match[0], start = match.index, end = start + shortcode.length;
|
|
166
|
+
try {
|
|
167
|
+
const row = this.lookup(shortcode, { shortcodeSource: options.shortcodeSource });
|
|
168
|
+
replacements.push({ emoji: row.emoji, shortcode, start, end });
|
|
169
|
+
} catch (error) {
|
|
170
|
+
if (!(error instanceof EmojiCatalogError)) throw error;
|
|
171
|
+
unresolved.push({ shortcode, start, end, reason: error.code === "not_found" ? "unknown" : "ambiguous" });
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
replacements.sort((a, b) => a.start - b.start);
|
|
175
|
+
unresolved.sort((a, b) => a.start - b.start);
|
|
176
|
+
let cursor = 0;
|
|
177
|
+
const chunks = [];
|
|
178
|
+
for (const row of replacements) {
|
|
179
|
+
chunks.push(text.slice(cursor, row.start), options.direction === "to_shortcodes" ? row.shortcode : row.emoji);
|
|
180
|
+
cursor = row.end;
|
|
181
|
+
}
|
|
182
|
+
chunks.push(text.slice(cursor));
|
|
183
|
+
return { text: chunks.join(""), unresolved };
|
|
184
|
+
}
|
|
185
|
+
/** Whole-grapheme predicate. Use parse() to extract emoji from prose with positions. */
|
|
186
|
+
regex(options = {}) {
|
|
187
|
+
const forms = options.qualifiedOnly ? [...this.byGlyph.keys()] : Object.keys(this.data.forms).filter((form) => this.byGlyph.has(this.data.forms[form]));
|
|
188
|
+
if (options.components) forms.push(...["\u{1F3FB}", "\u{1F3FC}", "\u{1F3FD}", "\u{1F3FE}", "\u{1F3FF}", "\u{1F9B0}", "\u{1F9B1}", "\u{1F9B2}", "\u{1F9B3}"]);
|
|
189
|
+
const escaped = [...new Set(forms)].sort((a, b) => b.length - a.length || (a < b ? -1 : 1)).map((form) => form.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"));
|
|
190
|
+
return new RegExp(`^(?:${escaped.length ? escaped.join("|") : "(?!)"})$`, "u");
|
|
191
|
+
}
|
|
192
|
+
categories() {
|
|
193
|
+
const groups = /* @__PURE__ */ new Map();
|
|
194
|
+
for (const row of this.data.emojis) {
|
|
195
|
+
if (!row.category) continue;
|
|
196
|
+
if (!groups.has(row.category)) groups.set(row.category, /* @__PURE__ */ new Set());
|
|
197
|
+
if (row.deep?.subcategory) groups.get(row.category).add(row.deep.subcategory);
|
|
198
|
+
}
|
|
199
|
+
return { categories: [...groups].map(([category, subcategories]) => ({ category, subcategories: [...subcategories] })) };
|
|
200
|
+
}
|
|
201
|
+
parse(text, options = {}) {
|
|
202
|
+
if (typeof text !== "string" || text.length > 16e3) invalid("Text must be a string of at most 16000 UTF-16 code units.");
|
|
203
|
+
const found = /* @__PURE__ */ new Map();
|
|
204
|
+
this.segmenter ??= new Intl.Segmenter("en", { granularity: "grapheme" });
|
|
205
|
+
for (const { segment, index } of this.segmenter.segment(text)) {
|
|
206
|
+
const canonical = Object.hasOwn(this.data.forms, segment) ? this.data.forms[segment] : segment;
|
|
207
|
+
const row = canonical ? this.byGlyph.get(canonical) : void 0;
|
|
208
|
+
if (!row) continue;
|
|
209
|
+
if (!found.has(row.emoji)) found.set(row.emoji, { ...this.project(row, options.deep === true), positions: [] });
|
|
210
|
+
const result = found.get(row.emoji);
|
|
211
|
+
result.positions.push({ start: index, end: index + segment.length });
|
|
212
|
+
}
|
|
213
|
+
return { emojis: [...found.values()] };
|
|
214
|
+
}
|
|
215
|
+
project(row, deep) {
|
|
216
|
+
const { emoji, name, shortcodes, category } = row;
|
|
217
|
+
return { emoji, name, shortcodes: [...shortcodes], category, ...deep ? { deep: structuredClone(row.deep) } : {} };
|
|
218
|
+
}
|
|
219
|
+
page(query, options) {
|
|
220
|
+
if (options.limit !== void 0 && (!Number.isSafeInteger(options.limit) || options.limit < 1)) invalid("Limit must be a positive integer.");
|
|
221
|
+
const limit = Math.min(options.limit ?? 20, 50), offset = options.offset ?? 0;
|
|
222
|
+
if (!Number.isSafeInteger(offset) || offset < 0 || offset > 1e5) invalid("Offset must be an integer between 0 and 100000.");
|
|
223
|
+
for (const [key, known] of [["category", this.categoryKeys], ["subcategory", this.subcategoryKeys]]) {
|
|
224
|
+
if (options[key] !== void 0 && (!options[key].trim() || !known.has(filterKey(options[key])))) invalid(`Unknown emoji ${key}. Use categories() to find supported values.`);
|
|
225
|
+
}
|
|
226
|
+
for (const key of ["version", "maxVersion"]) if (options[key] !== void 0 && (!/^\d{1,2}\.\d{1,2}$/.test(options[key]) || versionCompare(options[key], this.data.metadata.source_version) > 0)) invalid(`${key} must be an Emoji version no newer than ${this.data.metadata.source_version}.`);
|
|
227
|
+
let rows = this.filtered(options);
|
|
228
|
+
if (query) {
|
|
229
|
+
this.ensureSearch();
|
|
230
|
+
const needle = normalizeSearch(query), words = needle.split("_").filter(Boolean);
|
|
231
|
+
const scored = rows.flatMap((row) => {
|
|
232
|
+
const { names, primaryShorts, shorts, keywords, translatedKeywords, allNames, blob } = this.searchFacts.get(row.emoji);
|
|
233
|
+
if (!blob.includes(needle) && !(words.length > 1 && words.every((word) => blob.includes(word)))) return [];
|
|
234
|
+
const score = primaryShorts.includes(needle) || names.includes(needle) ? 0 : shorts.includes(needle) ? 0.5 : allNames.some((value) => value.startsWith(needle)) ? 1 : translatedKeywords.includes(needle) ? 1.5 : allNames.some((value) => value.includes(needle)) ? 2 : words.length > 1 && allNames.some((value) => words.every((word) => value.includes(word))) ? 2.5 : keywords.some((value) => value.includes(needle)) ? 3 : words.length > 1 && words.every((word) => blob.includes(word)) ? 4 : 100;
|
|
235
|
+
return score === 100 ? [] : [{ row, score }];
|
|
236
|
+
});
|
|
237
|
+
rows = scored.sort((a, b) => a.score - b.score || (this.data.localization?.resolved_locale ? this.compareNames(a.row.name, b.row.name) || a.row.emoji.localeCompare(b.row.emoji) : a.row.name.localeCompare(b.row.name))).map(({ row }) => row);
|
|
238
|
+
}
|
|
239
|
+
const page = rows.slice(offset, offset + limit), paged = !query || ["category", "subcategory", "version", "maxVersion", "includeSkinTones", "offset"].some((key) => options[key] !== void 0);
|
|
240
|
+
return { q: query, emojis: page.map((row) => this.project(row, options.deep === true)), ...paged ? { total: rows.length, next_offset: offset + page.length < rows.length ? offset + page.length : null } : {} };
|
|
241
|
+
}
|
|
242
|
+
filtered(options) {
|
|
243
|
+
for (const [key2, known] of [["category", this.categoryKeys], ["subcategory", this.subcategoryKeys]]) {
|
|
244
|
+
if (options[key2] !== void 0 && (!options[key2].trim() || !known.has(filterKey(options[key2])))) invalid(`Unknown emoji ${key2}. Use categories() to find supported values.`);
|
|
245
|
+
}
|
|
246
|
+
for (const key2 of ["version", "maxVersion"]) if (options[key2] !== void 0 && (!/^\d{1,2}\.\d{1,2}$/.test(options[key2]) || versionCompare(options[key2], this.data.metadata.source_version) > 0)) invalid(`${key2} must be an Emoji version no newer than ${this.data.metadata.source_version}.`);
|
|
247
|
+
if (options.includeSkinTones !== false && options.category === void 0 && options.subcategory === void 0 && options.version === void 0 && options.maxVersion === void 0) return this.data.emojis;
|
|
248
|
+
const key = JSON.stringify([options.includeSkinTones === false, options.category === void 0 ? null : filterKey(options.category), options.subcategory === void 0 ? null : filterKey(options.subcategory), options.version, options.maxVersion]);
|
|
249
|
+
const cached = this.filterCache.get(key);
|
|
250
|
+
if (cached) return cached;
|
|
251
|
+
const rows = this.data.emojis.filter((row) => (options.includeSkinTones !== false || !/[\u{1F3FB}-\u{1F3FF}]/u.test(row.emoji)) && (options.category === void 0 || filterKey(row.category ?? "") === filterKey(options.category)) && (options.subcategory === void 0 || filterKey(row.deep?.subcategory ?? "") === filterKey(options.subcategory)) && (options.version === void 0 || versionCompare(row.deep.version, options.version) === 0) && (options.maxVersion === void 0 || versionCompare(row.deep.version, options.maxVersion) <= 0));
|
|
252
|
+
if (this.filterCache.size >= 32) this.filterCache.delete(this.filterCache.keys().next().value);
|
|
253
|
+
this.filterCache.set(key, rows);
|
|
254
|
+
return rows;
|
|
255
|
+
}
|
|
256
|
+
};
|
|
257
|
+
|
|
1
258
|
// src/index.ts
|
|
2
|
-
var VERSION = "1.10.
|
|
259
|
+
var VERSION = "1.10.2";
|
|
3
260
|
var API_VERSION = "2.0.0";
|
|
4
261
|
var DEFAULT_BASE_URL = "https://api.parseapi.com";
|
|
5
262
|
var DEFAULT_TIMEOUT_MS = 1e4;
|
|
@@ -326,7 +583,12 @@ function parseAPI(apiKey, options = {}) {
|
|
|
326
583
|
},
|
|
327
584
|
/** Parse a measurement or convert it to `to`. Without `to`, use its type's canonical unit. Invalid input is plain data with `valid: false`. */
|
|
328
585
|
measure: Object.assign(
|
|
329
|
-
(measure, opts) =>
|
|
586
|
+
async (measure, opts) => {
|
|
587
|
+
const result = await request(`/measure/${enc(measure)}`, { to: opts?.to, locale: opts?.locale, system: opts?.system }, void 0, opts);
|
|
588
|
+
if (!Object.prototype.hasOwnProperty.call(result, "reason")) Object.defineProperty(result, "reason", { value: null, writable: true, configurable: true });
|
|
589
|
+
if (!Object.prototype.hasOwnProperty.call(result, "choices")) Object.defineProperty(result, "choices", { value: [], writable: true, configurable: true });
|
|
590
|
+
return result;
|
|
591
|
+
},
|
|
330
592
|
{
|
|
331
593
|
/** Discover reviewed units. Pass `unit` to find compatible conversion targets. */
|
|
332
594
|
units: (opts) => request("/measure/units", { q: opts?.query, type: opts?.type, unit: opts?.unit }, void 0, opts)
|
|
@@ -403,13 +665,23 @@ function parseAPI(apiKey, options = {}) {
|
|
|
403
665
|
/** Get current conditions in metric and imperial units. Paid deep adds specialist current measurements, forecasts and related detail. With deep, date selects a past UTC day (YYYY-MM-DD) in deep.history alongside current conditions. Date alone does not request history. */
|
|
404
666
|
weather: (lat, lon, opts) => request("/weather", { lat, lon, date: opts?.date, ...deepQuery(opts) }, void 0, opts),
|
|
405
667
|
emoji: Object.assign(
|
|
406
|
-
(emoji, opts) => request(`/emoji/${enc(emoji)}`, deepQuery(opts), void 0, opts),
|
|
668
|
+
(emoji, opts) => request(`/emoji/${enc(emoji)}`, { shortcode_source: opts?.shortcodeSource, ...deepQuery(opts) }, void 0, opts),
|
|
407
669
|
{
|
|
408
|
-
search: (query, opts) => request("/emoji", { q: query,
|
|
670
|
+
search: (query, opts) => request("/emoji", { q: query, ...emojiFilters(opts), ...deepQuery(opts) }, void 0, opts),
|
|
671
|
+
browse: (opts) => request("/emoji", { ...emojiFilters(opts), ...deepQuery(opts) }, void 0, opts),
|
|
672
|
+
categories: (opts) => request("/emoji/categories", void 0, void 0, requestControls(opts)),
|
|
673
|
+
convert: (text, opts) => request("/emoji/convert", { direction: opts.direction, shortcode_source: opts.shortcodeSource }, void 0, opts, { text }),
|
|
674
|
+
/** Extract complete sequences. Positions use UTF-16 code units; end is exclusive. */
|
|
675
|
+
catalog: (opts) => request("/emoji/catalog", { ...emojiFilters(opts), profile: opts?.profile, deep: opts?.deep ?? true }, void 0, opts),
|
|
676
|
+
metadata: (opts) => request("/emoji/metadata", void 0, void 0, requestControls(opts)),
|
|
677
|
+
parse: (text, opts) => request("/emoji/parse", deepQuery(opts), void 0, opts, { text })
|
|
409
678
|
}
|
|
410
679
|
)
|
|
411
680
|
};
|
|
412
681
|
}
|
|
682
|
+
function emojiFilters(opts) {
|
|
683
|
+
return { limit: opts?.limit, category: opts?.category, subcategory: opts?.subcategory, version: opts?.version, max_version: opts?.maxVersion, include_skin_tones: opts?.includeSkinTones, offset: opts?.offset };
|
|
684
|
+
}
|
|
413
685
|
function timeSource(timezone, options = {}) {
|
|
414
686
|
const values = { ip: options.ip, city: options.city, country: options.country, state: options.state, iata: options.iata, icao: options.icao, unlocode: options.unlocode, address: options.address };
|
|
415
687
|
const primary = [options.ip, options.city, options.iata, options.icao, options.unlocode, options.address].filter((value) => value !== void 0);
|
|
@@ -429,6 +701,8 @@ function timeTargets(targets, to) {
|
|
|
429
701
|
return targets.join(",");
|
|
430
702
|
}
|
|
431
703
|
export {
|
|
704
|
+
EmojiCatalog,
|
|
705
|
+
EmojiCatalogError,
|
|
432
706
|
ParseAPIError,
|
|
433
707
|
parseAPI
|
|
434
708
|
};
|
package/dist/offline.cjs
ADDED
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/offline.ts
|
|
21
|
+
var offline_exports = {};
|
|
22
|
+
__export(offline_exports, {
|
|
23
|
+
EmojiCatalog: () => EmojiCatalog,
|
|
24
|
+
EmojiCatalogError: () => EmojiCatalogError
|
|
25
|
+
});
|
|
26
|
+
module.exports = __toCommonJS(offline_exports);
|
|
27
|
+
|
|
28
|
+
// src/emoji-catalog.ts
|
|
29
|
+
var EmojiCatalogError = class extends Error {
|
|
30
|
+
constructor(code, message) {
|
|
31
|
+
super(message);
|
|
32
|
+
this.code = code;
|
|
33
|
+
this.name = "EmojiCatalogError";
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
var sources = ["cldr", "github", "slack", "discord"];
|
|
37
|
+
var stripColons = (value) => value.trim().replace(/^:+|:+$/g, "");
|
|
38
|
+
var normalizeName = (value) => stripColons(value).toLowerCase().replace(/[\s_-]+/g, "_");
|
|
39
|
+
var normalizeSearch = (value) => normalizeName(value).normalize("NFD").replace(new RegExp("\\p{M}", "gu"), "").replace(/œ/g, "oe").replace(/æ/g, "ae").replace(/ß/g, "ss");
|
|
40
|
+
var legacyName = (value) => stripColons(value).toLowerCase().replace(/\s+/g, "_");
|
|
41
|
+
var filterKey = (value) => value.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_|_$/g, "");
|
|
42
|
+
var versionCompare = (a, b) => {
|
|
43
|
+
const left = a.split(".").map(Number), right = b.split(".").map(Number);
|
|
44
|
+
return (left[0] ?? 0) - (right[0] ?? 0) || (left[1] ?? 0) - (right[1] ?? 0);
|
|
45
|
+
};
|
|
46
|
+
var invalid = (message) => {
|
|
47
|
+
throw new EmojiCatalogError("validation_error", message);
|
|
48
|
+
};
|
|
49
|
+
var EmojiCatalog = class {
|
|
50
|
+
data;
|
|
51
|
+
byGlyph = /* @__PURE__ */ new Map();
|
|
52
|
+
names = /* @__PURE__ */ new Map();
|
|
53
|
+
aliases = /* @__PURE__ */ new Map();
|
|
54
|
+
sourceAliases = /* @__PURE__ */ new Map();
|
|
55
|
+
encodings = /* @__PURE__ */ new Map();
|
|
56
|
+
searchFacts = /* @__PURE__ */ new Map();
|
|
57
|
+
categoryKeys = /* @__PURE__ */ new Set();
|
|
58
|
+
subcategoryKeys = /* @__PURE__ */ new Set();
|
|
59
|
+
filterCache = /* @__PURE__ */ new Map();
|
|
60
|
+
searchReady = false;
|
|
61
|
+
compareNames;
|
|
62
|
+
segmenter;
|
|
63
|
+
constructor(data) {
|
|
64
|
+
if (!data || !Array.isArray(data.emojis) || !data.forms || !data.shortcodes || !data.legacy || !data.metadata?.source_version) {
|
|
65
|
+
throw new EmojiCatalogError("invalid_catalog", "Use the JSON response from GET /emoji/catalog.");
|
|
66
|
+
}
|
|
67
|
+
this.data = structuredClone(data);
|
|
68
|
+
this.compareNames = new Intl.Collator(this.data.localization?.resolved_locale ?? void 0).compare;
|
|
69
|
+
const add = (map, key, glyph) => {
|
|
70
|
+
if (!key) return;
|
|
71
|
+
if (!map.has(key)) map.set(key, /* @__PURE__ */ new Set());
|
|
72
|
+
map.get(key).add(glyph);
|
|
73
|
+
};
|
|
74
|
+
for (const row of this.data.emojis) {
|
|
75
|
+
if (!row?.emoji || !row.deep?.version || !Array.isArray(row.deep.keywords)) {
|
|
76
|
+
throw new EmojiCatalogError("invalid_catalog", "Offline catalogs require the default deep=true export.");
|
|
77
|
+
}
|
|
78
|
+
this.byGlyph.set(row.emoji, row);
|
|
79
|
+
add(this.names, normalizeName(row.name), row.emoji);
|
|
80
|
+
for (const alias of row.shortcodes) add(this.aliases, normalizeName(alias), row.emoji);
|
|
81
|
+
this.categoryKeys.add(filterKey(row.category ?? ""));
|
|
82
|
+
this.subcategoryKeys.add(filterKey(row.deep.subcategory ?? ""));
|
|
83
|
+
}
|
|
84
|
+
for (const source of sources) {
|
|
85
|
+
if (!this.data.shortcodes[source]) throw new EmojiCatalogError("invalid_catalog", `Catalog is missing the ${source} shortcode map.`);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
/** Build once on first search; shared normalized strings avoid repeated annotation copies. */
|
|
89
|
+
ensureSearch() {
|
|
90
|
+
if (this.searchReady) return;
|
|
91
|
+
const cache = /* @__PURE__ */ new Map(), normal = (value) => {
|
|
92
|
+
let result = cache.get(value);
|
|
93
|
+
if (result === void 0) {
|
|
94
|
+
result = normalizeSearch(value);
|
|
95
|
+
cache.set(value, result);
|
|
96
|
+
}
|
|
97
|
+
return result;
|
|
98
|
+
};
|
|
99
|
+
for (const source of sources) for (const [alias, glyphs] of Object.entries(this.data.shortcodes[source])) {
|
|
100
|
+
const value = normal(alias);
|
|
101
|
+
for (const glyph of glyphs) if (this.byGlyph.has(glyph)) {
|
|
102
|
+
if (!this.sourceAliases.has(glyph)) this.sourceAliases.set(glyph, /* @__PURE__ */ new Set());
|
|
103
|
+
this.sourceAliases.get(glyph).add(value);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
const localizedKeywords = new Set(this.data.search?.localized_keywords ?? []);
|
|
107
|
+
for (const row of this.data.emojis) {
|
|
108
|
+
const native = this.data.search?.native[row.emoji];
|
|
109
|
+
const names = [...new Set([row.name, native?.name].filter((name) => typeof name === "string").map(normal))];
|
|
110
|
+
const primaryShorts = [...new Set(row.shortcodes.map(normal))];
|
|
111
|
+
const shorts = [.../* @__PURE__ */ new Set([...primaryShorts, ...this.sourceAliases.get(row.emoji) ?? []])];
|
|
112
|
+
const keywords = [...new Set([...row.deep?.keywords ?? [], ...native?.keywords ?? []].map(normal))];
|
|
113
|
+
const allNames = [...names, ...shorts];
|
|
114
|
+
this.searchFacts.set(row.emoji, { names, primaryShorts, shorts, keywords, translatedKeywords: this.data.localization?.resolved_locale && localizedKeywords.has(row.emoji) ? (row.deep?.keywords ?? []).map(normal) : [], allNames, blob: [.../* @__PURE__ */ new Set([...allNames, ...keywords])].join(" ") });
|
|
115
|
+
}
|
|
116
|
+
this.sourceAliases.clear();
|
|
117
|
+
this.searchReady = true;
|
|
118
|
+
}
|
|
119
|
+
encoding(source) {
|
|
120
|
+
let map = this.encodings.get(source);
|
|
121
|
+
if (!map) {
|
|
122
|
+
map = /* @__PURE__ */ new Map();
|
|
123
|
+
for (const [alias, glyphs] of Object.entries(this.data.shortcodes[source])) if (glyphs.length === 1 && this.byGlyph.has(glyphs[0]) && !map.has(glyphs[0])) map.set(glyphs[0], `:${stripColons(alias)}:`);
|
|
124
|
+
this.encodings.set(source, map);
|
|
125
|
+
}
|
|
126
|
+
return map;
|
|
127
|
+
}
|
|
128
|
+
/** Evidence for the full server inventory; scope() identifies this downloaded subset. */
|
|
129
|
+
metadata() {
|
|
130
|
+
return structuredClone(this.data.metadata);
|
|
131
|
+
}
|
|
132
|
+
/** Filters, local record count and whether this download contains the full server inventory. */
|
|
133
|
+
scope() {
|
|
134
|
+
return structuredClone({ ...this.data.scope, filters: this.data.scope?.filters ?? {}, total: this.data.total, complete_inventory: this.data.scope?.complete_inventory ?? this.data.total === this.data.metadata.total });
|
|
135
|
+
}
|
|
136
|
+
lookup(input, options = {}) {
|
|
137
|
+
if (typeof input !== "string" || !input.trim() || input.trim().length > 512) invalid("Emoji input must contain 1\u2013512 characters.");
|
|
138
|
+
const value = input.trim(), source = options.shortcodeSource;
|
|
139
|
+
if (source && !sources.includes(source)) invalid("shortcodeSource must be cldr, github, slack or discord.");
|
|
140
|
+
let glyph = this.byGlyph.has(value) ? value : void 0;
|
|
141
|
+
if (!glyph && !source && Object.hasOwn(this.data.legacy, legacyName(value))) glyph = this.data.legacy[legacyName(value)];
|
|
142
|
+
if (!glyph) {
|
|
143
|
+
let form = value;
|
|
144
|
+
if (/^(?:U\+)?[0-9A-F]{4,6}(?:[\s,-]+(?:U\+)?[0-9A-F]{4,6})*$/i.test(value)) {
|
|
145
|
+
const points = value.split(/[\s,-]+/).map((part) => Number.parseInt(part.replace(/^U\+/i, ""), 16));
|
|
146
|
+
if (points.length > 32 || points.some((cp) => cp > 1114111 || cp >= 55296 && cp <= 57343)) invalid("Unicode codepoints must be valid scalar values.");
|
|
147
|
+
form = String.fromCodePoint(...points);
|
|
148
|
+
} else if (/^U\+/i.test(value)) invalid("Use Unicode codepoints such as U+1F680 or U+2764 U+FE0F.");
|
|
149
|
+
const canonical = Object.hasOwn(this.data.forms, form) ? this.data.forms[form] : form;
|
|
150
|
+
if (canonical && this.byGlyph.has(canonical)) glyph = canonical;
|
|
151
|
+
}
|
|
152
|
+
if (!glyph) {
|
|
153
|
+
const candidates = /* @__PURE__ */ new Set();
|
|
154
|
+
for (const key of /* @__PURE__ */ new Set([stripColons(value).toLowerCase(), normalizeName(value)])) {
|
|
155
|
+
for (const name of source ? [source] : sources) {
|
|
156
|
+
const dictionary = this.data.shortcodes[name];
|
|
157
|
+
if (Object.hasOwn(dictionary, key)) {
|
|
158
|
+
for (const candidate of dictionary[key] ?? []) if (this.byGlyph.has(candidate)) candidates.add(candidate);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
if (!source) for (const candidate of this.aliases.get(normalizeName(value)) ?? []) candidates.add(candidate);
|
|
163
|
+
if (!source || !candidates.size && !/^:.*:$/.test(value)) for (const candidate of this.names.get(normalizeName(value)) ?? []) candidates.add(candidate);
|
|
164
|
+
if (candidates.size > 1) invalid("Emoji shortcode is ambiguous. Set shortcodeSource to cldr, github, slack or discord.");
|
|
165
|
+
glyph = candidates.values().next().value;
|
|
166
|
+
}
|
|
167
|
+
const row = glyph ? this.byGlyph.get(glyph) : void 0;
|
|
168
|
+
if (!row) throw new EmojiCatalogError("not_found", "Emoji not found in this downloaded catalog.");
|
|
169
|
+
return this.project(row, options.deep === true);
|
|
170
|
+
}
|
|
171
|
+
search(query, options = {}) {
|
|
172
|
+
if (typeof query !== "string" || !query.trim() || query.trim().length > 256) invalid("Search query must contain 1\u2013256 characters.");
|
|
173
|
+
return this.page(query.trim(), options);
|
|
174
|
+
}
|
|
175
|
+
browse(options = {}) {
|
|
176
|
+
const { q, ...result } = this.page("", options);
|
|
177
|
+
return result;
|
|
178
|
+
}
|
|
179
|
+
/** Unknown and ambiguous inputs remain literal; positions refer to untouched input. */
|
|
180
|
+
convert(text, options) {
|
|
181
|
+
if (!options || !["to_shortcodes", "to_emoji"].includes(options.direction)) invalid("Direction must be to_shortcodes or to_emoji.");
|
|
182
|
+
if (!sources.includes(options.shortcodeSource)) invalid("shortcodeSource must be cldr, github, slack or discord.");
|
|
183
|
+
const parsed = this.parse(text), replacements = [], unresolved = [];
|
|
184
|
+
if (options.direction === "to_shortcodes") {
|
|
185
|
+
const encodings = this.encoding(options.shortcodeSource);
|
|
186
|
+
for (const row of parsed.emojis) {
|
|
187
|
+
const shortcode = encodings.get(row.emoji);
|
|
188
|
+
for (const position of row.positions) if (shortcode) replacements.push({ emoji: row.emoji, shortcode, ...position });
|
|
189
|
+
else unresolved.push({ emoji: row.emoji, ...position, reason: "unavailable" });
|
|
190
|
+
}
|
|
191
|
+
} else for (const match of text.matchAll(/:[^:\s]{1,128}:/gu)) {
|
|
192
|
+
const shortcode = match[0], start = match.index, end = start + shortcode.length;
|
|
193
|
+
try {
|
|
194
|
+
const row = this.lookup(shortcode, { shortcodeSource: options.shortcodeSource });
|
|
195
|
+
replacements.push({ emoji: row.emoji, shortcode, start, end });
|
|
196
|
+
} catch (error) {
|
|
197
|
+
if (!(error instanceof EmojiCatalogError)) throw error;
|
|
198
|
+
unresolved.push({ shortcode, start, end, reason: error.code === "not_found" ? "unknown" : "ambiguous" });
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
replacements.sort((a, b) => a.start - b.start);
|
|
202
|
+
unresolved.sort((a, b) => a.start - b.start);
|
|
203
|
+
let cursor = 0;
|
|
204
|
+
const chunks = [];
|
|
205
|
+
for (const row of replacements) {
|
|
206
|
+
chunks.push(text.slice(cursor, row.start), options.direction === "to_shortcodes" ? row.shortcode : row.emoji);
|
|
207
|
+
cursor = row.end;
|
|
208
|
+
}
|
|
209
|
+
chunks.push(text.slice(cursor));
|
|
210
|
+
return { text: chunks.join(""), unresolved };
|
|
211
|
+
}
|
|
212
|
+
/** Whole-grapheme predicate. Use parse() to extract emoji from prose with positions. */
|
|
213
|
+
regex(options = {}) {
|
|
214
|
+
const forms = options.qualifiedOnly ? [...this.byGlyph.keys()] : Object.keys(this.data.forms).filter((form) => this.byGlyph.has(this.data.forms[form]));
|
|
215
|
+
if (options.components) forms.push(...["\u{1F3FB}", "\u{1F3FC}", "\u{1F3FD}", "\u{1F3FE}", "\u{1F3FF}", "\u{1F9B0}", "\u{1F9B1}", "\u{1F9B2}", "\u{1F9B3}"]);
|
|
216
|
+
const escaped = [...new Set(forms)].sort((a, b) => b.length - a.length || (a < b ? -1 : 1)).map((form) => form.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"));
|
|
217
|
+
return new RegExp(`^(?:${escaped.length ? escaped.join("|") : "(?!)"})$`, "u");
|
|
218
|
+
}
|
|
219
|
+
categories() {
|
|
220
|
+
const groups = /* @__PURE__ */ new Map();
|
|
221
|
+
for (const row of this.data.emojis) {
|
|
222
|
+
if (!row.category) continue;
|
|
223
|
+
if (!groups.has(row.category)) groups.set(row.category, /* @__PURE__ */ new Set());
|
|
224
|
+
if (row.deep?.subcategory) groups.get(row.category).add(row.deep.subcategory);
|
|
225
|
+
}
|
|
226
|
+
return { categories: [...groups].map(([category, subcategories]) => ({ category, subcategories: [...subcategories] })) };
|
|
227
|
+
}
|
|
228
|
+
parse(text, options = {}) {
|
|
229
|
+
if (typeof text !== "string" || text.length > 16e3) invalid("Text must be a string of at most 16000 UTF-16 code units.");
|
|
230
|
+
const found = /* @__PURE__ */ new Map();
|
|
231
|
+
this.segmenter ??= new Intl.Segmenter("en", { granularity: "grapheme" });
|
|
232
|
+
for (const { segment, index } of this.segmenter.segment(text)) {
|
|
233
|
+
const canonical = Object.hasOwn(this.data.forms, segment) ? this.data.forms[segment] : segment;
|
|
234
|
+
const row = canonical ? this.byGlyph.get(canonical) : void 0;
|
|
235
|
+
if (!row) continue;
|
|
236
|
+
if (!found.has(row.emoji)) found.set(row.emoji, { ...this.project(row, options.deep === true), positions: [] });
|
|
237
|
+
const result = found.get(row.emoji);
|
|
238
|
+
result.positions.push({ start: index, end: index + segment.length });
|
|
239
|
+
}
|
|
240
|
+
return { emojis: [...found.values()] };
|
|
241
|
+
}
|
|
242
|
+
project(row, deep) {
|
|
243
|
+
const { emoji, name, shortcodes, category } = row;
|
|
244
|
+
return { emoji, name, shortcodes: [...shortcodes], category, ...deep ? { deep: structuredClone(row.deep) } : {} };
|
|
245
|
+
}
|
|
246
|
+
page(query, options) {
|
|
247
|
+
if (options.limit !== void 0 && (!Number.isSafeInteger(options.limit) || options.limit < 1)) invalid("Limit must be a positive integer.");
|
|
248
|
+
const limit = Math.min(options.limit ?? 20, 50), offset = options.offset ?? 0;
|
|
249
|
+
if (!Number.isSafeInteger(offset) || offset < 0 || offset > 1e5) invalid("Offset must be an integer between 0 and 100000.");
|
|
250
|
+
for (const [key, known] of [["category", this.categoryKeys], ["subcategory", this.subcategoryKeys]]) {
|
|
251
|
+
if (options[key] !== void 0 && (!options[key].trim() || !known.has(filterKey(options[key])))) invalid(`Unknown emoji ${key}. Use categories() to find supported values.`);
|
|
252
|
+
}
|
|
253
|
+
for (const key of ["version", "maxVersion"]) if (options[key] !== void 0 && (!/^\d{1,2}\.\d{1,2}$/.test(options[key]) || versionCompare(options[key], this.data.metadata.source_version) > 0)) invalid(`${key} must be an Emoji version no newer than ${this.data.metadata.source_version}.`);
|
|
254
|
+
let rows = this.filtered(options);
|
|
255
|
+
if (query) {
|
|
256
|
+
this.ensureSearch();
|
|
257
|
+
const needle = normalizeSearch(query), words = needle.split("_").filter(Boolean);
|
|
258
|
+
const scored = rows.flatMap((row) => {
|
|
259
|
+
const { names, primaryShorts, shorts, keywords, translatedKeywords, allNames, blob } = this.searchFacts.get(row.emoji);
|
|
260
|
+
if (!blob.includes(needle) && !(words.length > 1 && words.every((word) => blob.includes(word)))) return [];
|
|
261
|
+
const score = primaryShorts.includes(needle) || names.includes(needle) ? 0 : shorts.includes(needle) ? 0.5 : allNames.some((value) => value.startsWith(needle)) ? 1 : translatedKeywords.includes(needle) ? 1.5 : allNames.some((value) => value.includes(needle)) ? 2 : words.length > 1 && allNames.some((value) => words.every((word) => value.includes(word))) ? 2.5 : keywords.some((value) => value.includes(needle)) ? 3 : words.length > 1 && words.every((word) => blob.includes(word)) ? 4 : 100;
|
|
262
|
+
return score === 100 ? [] : [{ row, score }];
|
|
263
|
+
});
|
|
264
|
+
rows = scored.sort((a, b) => a.score - b.score || (this.data.localization?.resolved_locale ? this.compareNames(a.row.name, b.row.name) || a.row.emoji.localeCompare(b.row.emoji) : a.row.name.localeCompare(b.row.name))).map(({ row }) => row);
|
|
265
|
+
}
|
|
266
|
+
const page = rows.slice(offset, offset + limit), paged = !query || ["category", "subcategory", "version", "maxVersion", "includeSkinTones", "offset"].some((key) => options[key] !== void 0);
|
|
267
|
+
return { q: query, emojis: page.map((row) => this.project(row, options.deep === true)), ...paged ? { total: rows.length, next_offset: offset + page.length < rows.length ? offset + page.length : null } : {} };
|
|
268
|
+
}
|
|
269
|
+
filtered(options) {
|
|
270
|
+
for (const [key2, known] of [["category", this.categoryKeys], ["subcategory", this.subcategoryKeys]]) {
|
|
271
|
+
if (options[key2] !== void 0 && (!options[key2].trim() || !known.has(filterKey(options[key2])))) invalid(`Unknown emoji ${key2}. Use categories() to find supported values.`);
|
|
272
|
+
}
|
|
273
|
+
for (const key2 of ["version", "maxVersion"]) if (options[key2] !== void 0 && (!/^\d{1,2}\.\d{1,2}$/.test(options[key2]) || versionCompare(options[key2], this.data.metadata.source_version) > 0)) invalid(`${key2} must be an Emoji version no newer than ${this.data.metadata.source_version}.`);
|
|
274
|
+
if (options.includeSkinTones !== false && options.category === void 0 && options.subcategory === void 0 && options.version === void 0 && options.maxVersion === void 0) return this.data.emojis;
|
|
275
|
+
const key = JSON.stringify([options.includeSkinTones === false, options.category === void 0 ? null : filterKey(options.category), options.subcategory === void 0 ? null : filterKey(options.subcategory), options.version, options.maxVersion]);
|
|
276
|
+
const cached = this.filterCache.get(key);
|
|
277
|
+
if (cached) return cached;
|
|
278
|
+
const rows = this.data.emojis.filter((row) => (options.includeSkinTones !== false || !/[\u{1F3FB}-\u{1F3FF}]/u.test(row.emoji)) && (options.category === void 0 || filterKey(row.category ?? "") === filterKey(options.category)) && (options.subcategory === void 0 || filterKey(row.deep?.subcategory ?? "") === filterKey(options.subcategory)) && (options.version === void 0 || versionCompare(row.deep.version, options.version) === 0) && (options.maxVersion === void 0 || versionCompare(row.deep.version, options.maxVersion) <= 0));
|
|
279
|
+
if (this.filterCache.size >= 32) this.filterCache.delete(this.filterCache.keys().next().value);
|
|
280
|
+
this.filterCache.set(key, rows);
|
|
281
|
+
return rows;
|
|
282
|
+
}
|
|
283
|
+
};
|
|
284
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
285
|
+
0 && (module.exports = {
|
|
286
|
+
EmojiCatalog,
|
|
287
|
+
EmojiCatalogError
|
|
288
|
+
});
|