@recursiv/sdk 0.7.82 → 0.7.83
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 +44 -1
- package/dist/assistant/fixedCardData.d.ts +91 -0
- package/dist/assistant/fixedCardData.js +372 -0
- package/dist/assistant/fixedCards.d.ts +35 -0
- package/dist/assistant/fixedCards.js +904 -0
- package/dist/assistant/hiddenText.d.ts +24 -0
- package/dist/assistant/hiddenText.js +263 -0
- package/dist/assistant/index.d.ts +22 -0
- package/dist/assistant/index.js +22 -0
- package/dist/assistant/render.d.ts +8 -0
- package/dist/assistant/render.js +253 -0
- package/dist/assistant/subjectBundle.d.ts +30 -0
- package/dist/assistant/subjectBundle.js +518 -0
- package/dist/assistant/types.d.ts +353 -0
- package/dist/assistant/types.js +11 -0
- package/dist/assistant-surfaces.d.ts +42 -0
- package/dist/assistant-surfaces.js +113 -0
- package/dist/index.d.ts +5 -2
- package/dist/index.js +1 -0
- package/dist/realtime.d.ts +10 -3
- package/dist/realtime.js +11 -2
- package/dist/resources/agents.d.ts +6 -1
- package/dist/resources/agents.js +12 -2
- package/dist/resources/e2ee.d.ts +56 -0
- package/dist/resources/e2ee.js +21 -0
- package/dist/resources/link-preview.d.ts +42 -1
- package/dist/resources/link-preview.js +36 -2
- package/dist/resources/network.d.ts +6 -0
- package/dist/resources/project-brain.d.ts +8 -0
- package/dist/resources/users.d.ts +11 -2
- package/dist/resources/users.js +1 -1
- package/dist/types.d.ts +34 -0
- package/llm.md +32 -2
- package/package.json +6 -1
|
@@ -0,0 +1,904 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fixed election and crisis cards (George plan G6b, risks 3 and 22, D11): a member's message that asks how, where or
|
|
3
|
+
* when to vote gets the official sites, and one that shows a risk of self-harm or immediate danger gets the local
|
|
4
|
+
* crisis line and emergency number. No model is called to decide or to write either card: matching is plain code over
|
|
5
|
+
* normalised text, and every card is fixed data (fixedCardData.ts), so the server's safe turn and a Sealed phone show
|
|
6
|
+
* the same card for the same words.
|
|
7
|
+
*
|
|
8
|
+
* Languages read: English and Spanish (FIXED_CARD_LANGUAGES). A message in another language matches only through the
|
|
9
|
+
* words it shares with those two. The lists lean differently on purpose:
|
|
10
|
+
* - crisis catches too many rather than too few: the card always shows and the turn can still answer, so a false
|
|
11
|
+
* positive costs a card, a miss can cost a life. Letters may be stretched ("dieee"), digits may stand for letters
|
|
12
|
+
* ("k1ll"), and accents, case, apostrophes, zero-width characters, "fancy font" letters and common look-alike
|
|
13
|
+
* letters are ignored. A few idioms ("dying to see", "killed myself laughing", "me muero de risa") are taken out
|
|
14
|
+
* first. Danger from someone else leads with the emergency number; worry about someone else gets a card addressed
|
|
15
|
+
* to the helper.
|
|
16
|
+
* - election catches too few rather than too many: only a clear logistics ask (registering, deadlines, polling
|
|
17
|
+
* places, mail or absentee ballots, voter ID, checking registration or a ballot) matches. Talk about candidates,
|
|
18
|
+
* results or polls, opinions, claims of fraud, and votes on Minds itself (a post, a poll, a contest) never do.
|
|
19
|
+
*
|
|
20
|
+
* Pure: no network, clock, randomness or Node-only API. Every pattern runs in time linear in the message. Read the
|
|
21
|
+
* member's own words, not the post they ask about.
|
|
22
|
+
*/
|
|
23
|
+
import { COUNTRY_ALIASES, CRISIS_DIRECTORY, CRISIS_LINES, ELECTION_SITES, FIXED_CARD_DATA_AS_OF, } from './fixedCardData.js';
|
|
24
|
+
import { removeInvisibleCharacters } from './hiddenText.js';
|
|
25
|
+
/**
|
|
26
|
+
* The longest message read in full, counted after invisible characters are dropped. Past it, the first and the last
|
|
27
|
+
* FIXED_CARD_SCAN_LIMIT / 2 characters are read, each on its own, so a long paste that ends with a crisis statement
|
|
28
|
+
* still matches.
|
|
29
|
+
*/
|
|
30
|
+
export const FIXED_CARD_SCAN_LIMIT = 20_000;
|
|
31
|
+
/**
|
|
32
|
+
* Until this moment an election card replaces the model's answer; after it, the card shows first and the turn may go
|
|
33
|
+
* on. Noon UTC on 2026-11-04: the day after the US midterms (2026-11-03), after the last US polls close (Hawaii and
|
|
34
|
+
* the Aleutians, 05:00 to 06:00 UTC on 11-04).
|
|
35
|
+
*/
|
|
36
|
+
export const ELECTION_CARD_ONLY_UNTIL = '2026-11-04T12:00:00Z';
|
|
37
|
+
// ── Normalising ──
|
|
38
|
+
/** Letters a stripped accent does not reach, in case `normalize` is missing (old Hermes builds). */
|
|
39
|
+
const FOLD = {
|
|
40
|
+
á: 'a', à: 'a', â: 'a', ä: 'a', ã: 'a', å: 'a', é: 'e', è: 'e', ê: 'e', ë: 'e', í: 'i', ì: 'i', î: 'i', ï: 'i',
|
|
41
|
+
ó: 'o', ò: 'o', ô: 'o', ö: 'o', õ: 'o', ú: 'u', ù: 'u', û: 'u', ü: 'u', ñ: 'n', ç: 'c', ý: 'y', ÿ: 'y',
|
|
42
|
+
};
|
|
43
|
+
const FOLDABLE = /[áàâäãåéèêëíìîïóòôöõúùûüñçýÿ]/g;
|
|
44
|
+
/**
|
|
45
|
+
* Letters NFKD does not take to ASCII but that read as ASCII letters: small capitals ("ᴋɪʟʟ"), and Cyrillic and Greek
|
|
46
|
+
* letters drawn like Latin ones, after lower-casing.
|
|
47
|
+
*/
|
|
48
|
+
const LOOKALIKE = {
|
|
49
|
+
ᴀ: 'a', ʙ: 'b', ᴄ: 'c', ᴅ: 'd', ᴇ: 'e', ꜰ: 'f', ɢ: 'g', ʜ: 'h', ɪ: 'i', ᴊ: 'j', ᴋ: 'k', ʟ: 'l', ᴍ: 'm', ɴ: 'n',
|
|
50
|
+
ᴏ: 'o', ᴘ: 'p', ʀ: 'r', ꜱ: 's', ᴛ: 't', ᴜ: 'u', ᴠ: 'v', ᴡ: 'w', ʏ: 'y', ᴢ: 'z',
|
|
51
|
+
а: 'a', в: 'b', е: 'e', к: 'k', м: 'm', н: 'h', о: 'o', р: 'p', с: 'c', т: 't', у: 'y', х: 'x', і: 'i', ј: 'j',
|
|
52
|
+
ѕ: 's', ԁ: 'd', һ: 'h', ӏ: 'l',
|
|
53
|
+
α: 'a', ε: 'e', ι: 'i', κ: 'k', ο: 'o', ρ: 'p', τ: 't', χ: 'x',
|
|
54
|
+
};
|
|
55
|
+
const LOOKALIKES = new RegExp(`[${Object.keys(LOOKALIKE).join('')}]`, 'g');
|
|
56
|
+
/** Digits and signs read as letters inside a word that also has letters: "k1ll", "d13", "$uicide". */
|
|
57
|
+
const LEET = { 0: 'o', 1: 'i', 3: 'e', 4: 'a', 5: 's', 7: 't', '@': 'a', $: 's' };
|
|
58
|
+
/** The text read: all of it, or past FIXED_CARD_SCAN_LIMIT its first and last halves. Invisible characters go first. */
|
|
59
|
+
function scanParts(text) {
|
|
60
|
+
const visible = removeInvisibleCharacters(text);
|
|
61
|
+
if (visible.length <= FIXED_CARD_SCAN_LIMIT)
|
|
62
|
+
return [visible];
|
|
63
|
+
const half = FIXED_CARD_SCAN_LIMIT / 2;
|
|
64
|
+
return [visible.slice(0, half), visible.slice(visible.length - half)];
|
|
65
|
+
}
|
|
66
|
+
function normalisePart(text) {
|
|
67
|
+
let out = text;
|
|
68
|
+
try {
|
|
69
|
+
// Before lower-casing: "fancy font" capitals (𝐊𝐈𝐋𝐋) decompose to upper-case ASCII, which only then lower-case.
|
|
70
|
+
out = out.normalize('NFKD');
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
// No normalize on this engine: the fold table below still takes the accents off Spanish.
|
|
74
|
+
}
|
|
75
|
+
out = out.toLowerCase();
|
|
76
|
+
out = out.replace(/[̀-ͯ]/g, '')
|
|
77
|
+
.replace(FOLDABLE, (letter) => FOLD[letter] ?? letter)
|
|
78
|
+
.replace(LOOKALIKES, (letter) => LOOKALIKE[letter] ?? letter);
|
|
79
|
+
out = out.replace(/['‘’ʼ`´]/g, '');
|
|
80
|
+
out = out.replace(/[a-z0-9@$]+/g, (token) => /[a-z]/.test(token) && /[0-9@$]/.test(token) ? token.replace(/[013457@$]/g, (sign) => LEET[sign] ?? sign) : token);
|
|
81
|
+
return out.replace(/[^a-z0-9]+/g, ' ').trim();
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Lower case, no accents, no apostrophes ("I'm" reads "im"), look-alike and "fancy font" letters as plain ones, digits
|
|
85
|
+
* standing for letters read as letters, and every other run of non-letters one space: what the phrase lists are
|
|
86
|
+
* written against. Past FIXED_CARD_SCAN_LIMIT characters, the first and last halves joined by a space.
|
|
87
|
+
*/
|
|
88
|
+
export function normaliseForMatching(text) {
|
|
89
|
+
if (typeof text !== 'string' || !text)
|
|
90
|
+
return '';
|
|
91
|
+
return scanParts(text).map(normalisePart).filter(Boolean).join(' ');
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* A run of three or more of one letter read as two ("killlll" reads "kill"). No phrase has a letter three times in a
|
|
95
|
+
* row, so nothing is lost, and a stretched pattern then never meets a long run: the matcher stays linear.
|
|
96
|
+
*/
|
|
97
|
+
const squeeze = (normal) => normal.replace(/([a-z])\1{2,}/g, '$1$1');
|
|
98
|
+
// ── Phrase lists ──
|
|
99
|
+
//
|
|
100
|
+
// Each entry is a regular expression over normalised text (lower-case letters, digits, single spaces), matched as
|
|
101
|
+
// whole words. `WORD` stands for one word. Crisis lists are "stretched" before compiling: each letter may repeat.
|
|
102
|
+
// In a crisis list, write no character class with a letter in it and no quantifier on a letter but '?'.
|
|
103
|
+
const MYSELF = '(?:myself|my self|meself|myslef)';
|
|
104
|
+
const TO = '(?:to|2)';
|
|
105
|
+
/** People someone may worry about, or fear (English). */
|
|
106
|
+
const PERSON_EN = '(?:friend|best friend|boyfriend|girlfriend|bf|gf|partner|husband|wife|ex|son|daughter|kid|child|baby|brother|sister|mom|mum|mother|dad|father|stepdad|stepfather|stepmom|stepmother|cousin|roommate|flatmate|coworker|classmate|student|uncle|aunt|niece|nephew|grandma|grandpa|teen|teenager|bestie|buddy|mate|neighbor|neighbour|fiance|fiancee|parents)';
|
|
107
|
+
/** Someone other than the member (English). */
|
|
108
|
+
const SOMEONE_EN = `(?:he|she|they|hes|shes|theyre|he is|she is|they are|someone|somebody|(?:my|our|a) (?:(?:best|close|little|older|younger|big|baby|teenage|teen|online) )?${PERSON_EN})`;
|
|
109
|
+
/** Someone the member may fear (English). */
|
|
110
|
+
const ATTACKER_EN = `(?:he|she|they|hes|shes|theyre|he is|she is|they are|someone|somebody|my ${PERSON_EN}|a (?:man|guy|woman|stranger))`;
|
|
111
|
+
/** First-person self-harm or suicide risk (English). */
|
|
112
|
+
const CRISIS_SELF_EN = [
|
|
113
|
+
`(?:kill|killing|killed|kil) ${MYSELF}`,
|
|
114
|
+
'kms',
|
|
115
|
+
'unaliv(?:e|ing)',
|
|
116
|
+
`un aliv(?:e|ing) (?:${MYSELF}|me)`,
|
|
117
|
+
'sewer ?slide',
|
|
118
|
+
'(?:end|ending|ended) (?:my|my own) life',
|
|
119
|
+
'(?:end|ending) it all',
|
|
120
|
+
'(?:take|taking|took) my (?:own )?life',
|
|
121
|
+
`(?:want|wanna|wanted|wish|ready|planning|plan|deserve|need) (?:${TO} )?die`,
|
|
122
|
+
`(?:want|wanna|wanted|rather|ready|wish ${TO}) (?:${TO} )?be dead(?! (?:serious|honest|tired|last|center|centre|even|on|wrong|right|set|sure|silent|quiet))`,
|
|
123
|
+
'wish i (?:was|were|could be) dead',
|
|
124
|
+
'wish i (?:could|would|can) (?:just )?(?:die|disappear forever|not wake up|stop existing)',
|
|
125
|
+
'wish i (?:wasnt|werent|was not|were not) (?:alive|here|born)',
|
|
126
|
+
'wish i (?:had|d) never been born',
|
|
127
|
+
'wish i was never born',
|
|
128
|
+
'hope i (?:just )?(?:die|dont wake up|do not wake up|never wake up|wont wake up)',
|
|
129
|
+
'(?:sleep|go to sleep|fall asleep|close my eyes) and (?:never|not) wake up',
|
|
130
|
+
'better off dead',
|
|
131
|
+
'(?:better|happier) (?:off )?without me',
|
|
132
|
+
`(?:dont|do not|dnt) want ${TO} (?:be alive|exist|wake up|be here anymore|be here any more|go on|keep going|live anymore|live any more|be around anymore)`,
|
|
133
|
+
`(?:dont|do not|dnt) want ${TO} live(?! (?:in (?!(?:this world|this life|this body|this pain|pain|fear|misery))|with (?!(?:this pain|the pain|this|myself|it|the guilt|this guilt|the shame|this feeling|this depression|depression|anxiety))|near|without|on (?!(?:this earth|this planet))|at|there|here|alone|together|next|by|so|that|somewhere|anywhere|forever|like that))`,
|
|
134
|
+
`(?:want|wanna|wants|ready|going|gonna|need|decided) ${TO} stop living(?! (?:in|with|like|at|for|paycheck|off|on|under|my life))`,
|
|
135
|
+
'(?:no|nothing|zero) (?:reason|reasons|point) (?:to|in|for) (?:live|living|going on|being alive|keep going)',
|
|
136
|
+
'nothing (?:left )?to live for',
|
|
137
|
+
'(?:no|lost the|lost my) will to live',
|
|
138
|
+
'(?:life|my life) (?:is not|isnt|aint|is no longer) worth (?:living|it)',
|
|
139
|
+
'not worth living',
|
|
140
|
+
'(?:cant|cannot|can not) (?:go on|do this|take it|take this|keep going|keep living|live like this)(?: like this)? (?:anymore|any more|any longer)',
|
|
141
|
+
'(?:cant|cannot|can not) go on like this',
|
|
142
|
+
'suicidal',
|
|
143
|
+
'suicide (?:note|notes|plan|plans|letter|method|methods|attempt|attempts|pact|hotline|helpline|line|lifeline)',
|
|
144
|
+
'(?:method|methods|way|ways|means) (?:of|for|to commit|to) suicide',
|
|
145
|
+
'suicide (?:seems|is|feels|looks|sounds|would be) (?:like )?(?:the |my )?(?:only|best|easiest|last|right) (?:option|way|choice|answer|way out|solution|thing)',
|
|
146
|
+
'(?:only|best|last) (?:option|way out|choice|answer|solution) (?:is|left is|would be) suicide',
|
|
147
|
+
'my (?:own )?suicide(?! (?:prevention|awareness|squad|training|research|class|course|paper|essay))',
|
|
148
|
+
'(?:planning|planned|preparing|prepared) my (?:own )?(?:suicide|death)',
|
|
149
|
+
'(?:commit|commits|committing|committed|commited|comit) suicide',
|
|
150
|
+
`(?:thinking|thought|thoughts|think|considering|contemplating) (?:about |of )?(?:suicide|killing ${MYSELF}|ending my life|ending it all)`,
|
|
151
|
+
'(?:thinking|thought|think|considering) (?:of |about )?ending it(?! (?:with|for|between|all))',
|
|
152
|
+
`(?:attempt|attempted|attempting|tried) (?:suicide|${TO} kill ${MYSELF}|${TO} end my life)`,
|
|
153
|
+
`(?:how|ways|best way|easiest way|quickest way|fastest way|painless way|quick way|easy way) (?:${TO}|do i|can i|should i|of) (?:die|kill ${MYSELF}|commit suicide|end my life|end it all)`,
|
|
154
|
+
'painless (?:death|suicide|way to die|way out)',
|
|
155
|
+
'self ?harm(?:ing|ed)?',
|
|
156
|
+
'self ?injur(?:y|e|ing|ies)',
|
|
157
|
+
'self mutilat(?:e|ion|ing)',
|
|
158
|
+
`(?:want|wanna|going|gonna|need|needing|urge|urges|tempted|thinking about|thought about|started|start|keep|kept|cant stop) (?:${TO} )?(?:hurt|harm|cut|burn|hurting|harming|cutting|burning|punish|punishing|starve|starving) ${MYSELF}(?! out)`,
|
|
159
|
+
`(?:im|i am|ive been|i have been|been|i was|i still|still) (?:starving|burning|cutting|hurting|harming|punishing) ${MYSELF}(?! out)`,
|
|
160
|
+
`(?:cut|cuts|cutting) ${MYSELF}`,
|
|
161
|
+
`(?:cut|hurt|harm|burned|burnt) ${MYSELF} (?:again|on purpose|deliberately)`,
|
|
162
|
+
`harming ${MYSELF}`,
|
|
163
|
+
'started cutting again',
|
|
164
|
+
`(?:hang|hanging|hanged) ${MYSELF}`,
|
|
165
|
+
'slit (?:my|both) (?:wrist|wrists|throat)',
|
|
166
|
+
`(?:shoot|shooting|shot) ${MYSELF}`,
|
|
167
|
+
`(?:drown|drowning|poison|poisoning|gas|gassing|suffocate|suffocating|electrocute) ${MYSELF}`,
|
|
168
|
+
'(?:put|putting|hold|holding) a gun (?:to|in) my (?:head|mouth)',
|
|
169
|
+
`(?:gun|knife|rope|blade|razor|pills) to use on ${MYSELF}`,
|
|
170
|
+
`(?:use|used|using|turn|turned|turning|point|pointed|pointing) (?:a |the |my |this |that )?(?:gun|knife|blade|razor|rope) on ${MYSELF}`,
|
|
171
|
+
`(?:want|wanna|going|gonna|plan|planning|thinking about|thought about|about|ready|tempted|decided) (?:${TO} )?(?:jump|jumping) (?:off|from|in front of)`,
|
|
172
|
+
'jump (?:off|from) (?:a|the|this) (?:bridge|building|roof|rooftop|cliff|balcony|overpass|tower|skyscraper)',
|
|
173
|
+
`(?:jump|step|walk|throw ${MYSELF}) in front of (?:a|the) (?:train|truck|car|bus|subway)`,
|
|
174
|
+
'(?:on|at|up on|standing on|standing at) (?:the|a|this|my|our) (?:roof|rooftop|bridge|edge|ledge|balcony|cliff|overpass|tower)(?: WORD){0,4} (?:jump|jumping)',
|
|
175
|
+
'(?:im|i am) (?:standing|sitting) (?:on|at) (?:the|a) (?:edge of (?:the |a )?)?(?:bridge|ledge|railing)',
|
|
176
|
+
'(?:im|i am) (?:on|at) the (?:edge of the )?(?:bridge|ledge|railing) (?:right now|now|tonight)',
|
|
177
|
+
'(?:lethal|fatal) dose',
|
|
178
|
+
'overdose on purpose',
|
|
179
|
+
'intentional(?:ly)? overdose',
|
|
180
|
+
`(?:want|wanna|going|gonna|plan|planning|try|trying|tried|thinking about) (?:${TO} )?(?:overdose|od)`,
|
|
181
|
+
'(?:took|take|taken|taking) an overdose',
|
|
182
|
+
'(?:i|ive|i have|i just|i think i|i think ive|i might have|i may have) (?:just )?(?:overdosed|oded)',
|
|
183
|
+
`(?:how many|how much)(?: WORD){0,6} (?:to|would|will|does it take to|it takes to|until i) (?:die|kill me|kill ${MYSELF}|kill you|kill a person|kill someone|overdose|od|be fatal|be lethal)`,
|
|
184
|
+
'(?:swallow|swallowed|take|took|taking) (?:all|a bottle of|the whole bottle of|a handful of|a bunch of|a lot of|lots of|loads of|too many|all of) (?:my |the |these |those )?(?:pills|tablets|meds|medication|sleeping pills|painkillers)',
|
|
185
|
+
'(?:took|swallowed|taken|ate|take|swallow|taking) (?:like |about |around |over |almost |maybe )?[0-9]{2,} (?:of (?:my |the |these |those )?)?(?:pills|tablets|meds|sleeping pills|painkillers|tylenol|advil|ibuprofen|aspirin|xanax|benadryl)',
|
|
186
|
+
'goodbye cruel world',
|
|
187
|
+
`(?:want|wanna|wish i could|need) (?:${TO} )?(?:stop existing|disappear forever|disappear for good|sleep forever|never wake up|not wake up)`,
|
|
188
|
+
`want (?:it|this|everything|it all|this all|all of this|the pain) (?:all )?${TO} (?:end|be over)(?! (?:with|on|at|by|before|after|when|so|in (?:a|the)))`,
|
|
189
|
+
`want (?:the pain|it all|everything|all of this|all this) ${TO} stop`,
|
|
190
|
+
'(?:nobody|no one|noone) (?:would|will|is going to|gonna) (?:miss|notice|care about) me',
|
|
191
|
+
`(?:nobody|no one|noone) (?:would |will |even |really )?(?:care|cares|notice|notices|miss|misses) if i (?:die|died|was gone|were gone|disappeared|disappear|killed ${MYSELF}|kill ${MYSELF}|wasnt here|was dead|wasnt around)`,
|
|
192
|
+
'(?:im|i am) (?:just )?a burden (?:to|on) (?:everyone|everybody|my family|them|my friends)',
|
|
193
|
+
'(?:im|i am|i feel like|i feel like im|i feel|i feel im|i just feel like|i always feel like|i know im) (?:just |such |nothing but |only |always )?(?:a|such a) burden',
|
|
194
|
+
'(?:im|i am) done with (?:life|living)',
|
|
195
|
+
`(?:going ${TO}|gonna|want ${TO}|wanna|ready ${TO}|plan ${TO}|planning ${TO}|about ${TO}) end it(?: all| tonight| today| now| soon| here)?(?! (?:with|for|between))`,
|
|
196
|
+
`(?:going ${TO}|gonna|want ${TO}|wanna|ready ${TO}|plan ${TO}|planning ${TO}|about ${TO}|decided ${TO}|time ${TO}|need ${TO}|thinking (?:of|about)|think about|thought about|considering) (?:end|ending) (?:things|everything)(?! (?:with|between|for us))`,
|
|
197
|
+
'(?:tie|tying|make|making) a noose',
|
|
198
|
+
'(?:writing|wrote|write|written) (?:a|my) (?:suicide|goodbye) (?:note|letter)',
|
|
199
|
+
];
|
|
200
|
+
/**
|
|
201
|
+
* Worry about someone else's risk (English): the card speaks to the helper. Read before the member's own words, and
|
|
202
|
+
* taken out of them, so "my friend is suicidal" is not read as the member's own risk.
|
|
203
|
+
*/
|
|
204
|
+
const CRISIS_OTHER_EN = [
|
|
205
|
+
`wants ${TO} (?:die|be dead)`,
|
|
206
|
+
`${SOMEONE_EN} (?:wanted|want|says (?:he|she|they) wants?|said (?:he|she|they) wanted?|keeps saying (?:he|she|they) wants?) ${TO} (?:die|be dead)`,
|
|
207
|
+
`${SOMEONE_EN} (?:is |s |are |re |was |seems |seem |sounds |looks |might be |may be |could be |has been |have been |is being |is getting |feels |is feeling )?(?:really |very |so |kind of |kinda |pretty |extremely |actively )?suicidal`,
|
|
208
|
+
'(?:wants|want|wanted|trying|tried|going|gonna|threatening|threatened|threatens|plans|planning|says|said|talking about|talks about|thinking about|thinks about|keeps talking about|wrote about) (?:to )?(?:kill|killing|hurt|hurting|harm|harming|end|cut|cutting|hang|hanging|shoot|shooting|unalive|unaliving) (?:himself|herself|themselves|themself)',
|
|
209
|
+
'(?:wants|want|wanted|trying|tried|going|gonna|threatening|threatened|threatens|plans|planning|talking about|thinking about) (?:to )?(?:take|taking|end|ending) (?:his|her|their) (?:own )?life',
|
|
210
|
+
];
|
|
211
|
+
/**
|
|
212
|
+
* Immediate danger from someone else, or a medical emergency under way (English). Game talk ("beat me at chess") and
|
|
213
|
+
* the joke "my wife is going to kill me when she sees this" are left out; a threat, an attack under way, a weapon or
|
|
214
|
+
* an intruder are not.
|
|
215
|
+
*/
|
|
216
|
+
const CRISIS_DANGER_EN = [
|
|
217
|
+
'(?:trying|tried|threatening|threatened|threatens) (?:to )?(?:kill|murder|stab|shoot|rape|strangle|choke|hurt) me',
|
|
218
|
+
'(?:trying|tried|threatening|threatened|threatens) (?:to )?beat me up',
|
|
219
|
+
'(?:going to|gonna|about to|will) (?:kill|murder|stab|shoot|rape) me (?:help|please|im scared|i am scared|im terrified|right now|tonight)',
|
|
220
|
+
`${ATTACKER_EN} (?:is |s |are |re )?(?:going ${TO}|gonna|about ${TO}|will) (?:kill|murder|stab|shoot|rape|hurt|strangle|choke) me`,
|
|
221
|
+
'(?:hell|shell|theyll|hed|shed|theyd) (?:kill|murder|stab|shoot|rape|hurt) me',
|
|
222
|
+
'(?:im|i am) (?:scared|afraid|terrified) (?:that )?(?:he|she|they|hes|shes|theyre|hell|shell|theyll|someone) (?:is |s |are |re )?(?:going to |gonna |will |might |is going to )?(?:kill|hurt|murder|rape|stab|shoot) me',
|
|
223
|
+
'(?:hes|shes|he is|she is|theyre|they are|someone is|somebody is) (?:hurting|hitting|choking|strangling|attacking|raping|stabbing) me',
|
|
224
|
+
'(?:hes|shes|he is|she is|theyre|they are|someone is|somebody is) beating me up',
|
|
225
|
+
`my ${PERSON_EN} (?:hits|chokes|hurts|abuses|is hitting|is hurting|is abusing|is choking|hit|choked|kicks|kicked|punches|punched|slaps|slapped|strangles|strangled|rapes|raped|molests|molested|threatens|threatened|has been hitting|has been hurting|has been abusing|keeps hitting) me`,
|
|
226
|
+
`my ${PERSON_EN} (?:beats|beat|is beating|has been beating|keeps beating) me(?! (?:at|in|to|again at|every time at|online))`,
|
|
227
|
+
`my ${PERSON_EN} (?:beats|beat|is beating) me up`,
|
|
228
|
+
'(?:gun|knife|weapon) (?:pointed |aimed |held )?(?:at|to) (?:me|my head|my throat)',
|
|
229
|
+
`${ATTACKER_EN} (?:has|have|got|pulled|pulled out|is holding|s holding|is waving|s waving|is carrying|brought) (?:a|an|his|her|their) (?:gun|knife|weapon|pistol|rifle|machete)(?! (?:license|licence|permit|collection|safe|show|class|course|in (?:the|a|this) (?:game|movie|show|photo|picture|video)))`,
|
|
230
|
+
'(?:someone|somebody|a man|a guy|people|men|a stranger|an intruder) (?:is |s |are |re )?(?:breaking|broke|trying to break|getting) (?:in|into) (?:my|our|the) (?:house|home|apartment|flat|room)',
|
|
231
|
+
'(?:intruder|someone|somebody|a stranger) (?:is |s )?in my (?:house|home|apartment|flat|room)',
|
|
232
|
+
'(?:im|i am) being (?:attacked|abused|beaten|stalked|kidnapped|followed|raped|assaulted|held|trafficked|threatened|choked|strangled)',
|
|
233
|
+
'(?:im|i am) getting (?:attacked|abused|raped|assaulted|stalked)',
|
|
234
|
+
'(?:i was|ive been|i have been|i got|i just got|i was just|i think i was|i think ive been|i think i got) (?:raped|sexually assaulted|assaulted|molested|drugged|attacked|stabbed|shot|kidnapped|abducted)(?! (?:down|a|an|in (?:the|a) (?:game|movie)))',
|
|
235
|
+
`${ATTACKER_EN} (?:just )?(?:raped|molested|sexually assaulted|assaulted|drugged|stabbed|shot|attacked) me(?! (?:a|an|down|with (?:a|an) (?:look|glance|smile|text|message)))`,
|
|
236
|
+
'(?:im|i am) in danger',
|
|
237
|
+
'(?:im|i am) not safe (?:at home|here|right now|with him|with her|with them)',
|
|
238
|
+
'(?:im|i am) (?:being )?(?:held|locked|trapped) (?:in|inside) (?:a|my|the|his|her|their) (?:house|room|car|basement|apartment|trunk)',
|
|
239
|
+
'my life is in danger',
|
|
240
|
+
'(?:dont|do not) feel safe (?:at home|here|right now|in my home|in my house)',
|
|
241
|
+
'held against my will',
|
|
242
|
+
'(?:kidnapped|abducted) me',
|
|
243
|
+
'(?:someone|somebody|a man|a guy|a woman|a stranger|a car|a van|he|hes|she|shes|they|theyre|my ex) (?:is |s |are |re |keeps |has been |have been )?(?:following|chasing|stalking) me(?! (?:on|back|here|there|too|already|online))',
|
|
244
|
+
'(?:theres|there is|there s|we have|weve got|there are) (?:an |a )?(?:active )?(?:shooter|shooters|gunman|gunmen|man with a gun|guy with a gun)(?! (?:drill|drills|training|game|video|movie|scenario|show|episode))',
|
|
245
|
+
'(?:active )?(?:shooter|gunman) (?:at|in|on) (?:my|our|the) (?:school|work|office|building|mall|church|store|campus|class|classroom|job|street|house|neighborhood|neighbourhood|hospital|college|university)',
|
|
246
|
+
`${SOMEONE_EN} (?:just |has |have |had |may have |might have )?(?:overdosed|oded)`,
|
|
247
|
+
`${SOMEONE_EN} (?:is |s |are |re )?(?:not breathing|isnt breathing|arent breathing|stopped breathing|unconscious|unresponsive|turning blue)(?! (?:a word|to anyone|about))`,
|
|
248
|
+
'call (?:the police|911|999|112|an ambulance) for me',
|
|
249
|
+
];
|
|
250
|
+
/** People someone may worry about, or fear (Spanish). */
|
|
251
|
+
const PERSON_ES = '(?:amigo|amiga|novio|novia|pareja|esposo|esposa|marido|ex|hijo|hija|hermano|hermana|mama|madre|papa|padre|padrastro|madrastra|primo|prima|tio|tia|abuelo|abuela|companero|companera|vecino|vecina)';
|
|
252
|
+
/** Where a fall is meant (Spanish), after "tirarme", "me voy a tirar" and the like. */
|
|
253
|
+
const FALL_PLACE_ES = '(?:de|desde|del|al|a la|a las|a los|frente al|frente a|delante del|delante de|por)(?: WORD){0,2} (?:puente|edificio|techo|azotea|balcon|ventana|vias|via|tren|metro|camion|carro|coche|auto|precipicio|acantilado|barranco)';
|
|
254
|
+
/** "I'm going to", "I want to", "I feel like" (Spanish), before a verb that takes "me" in front. */
|
|
255
|
+
const INTENT_ES = '(?:voy a|quiero|pienso|tengo ganas de|dan ganas de|gustaria|quisiera|estoy pensando en)';
|
|
256
|
+
/** First-person self-harm or suicide risk (Spanish). */
|
|
257
|
+
const CRISIS_SELF_ES = [
|
|
258
|
+
'(?:me )?quiero morir(?:me)?',
|
|
259
|
+
'(?:me )?quisiera morir(?:me)?',
|
|
260
|
+
'(?:quiero|quisiera|prefiero|preferiria|deseo|me gustaria|solo quiero) estar muert(?:o|a)',
|
|
261
|
+
'(?:ganas|deseo|deseos) de morir(?:me)?',
|
|
262
|
+
'prefiero (?:estar muert(?:o|a)|morir(?:me)?)',
|
|
263
|
+
'ojala (?:me muera|me muriera|estuviera muert(?:o|a)|no hubiera nacido|no despertar|no despierte)',
|
|
264
|
+
'no (?:quiero|quisiera) (?:despertar|despertarme|existir|seguir aqui|seguir viviendo|estar viv(?:o|a)|vivir mas)',
|
|
265
|
+
'no quiero vivir(?! (?:en (?!(?:este mundo|esta vida|este cuerpo|este dolor|esta tristeza))|con (?!(?:este dolor|esto|esta tristeza|esta culpa|este sufrimiento|esta ansiedad|esta depresion|este vacio))|cerca|lejos|sin|alli|aqui|ahi|alla|sol(?:o|a)|junt(?:o|a)s|de))',
|
|
266
|
+
'no tengo (?:razon|razones|motivo|motivos|ganas|fuerzas) (?:para|de) (?:vivir|seguir)',
|
|
267
|
+
'no vale la pena (?:vivir|seguir viviendo|seguir)',
|
|
268
|
+
'(?:la vida|mi vida) no (?:tiene sentido|vale la pena)',
|
|
269
|
+
'no le (?:veo|encuentro) sentido a (?:la vida|mi vida|vivir|seguir)',
|
|
270
|
+
'(?:sin|perdi las) ganas de vivir',
|
|
271
|
+
'(?:cansad(?:o|a)|hart(?:o|a)) de vivir',
|
|
272
|
+
'(?:me )?quiero desaparecer(?! de (?:las redes|redes|internet|twitter|x|minds|instagram|tiktok|facebook))',
|
|
273
|
+
'ya no quiero (?:estar|seguir) aqui',
|
|
274
|
+
'no quiero seguir aqui',
|
|
275
|
+
'dormir y no despertar(?:me)?',
|
|
276
|
+
'(?:quiero|quisiera|deseo|ganas de|necesito|me gustaria|solo quiero|prefiero) (?:dejar de existir|dejar de vivir|desaparecer para siempre|no existir|no despertar(?:me)?|dormir para siempre)',
|
|
277
|
+
'(?:quisiera|ojala|preferiria) no (?:existir|haber nacido|despertar(?:me)?|estar viv(?:o|a))',
|
|
278
|
+
'nadie me (?:va a extranar|va a echar de menos|extranaria|extranara|echaria de menos|echara de menos|va a notar|notaria)',
|
|
279
|
+
'a nadie le (?:importa|importaria|va a importar|importara|importo) si (?:me muero|muero|me muriera|me mato|me matara|desaparezco|desapareciera|ya no estoy|dejo de existir|no estoy)',
|
|
280
|
+
'(?:soy|me siento como|me siento|siento que soy) (?:solo |solamente )?(?:una|un) carga',
|
|
281
|
+
'me (?:corto|corte|lastimo|hago dano|hice dano) (?:para|porque|cuando|a proposito)',
|
|
282
|
+
'me (?:hago|estoy haciendo) dano',
|
|
283
|
+
'me estoy (?:cortando|lastimando)',
|
|
284
|
+
'matarme',
|
|
285
|
+
// "Me gustaría matar a mi jefe" is about someone else; "me voy a matar" is not.
|
|
286
|
+
`me ${INTENT_ES} matar(?! (?:a|al|el|la|los|las|un|una|el tiempo))`,
|
|
287
|
+
`me ${INTENT_ES} (?:suicidar|ahorcar|envenenar|quitar la vida|cortar las venas|cortar)`,
|
|
288
|
+
`me ${INTENT_ES} colgar(?! (?:el|la|los|las|un|una|unos|unas|mi|mis|tu|tus|su|sus))`,
|
|
289
|
+
`me ${INTENT_ES} disparar(?! (?:una|un|la|el|unas|unos) (?:foto|fotos|flash|camara|selfie))`,
|
|
290
|
+
`me ${INTENT_ES} (?:tirar|aventar|lanzar|arrojar) ${FALL_PLACE_ES}`,
|
|
291
|
+
'suicid(?:arme|arse|arte|ar)',
|
|
292
|
+
'suicidas?',
|
|
293
|
+
'(?:pensando|pienso|pensado|pensar|pense) en (?:el )?suicidio',
|
|
294
|
+
'(?:intento|intentos|intente|intentar) (?:de )?suicid(?:io|arme)',
|
|
295
|
+
'nota (?:de )?suicid(?:io|a)',
|
|
296
|
+
'linea (?:de )?(?:prevencion del )?suicidio',
|
|
297
|
+
'quitarme la vida',
|
|
298
|
+
'(?:acabar|terminar|poner fin|ponerle fin) (?:con |a )?mi (?:propia )?vida',
|
|
299
|
+
'acabar con todo(?! (?:el|la|lo|los|las|mi|tu|su))',
|
|
300
|
+
'(?:estaria|estarian|estarias|estariamos|estara|estaran|serian|seria|estan|estarian todos) mejor(?:es)? sin mi',
|
|
301
|
+
'(?:mejor|estaria mejor|estarias mejor) (?:estar )?muert(?:o|a)',
|
|
302
|
+
'(?:quiero|voy a|pienso|ganas de|necesito|tengo ganas de|vuelvo a|volvi a|empece a) (?:hacerme dano|lastimarme|cortarme|autolesionarme|herirme|quemarme|castigarme)',
|
|
303
|
+
'autolesi(?:on|ones|onarme|onandome|ono|one)',
|
|
304
|
+
'me (?:corto|corte|estoy cortando) (?:los brazos|las munecas|las piernas|otra vez|de nuevo)',
|
|
305
|
+
'cortarme (?:los brazos|las venas|las munecas|las piernas)',
|
|
306
|
+
'ahorcarme',
|
|
307
|
+
'colgarme (?:de|del)',
|
|
308
|
+
'(?:pegarme|darme|meterme) un (?:tiro|balazo)',
|
|
309
|
+
'dispararme',
|
|
310
|
+
'envenenarme',
|
|
311
|
+
`(?:tirarme|lanzarme|aventarme|arrojarme) ${FALL_PLACE_ES}`,
|
|
312
|
+
'(?:tomarme|tomar|tragarme|tragar|me tome|me trague|me voy a tomar) todas (?:las|mis) (?:pastillas|pildoras|medicinas|medicamentos)',
|
|
313
|
+
'(?:tomarme|tragarme|me tome|me trague|tome|trague|me voy a tomar) (?:muchas|un frasco de|un monton de|varias|demasiadas|una caja de|un bote de|todo el frasco de|un punado de) (?:pastillas|pildoras|medicinas|medicamentos|tabletas|capsulas)',
|
|
314
|
+
'sobredosis (?:a proposito|intencional|para morir)',
|
|
315
|
+
'(?:quiero|voy a|pienso|ganas de) (?:tomar(?:me)? )?(?:una )?sobredosis',
|
|
316
|
+
'(?:me tome|tome|me di) una sobredosis',
|
|
317
|
+
'(?:dosis|cantidad) (?:letal|mortal)',
|
|
318
|
+
'(?:cuantas|cuantos|cuanta|cuanto)(?: WORD){0,6} (?:para|hacen falta para|se necesitan para) (?:morir|matarme|matar a alguien|una sobredosis|ser mortal|ser letal)',
|
|
319
|
+
'(?:ya )?no (?:aguanto|puedo|soporto) mas(?! (?:este|esta|estos|estas|ese|esa|esos|esas|el|la|los|las|al|tu|su|sus|tus|de|que|a (?!nadie)|con (?!(?:mi vida|esto|todo|nada|la vida))))',
|
|
320
|
+
'no (?:aguanto|puedo|soporto) mas (?:con )?(?:mi vida|esta vida|la vida|vivir)',
|
|
321
|
+
'(?:me estoy matando|estoy matandome|matandome|me mato) de hambre',
|
|
322
|
+
'de hambre (?:a proposito|para adelgazar|para bajar de peso|para castigarme)',
|
|
323
|
+
'(?:dejar|deje|dejo) de comer (?:a proposito|para (?:morir|desaparecer|castigarme))',
|
|
324
|
+
];
|
|
325
|
+
/** Worry about someone else's risk (Spanish): the card speaks to the helper. */
|
|
326
|
+
const CRISIS_OTHER_ES = [
|
|
327
|
+
'se (?:quiere|va a|piensa|quiso|intento|trato de) (?:matar|suicidar|quitar la vida|ahorcar|envenenar|cortar las venas)',
|
|
328
|
+
'(?:quiere|va a|piensa|intento|intenta|trato de|trata de|amenaza con|amenazo con|habla de|hablo de) (?:matarse|suicidarse|quitarse la vida|ahorcarse|envenenarse|cortarse las venas|hacerse dano|lastimarse|cortarse)',
|
|
329
|
+
'(?:se )?quiere morir(?:se)?',
|
|
330
|
+
'(?:dice|dijo|me dijo) que (?:se )?(?:quiere|va a) (?:morir(?:se)?|matar|suicidar|matarse|suicidarse)',
|
|
331
|
+
'(?:esta|anda|parece|se siente|se ve) (?:muy |bastante |un poco )?suicida',
|
|
332
|
+
];
|
|
333
|
+
/** Immediate danger from someone else, or a medical emergency under way (Spanish). */
|
|
334
|
+
const CRISIS_DANGER_ES = [
|
|
335
|
+
'(?:intenta|intentan|esta intentando|estan intentando|trata de|tratan de|amenaza con|amenazo con|amenazan con|amenazaron con|quiere|quieren|va a|van a|dijo que va a|dice que va a) (?:matarme|lastimarme|violarme|dispararme|apunalarme|secuestrarme|golpearme|matarnos|ahorcarme)',
|
|
336
|
+
'me (?:quiere|quieren|intenta|intentan|esta intentando|estan intentando|va a|van a|trata de|tratan de) (?:matar|violar|secuestrar|lastimar|apunalar|disparar|golpear|ahorcar)',
|
|
337
|
+
'(?:miedo|terror|panico) (?:de )?que me (?:mate|maten|lastime|lastimen|viole|violen|pegue|peguen|golpee|golpeen|haga dano|hagan dano|secuestren)',
|
|
338
|
+
'(?:estoy|estamos) en peligro',
|
|
339
|
+
'mi vida (?:esta|corre) (?:en )?peligro',
|
|
340
|
+
'no estoy a salvo',
|
|
341
|
+
'no me siento (?:segur(?:o|a)|a salvo) en (?:mi )?casa',
|
|
342
|
+
'(?:alguien|un hombre|un tipo|una persona|ladrones|un ladron) (?:entro|esta entrando|se metio|quiere entrar|intenta entrar|se esta metiendo) (?:en|a) (?:mi|la|nuestra) casa',
|
|
343
|
+
'hay (?:alguien|un hombre|un tipo|un intruso|una persona) (?:en|dentro de) mi casa',
|
|
344
|
+
'me (?:estan|esta) (?:siguiendo|persiguiendo|golpeando|pegando|atacando|lastimando|ahorcando|violando)(?! (?:en|por) (?:minds|twitter|x|instagram|tiktok|facebook|redes|la app|aqui))',
|
|
345
|
+
'me (?:secuestraron|tienen secuestrad(?:o|a)|tienen encerrad(?:o|a)|tienen retenid(?:o|a))',
|
|
346
|
+
'estoy secuestrad(?:o|a)',
|
|
347
|
+
'contra mi voluntad',
|
|
348
|
+
`mi ${PERSON_ES} me (?:pega|golpea|ahorca|lastima|maltrata|amenaza|viola|esta pegando|esta golpeando|pego|golpeo|amenazo)`,
|
|
349
|
+
'me (?:violaron|violo|abusaron|abuso|agredieron sexualmente|agredio sexualmente|drogaron|drogo|apunalaron|apunalo|dispararon|balearon)',
|
|
350
|
+
'(?:hay|tenemos) (?:un|una) (?:tirador|tiradora|atacante|hombre armado|persona armada|pistolero)',
|
|
351
|
+
'(?:tiroteo|balacera|disparos) en (?:mi|la|el|nuestra|nuestro) (?:escuela|trabajo|oficina|colegio|universidad|edificio|calle|casa|colonia|barrio)',
|
|
352
|
+
'(?:tiene|trae|saco|sacaron|tienen|traen) (?:una pistola|un cuchillo|un arma|una navaja|un machete|una escopeta)(?! (?:de juguete|de agua|en el juego|en la pelicula|en la serie|en la foto|en el video|en su casa|en casa|guardada|con licencia|con permiso))',
|
|
353
|
+
'(?:no respira|dejo de respirar|esta inconsciente|tuvo una sobredosis|le dio una sobredosis|se paso de pastillas|se tomo todas las pastillas)(?! bien)',
|
|
354
|
+
'(?:llama|llamen|llamar) (?:a la policia|al 911|al 112|a una ambulancia) por mi',
|
|
355
|
+
'(?:pistola|arma|cuchillo|navaja) (?:apuntandome|en mi cabeza|contra mi)',
|
|
356
|
+
];
|
|
357
|
+
/** Idioms that use crisis words for something else, taken out before matching. Kept tight: a message that also says something real still matches on that. */
|
|
358
|
+
const CRISIS_IDIOMS = [
|
|
359
|
+
`(?:kill|killing|killed) ${MYSELF} (?:laughing|with laughter)`,
|
|
360
|
+
'(?:die|died|dying|dead) (?:of|from|with) (?:laughter|laughing|embarrassment|boredom|cringe|shame|cuteness|jealousy|envy|excitement|hunger|thirst|heat|curiosity)',
|
|
361
|
+
'(?:die|died|dying|dyed|dye) (?:my|her|his|their) hair',
|
|
362
|
+
'die on (?:this|that) hill',
|
|
363
|
+
`(?:shoot|shot|shooting) ${MYSELF} in the foot`,
|
|
364
|
+
`(?:cut|cutting) ${MYSELF} (?:a|some|another) (?:slice|piece|break|deal|bit|slack)`,
|
|
365
|
+
`(?:cut|cutting) ${MYSELF} (?:shaving|cooking|chopping|slicing|gardening|by accident|accidentally|off|on (?:a|the|some|my|this) (?:paper|glass|knife|can|lid|edge|nail|fence|razor|tin|bottle|plate|blade))`,
|
|
366
|
+
`accidentally (?:cut|burned|burnt|hurt) ${MYSELF}`,
|
|
367
|
+
`(?:burning|burn|burned|burnt) ${MYSELF} out`,
|
|
368
|
+
`killing ${MYSELF} (?:at|in) (?:the )?(?:gym|work|office)`,
|
|
369
|
+
`killing ${MYSELF} (?:to|trying to) (?:finish|get|make|meet|keep)`,
|
|
370
|
+
'if (?:you|someone|somebody) (?:put|puts|held|holds) a gun to my head',
|
|
371
|
+
'(?:[0-9]+|how many|how much|how far|many|few|several|some|more|less|of|the|in|per|couple|those|these|extra|hundreds of|thousands of) kms',
|
|
372
|
+
'kms (?:per|an hour|a day|away|from|left|long|north|south|east|west|ahead|total|of)',
|
|
373
|
+
`(?:going ${TO}|gonna|will) (?:kill|murder) me when (?:he|she|they) (?:sees|see|finds out|find out|hears|hear|reads|read)`,
|
|
374
|
+
'(?:me (?:va|van) a matar|va a matarme|van a matarme) cuando (?:vea|vean|se entere|se enteren|sepa|sepan|oiga|escuche|lo vea|lo vean|lo sepa|se de cuenta)',
|
|
375
|
+
'(?:el |este |esta |la )?(?:calor|frio|risa|hambre|sed|sueno|tarea|examen|trafico|cansancio|dolor de cabeza|ansiedad|estres|suspenso|dieta) me (?:va|van) a matar',
|
|
376
|
+
'(?:morir|morirme|morirse|morirte|morirnos|muero|muera|muere|mueren|muriendo|murio) de (?:risa|ganas|verguenza|pena|hambre|sed|sueno|calor|frio|aburrimiento|envidia|amor|nervios|celos|ternura)',
|
|
377
|
+
// "Matarse de hambre" is self-starvation, so hunger stays a crisis word with "matar".
|
|
378
|
+
'(?:matar|mata|mato|matarme|matando) de (?:risa|ganas|verguenza|pena|sed|sueno|calor|frio|aburrimiento|envidia|amor|nervios|celos|ternura)',
|
|
379
|
+
'(?:matarme|me mato|matandome|me estoy matando) (?:a )?(?:estudiar|estudiando|trabajar|trabajando|entrenar|entrenando|correr|corriendo|en el gym|en el gimnasio)',
|
|
380
|
+
'(?:cortar|cortarme|corto|corte|cortando|cortandome) (?:el|la|las|los|mi|mis) (?:pelo|cabello|flequillo|fleco|unas|barba|puntas|melena|bigote|pelito)',
|
|
381
|
+
'(?:colgar|colgarme|cuelgo|me cuelgo|me colgue) (?:del|de la|al|a la) (?:wifi|internet|red|senal|telefono|llamada)',
|
|
382
|
+
'(?:atentado|atentados|terrorista|terroristas|ataque|ataques|bombardero|mision|misiones|escuadron|dron|drones) suicidas?',
|
|
383
|
+
'(?:matar|mata) el tiempo',
|
|
384
|
+
];
|
|
385
|
+
// Election lists come in four kinds, so a policy question that names a voting method ("Why do some states ban
|
|
386
|
+
// mail-in ballots?") does not match while the member's own ask does ("How do I get a mail-in ballot?"):
|
|
387
|
+
// - asks: whole questions about the member's own vote, which match on their own;
|
|
388
|
+
// - topics: logistics words that match only a few words after an ask frame ("how do I", "where", "what are the");
|
|
389
|
+
// - context asks: "how do I vote" and the like, which match only when the message also names an election, since on
|
|
390
|
+
// Minds they could be about a poll or a post;
|
|
391
|
+
// - talk (opinions, claims, statistics) vetoes topics and context asks but never a whole ask; votes on Minds itself
|
|
392
|
+
// veto everything.
|
|
393
|
+
/** Where an election name or date ends: not "primary key", not "election results". */
|
|
394
|
+
const ELECTION_EVENT_END = '(?=$| (?:in|for|on|this|next|day|date|held|happening|coming|scheduled|and|or|[0-9]+)(?= |$))';
|
|
395
|
+
const BALLOT_EN = '(?:mail |mail in |absentee |postal |overseas )?ballots?';
|
|
396
|
+
/** Whole election-logistics asks (English). */
|
|
397
|
+
const ELECTION_ASKS_EN = [
|
|
398
|
+
'where (?:do|can|will|must|am) (?:i|we|you)(?: go)? (?:to )?vote',
|
|
399
|
+
'where to vote',
|
|
400
|
+
'where (?:is|s) my (?:polling|voting)',
|
|
401
|
+
'(?:how|where) (?:do|can) (?:i|we|you) (?:register|sign up|get registered) to vote',
|
|
402
|
+
'how to register to vote',
|
|
403
|
+
'am i (?:still )?registered to vote',
|
|
404
|
+
'am i (?:eligible|allowed|able) to vote',
|
|
405
|
+
'can (?:i|we|you) (?:still )?vote (?:if|without|with a|with my|with an|by mail|early|absentee|from abroad|overseas|in another|in a different|after|while|in person|on election day)',
|
|
406
|
+
'can (?:felons|ex felons|former felons|convicted felons|people with (?:a )?(?:felony|felonies|criminal record)|noncitizens|non citizens|green card holders|permanent residents) (?:still )?vote',
|
|
407
|
+
`(?:when (?:is|s|are|re|will be)|whens) (?:the )?(?:next )?(?:election|elections|election day|midterm|midterms|midterm election|midterm elections|general election|runoff|runoff election|primary election|primary elections|primaries|primary)${ELECTION_EVENT_END}`,
|
|
408
|
+
`what (?:day|date) is (?:the )?(?:next )?(?:election|election day|midterm|midterms|primary|general election)${ELECTION_EVENT_END}`,
|
|
409
|
+
'when (?:do|can|must) (?:i|we) vote',
|
|
410
|
+
'when (?:does|do) (?:early )?voting (?:start|begin|end|open|close)',
|
|
411
|
+
`(?:deadline|last day|cutoff|cut off|due date) (?:to|for) (?:vote|voting|register to vote|registering to vote|voter registration|(?:request|requesting|return|returning|mail|mailing|send|sending|apply for|applying for|get|getting|turn in|drop off) (?:a |an |my |the |your )?${BALLOT_EN})`,
|
|
412
|
+
'(?:deadline|last day|cutoff|cut off) (?:to|for) (?:register|registering|registration)(?=$| (?:in|to vote|as a voter|online|by mail|for (?:the )?(?:election|elections|midterms|midterm|primary|primaries|general election|november))(?= |$))',
|
|
413
|
+
`too late (?:to|for) (?:vote|voting|register to vote|registering to vote|voter registration|(?:request|get|mail|send|return) (?:a |an |my )?${BALLOT_EN})`,
|
|
414
|
+
'too late to register(?=$| (?:in|to vote|for (?:the )?(?:election|midterms|primary|november))(?= |$))',
|
|
415
|
+
'register (?:to vote )?(?:on|at) (?:election day|the polls|my polling place)',
|
|
416
|
+
'(?:check|see|find out|know|verify|confirm) (?:if|whether|that) (?:im|i am) (?:still )?registered(?=$| (?:to vote|as a voter|in (?:my |this |the )?(?:state|county|district|precinct))(?= |$))',
|
|
417
|
+
'(?:id|identification|photo id|license|licence|documents|document) (?:do i need|do you need|i need|to bring|should i bring|is needed|is required|are needed|are required) (?:to|for|at) (?:vote|voting|the polls)',
|
|
418
|
+
'(?:do|will) (?:i|you) need (?:an |a |my |photo )?(?:id|identification) to vote',
|
|
419
|
+
'(?:need|have) to (?:show|bring|have|present) (?:an |a |my |photo |a photo )?(?:id|identification|license|licence|drivers license) (?:to|at|when i|when you) (?:vote|the polls|the polling place)',
|
|
420
|
+
'what do i (?:need to )?bring to (?:vote|the polls)',
|
|
421
|
+
'(?:what time|when) (?:do|does) (?:the )?polls (?:open|close)',
|
|
422
|
+
'how late (?:are|is) (?:the )?polls open',
|
|
423
|
+
'are (?:the )?polls (?:still )?open',
|
|
424
|
+
'(?:track|tracking|check|checking) (?:on )?my (?:mail |mail in |absentee |postal )?ballot',
|
|
425
|
+
'(?:did|was|has|is) my (?:mail |mail in |absentee |postal )?ballot (?:arrive|arrived|received|counted|count|been counted|been received|been accepted|been rejected|been processed|been delivered|get counted|get received|accepted|rejected)',
|
|
426
|
+
'(?:know|check|see|find out|confirm|verify|tell) (?:if |whether |that )?my (?:mail |mail in |absentee |postal )?(?:ballot|vote) (?:was|is|has been|got|had been) (?:counted|received|accepted|rejected|processed)',
|
|
427
|
+
'(?:drop|drop off|return|turn in|mail|send|hand in|deliver|submit) my (?:mail |mail in |absentee |postal )?ballot',
|
|
428
|
+
'cure my ballot',
|
|
429
|
+
'(?:what|who|whats|whos) (?:is |s |are |re )?on my (?:ballot|sample ballot)',
|
|
430
|
+
'(?:what|whats|which|where|wheres) (?:is |s )?my (?:voting )?(?:precinct|polling district|voting district|election district)',
|
|
431
|
+
'how old (?:do (?:you|i) (?:have|need) to|must (?:you|i)|should you|to) be (?:to|in order to) vote',
|
|
432
|
+
'(?:lost|stolen|damaged|missing|never got|never received|didnt get|did not get|didnt receive|did not receive|spoiled|ruined|messed up) (?:my )?(?:mail |mail in |absentee |postal )?ballot',
|
|
433
|
+
'ballot (?:was|got|has been|is|went) (?:stolen|lost|damaged|missing|ruined|spoiled)',
|
|
434
|
+
'(?:get|request|order|need) (?:a |an )?(?:replacement|new|another|second|duplicate) (?:mail |mail in |absentee |postal )?ballot',
|
|
435
|
+
'replacement ballot',
|
|
436
|
+
];
|
|
437
|
+
/** Election-logistics words that match only a few words after an ask frame (English). */
|
|
438
|
+
const ELECTION_TOPICS_EN = [
|
|
439
|
+
'register(?:ed|ing)? to vote',
|
|
440
|
+
'(?:sign up|signing up|signed up) to vote',
|
|
441
|
+
'voter (?:registration|registrations|id|identification|roll|rolls)',
|
|
442
|
+
'same day registration',
|
|
443
|
+
'(?:registration|ballot|mail ballot|absentee ballot|voter registration) deadline',
|
|
444
|
+
'polling (?:place|places|station|stations|location|locations|site|sites|booth|booths|hours)',
|
|
445
|
+
'voting (?:location|locations|place|places|site|sites|center|centers|centre|centres|hours|precinct)',
|
|
446
|
+
'ballot drop ?box(?:es)?',
|
|
447
|
+
'vote by mail',
|
|
448
|
+
'voting by mail',
|
|
449
|
+
'mail in (?:ballot|ballots|vote|voting)',
|
|
450
|
+
'mail (?:ballot|ballots)',
|
|
451
|
+
'absentee (?:ballot|ballots|vote|voting|voter)',
|
|
452
|
+
'vote absentee',
|
|
453
|
+
'postal (?:vote|votes|voting|ballot|ballots)',
|
|
454
|
+
'early voting',
|
|
455
|
+
'vote early',
|
|
456
|
+
'voting early',
|
|
457
|
+
'early vote',
|
|
458
|
+
'in person voting',
|
|
459
|
+
'vote in person',
|
|
460
|
+
'provisional ballot',
|
|
461
|
+
'overseas (?:voter|voters|ballot|ballots|voting)',
|
|
462
|
+
'vote from (?:abroad|overseas)',
|
|
463
|
+
'military (?:ballot|ballots)',
|
|
464
|
+
'request (?:a |an |my )?(?:absentee |mail |mail in |postal |overseas )?ballot',
|
|
465
|
+
'get (?:a |an |my )(?:absentee |mail |mail in |postal )ballot',
|
|
466
|
+
'ballot status',
|
|
467
|
+
'sample ballot',
|
|
468
|
+
'ballot (?:cure|curing)',
|
|
469
|
+
];
|
|
470
|
+
/**
|
|
471
|
+
* The member asking (English): a question frame, never a bare pronoun, so "my polling place was full" or "I think
|
|
472
|
+
* voter ID laws are unfair" do not count as asks. "Us" is also the country, and "id" is also "I'd".
|
|
473
|
+
*/
|
|
474
|
+
const ELECTION_FRAMES_EN = '(?:how (?:do|can|could|to|does one)|how far|how long|how (?:many|much) (?:miles|kms|km|kilometers|kilometres|blocks|minutes)|where|wheres|when (?:is|s|are|re|does|do|can|will)|whens|what time|(?:what (?:is|are|s|re)|whats) (?:the|my)|is there|are there|do i|can i|am i|must i|will i|is my|are my|find my|check my|look up my|nearest|closest)';
|
|
475
|
+
/** Asks that match only when the message also names an election (English). */
|
|
476
|
+
const ELECTION_CONTEXT_ASKS_EN = [
|
|
477
|
+
'how (?:do|can|to|does one) (?:i |we |you |one )?vote',
|
|
478
|
+
'how (?:do|can|to) (?:i |we |you )?(?:register|sign up)(?! (?:my|a|an|the|for|your|our|his|her|their|it|this|that|on|with|at)(?= |$))',
|
|
479
|
+
'(?:check|checking|verify) (?:my |if im |if i am |whether im |whether i am )?(?:still )?registration',
|
|
480
|
+
'is my registration (?:active|valid|current|still active|still valid)',
|
|
481
|
+
'(?:check|see|find out|know|verify|confirm) (?:if|whether) (?:im|i am) (?:still )?registered',
|
|
482
|
+
'(?:deadline|last day|cutoff|cut off) (?:to|for) (?:register|registering|registration)',
|
|
483
|
+
'too late to register',
|
|
484
|
+
];
|
|
485
|
+
const ELECTION_CONTEXT_EN = '(?:election|elections|electoral|ballot|ballots|midterm|midterms|primary|primaries|polls|polling|precinct|county|state|governor|senate|senator|congress|congressional|president|presidential|mayor|referendum|november|voter|voters|registrar|clerk|secretary of state|board of elections|by mail|absentee|citizen|citizens|united states|america)';
|
|
486
|
+
/** Whole election-logistics asks (Spanish). */
|
|
487
|
+
const ELECTION_ASKS_ES = [
|
|
488
|
+
'(?:donde|cuando) (?:puedo |tengo que |hay que |me toca |nos toca |se puede |se )?vot(?:ar|o|amos)',
|
|
489
|
+
'(?:como|donde) (?:puedo |me puedo |tengo que )?(?:registrarme|inscribirme|empadronarme) para votar',
|
|
490
|
+
'como (?:me )?(?:registro|inscribo|empadrono) para votar',
|
|
491
|
+
'(?:registrarme|inscribirme|empadronarme|registrarse|inscribirse|empadronarse) (?:para votar|como votante|en el padron electoral|en el censo electoral)',
|
|
492
|
+
'(?:estoy|sigo) (?:registrad(?:o|a)|inscrit(?:o|a)|empadronad(?:o|a)) (?:para votar|en el padron|en el censo)',
|
|
493
|
+
'donde (?:esta|queda) mi (?:casilla|mesa|colegio electoral|local de votacion|puesto de votacion|centro de votacion|lugar de votacion)',
|
|
494
|
+
'(?:cuando|que dia) (?:son|es|seran|sera|van a ser|va a ser) (?:las |la |el )?(?:proximas? )?(?:elecciones|eleccion|primarias|segunda vuelta|jornada electoral|dia de (?:las |la )?(?:elecciones|eleccion))',
|
|
495
|
+
'(?:plazo|fecha limite|ultimo dia) (?:para|de) (?:votar|(?:registrarme|inscribirme|registrarse|inscribirse|empadronarme) (?:para votar|en el padron|como votante)|(?:pedir|solicitar|enviar|mandar|devolver|entregar) (?:mi |la |el |una )?(?:boleta|papeleta|voto)|registro (?:de votantes|electoral)|inscripcion (?:electoral|para votar))',
|
|
496
|
+
'(?:hasta cuando|cuando) (?:puedo|tengo para|hay para|se puede) (?:votar|(?:registrarme|inscribirme|empadronarme) para votar|(?:pedir|solicitar|enviar|mandar|devolver|entregar) (?:mi |la |el )?(?:boleta|papeleta|voto))',
|
|
497
|
+
'que (?:documento|documentos|identificacion|dni|cedula|necesito)(?: WORD){0,3} para (?:ir a )?votar',
|
|
498
|
+
'necesito (?:la |el |mi |una |un )?(?:credencial|identificacion|dni|cedula|documento|ine)(?: WORD){0,2} para votar',
|
|
499
|
+
'puedo votar (?:si|sin|con|desde|por correo|en el extranjero|anticipadamente|en persona|en otro|en otra)',
|
|
500
|
+
'a que hora (?:abren|cierran) (?:las |los )?(?:casillas|urnas|mesas|centros de votacion|colegios electorales)',
|
|
501
|
+
'(?:llego|contaron|contaran|conto|se conto|recibieron) mi (?:voto|boleta|papeleta)',
|
|
502
|
+
'mi (?:voto|boleta|papeleta)(?: por correo)? (?:fue|ha sido|ya fue|se|lo) (?:contad(?:o|a)|contabilizad(?:o|a)|recibid(?:o|a)|contaron|recibieron)',
|
|
503
|
+
'(?:estado|seguimiento|rastrear|rastreo) de mi (?:boleta|papeleta|voto por correo)',
|
|
504
|
+
'(?:donde|como|cuando) (?:deposito|entrego|dejo|envio|mando|devuelvo) mi (?:boleta|papeleta|voto)',
|
|
505
|
+
'(?:depositar|entregar|devolver|enviar|mandar) mi (?:boleta|papeleta|voto por correo)',
|
|
506
|
+
];
|
|
507
|
+
/** Election-logistics words that match only a few words after an ask frame (Spanish). */
|
|
508
|
+
const ELECTION_TOPICS_ES = [
|
|
509
|
+
'(?:registrarme|inscribirme|empadronarme|registrarse|inscribirse|empadronarse) (?:para votar|como votante|en el padron|en el censo electoral)',
|
|
510
|
+
'registro (?:de votantes|electoral)',
|
|
511
|
+
'(?:padron|censo) electoral',
|
|
512
|
+
'consultar? (?:el |mi )?padron',
|
|
513
|
+
'(?:lugar|local|mesa|centro|colegio|casilla|puesto|recinto|sitio) (?:de )?(?:votacion|electoral)',
|
|
514
|
+
'voto (?:por correo|anticipado|en el extranjero|desde el extranjero|ausente|postal|por adelantado|temprano)',
|
|
515
|
+
'votar (?:por correo|anticipadamente|desde el extranjero|en el extranjero|por adelantado|temprano|en persona)',
|
|
516
|
+
'votacion (?:anticipada|por correo|temprana)',
|
|
517
|
+
'(?:boleta|papeleta) (?:por correo|de voto ausente)',
|
|
518
|
+
'(?:solicitar|pedir) (?:la |una |mi )?(?:boleta|papeleta)',
|
|
519
|
+
'credencial (?:para votar|de elector|del ine)',
|
|
520
|
+
'(?:identificacion|documento|dni|cedula) para votar',
|
|
521
|
+
];
|
|
522
|
+
/** The member asking (Spanish): a question frame, never a bare pronoun. */
|
|
523
|
+
const ELECTION_FRAMES_ES = '(?:como (?:puedo|me|se|hago|consigo|pido|solicito|tengo que|hay que)|donde (?:puedo|esta|estan|queda|quedan|se|me|consigo|pido|hay|tengo que)|cuando (?:puedo|es|son|se|abren|cierran|tengo que)|puedo|necesito|tengo que|hay que|que (?:necesito|documento|documentos)|plazo|fecha limite|ultimo dia|hasta cuando|cual es (?:el|la|mi)|cuales son (?:los|las|mis))';
|
|
524
|
+
/** Asks that match only when the message also names an election (Spanish). */
|
|
525
|
+
const ELECTION_CONTEXT_ASKS_ES = [
|
|
526
|
+
'como (?:puedo |tengo que |hay que |se |hago para )?votar',
|
|
527
|
+
'como (?:se vota|voto|votamos)',
|
|
528
|
+
'(?:plazo|fecha limite|ultimo dia) (?:para|de) (?:registrarme|inscribirme|registrarse|inscribirse|empadronarme|registro|inscripcion)',
|
|
529
|
+
'(?:hasta cuando|cuando) (?:puedo|tengo para|hay para|se puede) (?:registrarme|inscribirme|empadronarme)',
|
|
530
|
+
'cuando (?:es|son|sera|seran) (?:la |las )?(?:proximas? )?(?:votacion|votaciones)',
|
|
531
|
+
];
|
|
532
|
+
const ELECTION_CONTEXT_ES = '(?:elecciones|eleccion|electoral|electorales|boleta|papeleta|urna|urnas|casilla|casillas|padron|censo|primarias|presidenciales|legislativas|municipales|intermedias|midterms|condado|(?:mi|tu|su|cada|del|este|que) estado|gobernador|senado|congreso|presidente|alcalde|referendum|plebiscito|noviembre|votante|votantes|ine|ciudadano|ciudadana|estados unidos|eeuu|ee uu)';
|
|
533
|
+
/** Never an election card: votes on Minds itself (a post, a poll, a contest) and other kinds of votes. */
|
|
534
|
+
const ELECTION_NOT = [
|
|
535
|
+
'(?:vote|votes|voting|voted)(?: again)? (?:in|on|for) (?:WORD ){0,3}(?:poll|post|posts|comment|comments|reply|replies|thread|survey|contest|competition|proposal|meme|memes|photo|photos|picture|pictures|video|videos|award|awards|minds|song|songs|episode|player|mvp|oscar|oscars|eurovision|giveaway)',
|
|
536
|
+
'(?:community|group|chat|server|discord|channel) (?:vote|votes|voting|poll|polls)',
|
|
537
|
+
'upvote|upvotes|upvoting|upvoted|downvote|downvotes|downvoting|downvoted|vote (?:up|down)',
|
|
538
|
+
'(?:dao|shareholder|shareholders|proxy|governance|token|onchain|on chain|hoa|board|union|jury|committee) (?:vote|votes|voting|ballot|ballots)',
|
|
539
|
+
'(?:hoa|club|union|board|student|school|class|council|condo|team|company) (?:election|elections)',
|
|
540
|
+
'election (?:for|of) (?:the |our |my )?(?:hoa|club|union|board|class president|student council|condo|team captain)',
|
|
541
|
+
'(?:elecciones|eleccion|votacion|votaciones) (?:del|de la|de los|de mi|de nuestro|de nuestra|en el|en la) (?:club|sindicato|colegio|escuela|clase|grupo|comunidad de vecinos|asociacion|equipo|empresa|consejo estudiantil|condominio)',
|
|
542
|
+
// A single poll is a poll on Minds; elections have "the polls", poll workers and polling places.
|
|
543
|
+
'poll(?! (?:worker|workers|station|stations|place|places|tax|taxes|watcher|watchers|book|books|booth|booths|location|locations|site|sites|hours))',
|
|
544
|
+
'(?:votacion|votaciones|voto|votos|votar|vote|votamos)(?: de nuevo| otra vez)? (?:de|del|por|en|para) (?:WORD ){0,3}(?:encuesta|publicacion|post|posts|comentario|propuesta|meme|memes|concurso|premios|premio|oscar|oscares|eurovision|cancion|canciones|foto|fotos|video|videos|reality|programa|minds|jugador|equipo)',
|
|
545
|
+
'encuesta|encuestas',
|
|
546
|
+
];
|
|
547
|
+
/**
|
|
548
|
+
* Talk about the vote rather than an ask about casting one: opinions, claims of fraud, statistics. It vetoes topic
|
|
549
|
+
* and context matches, never a whole ask ("Is it a scam? Where do I vote?" still gets the card).
|
|
550
|
+
*/
|
|
551
|
+
const ELECTION_TALK = [
|
|
552
|
+
'how (?:should|would|could) (?:i|we|you|one|people) vote',
|
|
553
|
+
'how (?:can|could|do|does|did) (?:you|anyone|people|someone|somebody|they|he|she|americans|voters) (?:even |still |possibly |ever )?vote for',
|
|
554
|
+
'vote (?:for|against) (?:a|an|him|her|them|that|this|someone|somebody|anyone|anybody|such|those|these|trump|harris|biden|vance|walz|obama|desantis|newsom|democrats|republicans|dems|gop|the democrat|the republican|the democrats|the republicans|third party|independent|candidate|candidates|either|neither|who|whom)',
|
|
555
|
+
'(?:i think|i believe|i feel like|in my opinion|imo|imho|do you agree|dont you agree|what do you think|what do you make|your opinion|your take|thoughts on|is it true|true that|is it fair|controversial|arguments|pros and cons|history of|debate)',
|
|
556
|
+
'(?:fraud|fraudulent|rigged|rig the|rigging|hoax|conspiracy|cheat|cheating|cheated|scam|scams|stolen election|stole the election|steal the election|stop the steal|election was stolen|election is stolen)',
|
|
557
|
+
'(?:why|turnout|rejected|rejection rate|statistics|stats|percent|percentage|study|studies|research|compare|compared|comparison|racist|unfair|suppression|biased|ban|bans|banned|banning|should be required|should require|should be allowed|should be banned|should be illegal)',
|
|
558
|
+
'como (?:debo|deberia|deberiamos) votar',
|
|
559
|
+
'(?:votar|voto|vote|votaste|votaria|votarias) (?:por|a favor de|en contra de) (?:alguien|el|ella|ellos|ellas|esa|ese|este|esta|morena|el pan|el pri|sheinbaum|xochitl|trump|harris|milei|petro|lula|bolsonaro|la izquierda|la derecha|un|una|quien)',
|
|
560
|
+
'(?:izquierda o derecha|derecha o izquierda)',
|
|
561
|
+
'(?:creo que|pienso que|en mi opinion|que opinas|que piensas|que te parece|estas de acuerdo|es cierto|es verdad|crees que)',
|
|
562
|
+
'(?:fraude|fraudulent(?:o|a|os|as)|amanad(?:o|a|os|as)|robad(?:o|a|os|as)|robaron|robo|trampa|tramposo|estafa)',
|
|
563
|
+
'(?:por que|participacion|rechazad(?:o|a|os|as)|estadisticas|porcentaje|racista|injusto|prohibir|prohiben|prohibido)',
|
|
564
|
+
];
|
|
565
|
+
// ── Compiling ──
|
|
566
|
+
/**
|
|
567
|
+
* Each letter may repeat ("dieee"); a letter marked optional may repeat or be absent. A run of one letter becomes one
|
|
568
|
+
* quantifier ("ll" reads `l{2,}`, never `l+l+`), so no stretched pattern can backtrack over a run.
|
|
569
|
+
*/
|
|
570
|
+
function stretch(source) {
|
|
571
|
+
if (/\[[^\]]*[a-z]/.test(source) || /[a-z][+*{]/.test(source)) {
|
|
572
|
+
throw new Error('a crisis phrase uses a letter class or a letter quantifier, which stretching would break');
|
|
573
|
+
}
|
|
574
|
+
let out = '';
|
|
575
|
+
let index = 0;
|
|
576
|
+
while (index < source.length) {
|
|
577
|
+
const letter = source[index];
|
|
578
|
+
if (letter < 'a' || letter > 'z') {
|
|
579
|
+
out += letter;
|
|
580
|
+
index += 1;
|
|
581
|
+
continue;
|
|
582
|
+
}
|
|
583
|
+
let required = 0;
|
|
584
|
+
while (index < source.length && source[index] === letter) {
|
|
585
|
+
if (source[index + 1] === '?') {
|
|
586
|
+
index += 2;
|
|
587
|
+
}
|
|
588
|
+
else {
|
|
589
|
+
required += 1;
|
|
590
|
+
index += 1;
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
out += required === 0 ? `${letter}*` : required === 1 ? `${letter}+` : `${letter}{${required},}`;
|
|
594
|
+
}
|
|
595
|
+
return out;
|
|
596
|
+
}
|
|
597
|
+
/**
|
|
598
|
+
* One whole-word alternation over normalised text. Returns the source, not the RegExp: every RegExp below is built at
|
|
599
|
+
* module level from these constant lists, so no caller's string ever reaches the RegExp constructor.
|
|
600
|
+
*/
|
|
601
|
+
function wholeWords(sources, stretched) {
|
|
602
|
+
const body = sources.map((source) => (stretched ? stretch(source) : source)).join('|').replace(/WORD/g, '[a-z0-9]+');
|
|
603
|
+
// A `${` here is a phrase written in quotes instead of a template, its placeholder unfilled.
|
|
604
|
+
if (body.includes('${'))
|
|
605
|
+
throw new Error('fixed card phrase list holds an unfilled template');
|
|
606
|
+
return `(?:^| )(?:${body})(?= |$)`;
|
|
607
|
+
}
|
|
608
|
+
/** Topics a few words after an ask frame, as one pattern: the frame must come before the topic, not just anywhere. */
|
|
609
|
+
const framed = (frames, topics) => `${frames}(?: WORD){0,4} (?:${topics.join('|')})`;
|
|
610
|
+
const MATCHERS = {
|
|
611
|
+
crisis_self: {
|
|
612
|
+
en: new RegExp(wholeWords(CRISIS_SELF_EN, true)),
|
|
613
|
+
es: new RegExp(wholeWords(CRISIS_SELF_ES, true)),
|
|
614
|
+
},
|
|
615
|
+
crisis_other: {
|
|
616
|
+
en: new RegExp(wholeWords(CRISIS_OTHER_EN, true)),
|
|
617
|
+
es: new RegExp(wholeWords(CRISIS_OTHER_ES, true)),
|
|
618
|
+
},
|
|
619
|
+
crisis_other_all: new RegExp(wholeWords([...CRISIS_OTHER_EN, ...CRISIS_OTHER_ES], true), 'g'),
|
|
620
|
+
crisis_danger: {
|
|
621
|
+
en: new RegExp(wholeWords(CRISIS_DANGER_EN, true)),
|
|
622
|
+
es: new RegExp(wholeWords(CRISIS_DANGER_ES, true)),
|
|
623
|
+
},
|
|
624
|
+
crisis_idioms: new RegExp(wholeWords(CRISIS_IDIOMS, false), 'g'),
|
|
625
|
+
election_asks: {
|
|
626
|
+
en: new RegExp(wholeWords(ELECTION_ASKS_EN, false)),
|
|
627
|
+
es: new RegExp(wholeWords(ELECTION_ASKS_ES, false)),
|
|
628
|
+
},
|
|
629
|
+
election_topics: {
|
|
630
|
+
en: new RegExp(wholeWords([framed(ELECTION_FRAMES_EN, ELECTION_TOPICS_EN)], false)),
|
|
631
|
+
es: new RegExp(wholeWords([framed(ELECTION_FRAMES_ES, ELECTION_TOPICS_ES)], false)),
|
|
632
|
+
},
|
|
633
|
+
election_context_asks: {
|
|
634
|
+
en: new RegExp(wholeWords(ELECTION_CONTEXT_ASKS_EN, false)),
|
|
635
|
+
es: new RegExp(wholeWords(ELECTION_CONTEXT_ASKS_ES, false)),
|
|
636
|
+
},
|
|
637
|
+
election_context: {
|
|
638
|
+
en: new RegExp(wholeWords([ELECTION_CONTEXT_EN], false)),
|
|
639
|
+
es: new RegExp(wholeWords([ELECTION_CONTEXT_ES], false)),
|
|
640
|
+
},
|
|
641
|
+
election_not: new RegExp(wholeWords(ELECTION_NOT, false)),
|
|
642
|
+
election_talk: new RegExp(wholeWords(ELECTION_TALK, false)),
|
|
643
|
+
};
|
|
644
|
+
const LANGUAGES = ['en', 'es'];
|
|
645
|
+
const languagesMatching = (matchers, text) => LANGUAGES.filter((language) => matchers[language].test(text));
|
|
646
|
+
function matchCrisis(normal) {
|
|
647
|
+
const text = squeeze(normal).replace(MATCHERS.crisis_idioms, ' ').replace(/ {2,}/g, ' ').trim();
|
|
648
|
+
if (!text)
|
|
649
|
+
return null;
|
|
650
|
+
// Danger leads whenever it shows: its card gives the emergency number first and the crisis line next, so a message
|
|
651
|
+
// that also matched self-harm ("amenaza con matarme") loses nothing.
|
|
652
|
+
const danger = languagesMatching(MATCHERS.crisis_danger, text);
|
|
653
|
+
if (danger.length > 0)
|
|
654
|
+
return { focus: 'danger', languages: danger };
|
|
655
|
+
const other = languagesMatching(MATCHERS.crisis_other, text);
|
|
656
|
+
// Words about someone else are taken out before the member's own are read: "my friend is suicidal" is not "I am".
|
|
657
|
+
const own = other.length > 0 ? text.replace(MATCHERS.crisis_other_all, ' ') : text;
|
|
658
|
+
const self = languagesMatching(MATCHERS.crisis_self, own);
|
|
659
|
+
if (self.length > 0)
|
|
660
|
+
return { focus: 'self_harm', languages: self };
|
|
661
|
+
return other.length > 0 ? { focus: 'someone_else', languages: other } : null;
|
|
662
|
+
}
|
|
663
|
+
function matchElection(normal) {
|
|
664
|
+
if (MATCHERS.election_not.test(normal))
|
|
665
|
+
return null;
|
|
666
|
+
const talk = MATCHERS.election_talk.test(normal);
|
|
667
|
+
const languages = LANGUAGES.filter((language) => MATCHERS.election_asks[language].test(normal)
|
|
668
|
+
|| (!talk && (MATCHERS.election_topics[language].test(normal)
|
|
669
|
+
|| (MATCHERS.election_context_asks[language].test(normal) && MATCHERS.election_context[language].test(normal)))));
|
|
670
|
+
return languages.length > 0 ? languages : null;
|
|
671
|
+
}
|
|
672
|
+
function localeLanguage(locale) {
|
|
673
|
+
if (typeof locale !== 'string')
|
|
674
|
+
return null;
|
|
675
|
+
const language = /^([A-Za-z]{2,3})(?:[-_]|$)/.exec(locale.trim())?.[1]?.toLowerCase();
|
|
676
|
+
return language === 'en' || language === 'es' ? language : null;
|
|
677
|
+
}
|
|
678
|
+
/** The message's language when it matched in one; the app's when it matched in both; else English. */
|
|
679
|
+
function cardLanguage(matched, locale) {
|
|
680
|
+
if (matched.length === 1)
|
|
681
|
+
return matched[0];
|
|
682
|
+
return localeLanguage(locale) ?? 'en';
|
|
683
|
+
}
|
|
684
|
+
/** An ISO 3166-1 alpha-2 code, upper case, or null for anything else. */
|
|
685
|
+
function countryCode(country) {
|
|
686
|
+
if (typeof country !== 'string')
|
|
687
|
+
return null;
|
|
688
|
+
const code = country.trim().toUpperCase();
|
|
689
|
+
// Upper-case keys never name an Object.prototype member, so a plain lookup is safe (and Hermes-safe).
|
|
690
|
+
const resolved = COUNTRY_ALIASES[code] ?? code;
|
|
691
|
+
return /^[A-Z]{2}$/.test(resolved) ? resolved : null;
|
|
692
|
+
}
|
|
693
|
+
/** The table's entry for a two-letter upper-case code (never an Object.prototype member), or null. */
|
|
694
|
+
const entryFor = (table, code) => code === null ? null : (table[code] ?? null);
|
|
695
|
+
/**
|
|
696
|
+
* The fixed card a member's message calls for, or null. Crisis wins over election when a message shows both. Pass
|
|
697
|
+
* the member's own words (their question to George), not the post or chat they are asking about.
|
|
698
|
+
*/
|
|
699
|
+
export function matchFixedCard(text, options = {}) {
|
|
700
|
+
if (typeof text !== 'string' || !text)
|
|
701
|
+
return null;
|
|
702
|
+
// Past the scan limit the head and the tail are read apart, so no phrase is made across the gap between them.
|
|
703
|
+
const parts = scanParts(text).map(normalisePart).filter(Boolean);
|
|
704
|
+
if (parts.length === 0)
|
|
705
|
+
return null;
|
|
706
|
+
for (const normal of parts) {
|
|
707
|
+
const crisis = matchCrisis(normal);
|
|
708
|
+
if (crisis) {
|
|
709
|
+
return { kind: 'crisis', card: crisisCard(countryCode(options.country), cardLanguage(crisis.languages, options.locale), crisis.focus) };
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
for (const normal of parts) {
|
|
713
|
+
const election = matchElection(normal);
|
|
714
|
+
if (election)
|
|
715
|
+
return { kind: 'election', card: electionCard(countryCode(options.country), cardLanguage(election, options.locale)) };
|
|
716
|
+
}
|
|
717
|
+
return null;
|
|
718
|
+
}
|
|
719
|
+
/**
|
|
720
|
+
* A fixed card without reading a message: for a caller that already knows it must show one (the crisis card for a
|
|
721
|
+
* known under-18 member, D10). The language comes from `locale`, else English.
|
|
722
|
+
*/
|
|
723
|
+
export function fixedCardFor(kind, options = {}) {
|
|
724
|
+
const language = localeLanguage(options.locale) ?? 'en';
|
|
725
|
+
const country = countryCode(options.country);
|
|
726
|
+
return kind === 'crisis' ? crisisCard(country, language, 'self_harm') : electionCard(country, language);
|
|
727
|
+
}
|
|
728
|
+
/**
|
|
729
|
+
* What the turn does after a card: a crisis card always shows first and the turn may go on to a supportive answer;
|
|
730
|
+
* an election card replaces the model's answer until ELECTION_CARD_ONLY_UNTIL. `now` is epoch milliseconds or an ISO
|
|
731
|
+
* time from the caller's clock; one that cannot be read gives 'card_only'.
|
|
732
|
+
*/
|
|
733
|
+
export function fixedCardAnswer(kind, now) {
|
|
734
|
+
if (kind === 'crisis')
|
|
735
|
+
return 'card_then_answer';
|
|
736
|
+
const at = typeof now === 'number' ? now : typeof now === 'string' ? Date.parse(now) : Number.NaN;
|
|
737
|
+
if (!Number.isFinite(at))
|
|
738
|
+
return 'card_only';
|
|
739
|
+
return at < Date.parse(ELECTION_CARD_ONLY_UNTIL) ? 'card_only' : 'card_then_answer';
|
|
740
|
+
}
|
|
741
|
+
// ── Cards ──
|
|
742
|
+
const hostOf = (url) => url.replace(/^https:\/\//, '').replace(/^www\./, '').replace(/\/.*$/, '');
|
|
743
|
+
function phoneOf(line) {
|
|
744
|
+
return { name: line.name, number: line.number, dial: line.dial, text: line.text, emergency: false, sources: [...line.sources] };
|
|
745
|
+
}
|
|
746
|
+
function emergencyPhoneOf(name, number, sources) {
|
|
747
|
+
return { name, number, dial: number, text: null, emergency: true, sources: [...sources] };
|
|
748
|
+
}
|
|
749
|
+
/** "call or text 988", "call 116 123 (Samaritans)", "llamar al 13 11 14 (Lifeline) o enviar un mensaje de texto al 0477 13 11 14". */
|
|
750
|
+
function reach(line, language) {
|
|
751
|
+
const named = line.name.includes(line.number) ? line.number : `${line.number} (${line.name})`;
|
|
752
|
+
const textSame = line.text !== null && line.text.replace(/\D/g, '') === line.dial.replace(/\D/g, '');
|
|
753
|
+
if (language === 'es') {
|
|
754
|
+
if (line.text === null)
|
|
755
|
+
return `llamar al ${named}`;
|
|
756
|
+
return textSame ? `llamar o enviar un mensaje de texto al ${named}` : `llamar al ${named} o enviar un mensaje de texto al ${line.text}`;
|
|
757
|
+
}
|
|
758
|
+
if (line.text === null)
|
|
759
|
+
return `call ${named}`;
|
|
760
|
+
return textSame ? `call or text ${named}` : `call ${named} or text ${line.text}`;
|
|
761
|
+
}
|
|
762
|
+
const CRISIS_COPY = {
|
|
763
|
+
en: {
|
|
764
|
+
title: 'Talk to someone now',
|
|
765
|
+
title_danger: 'Get help now',
|
|
766
|
+
line: (how, where, after, anyTime) => `If you're thinking about hurting yourself, you can ${how} in ${where}${after ? ` and ${after}` : ''}${anyTime ? ', any time' : ''}.`,
|
|
767
|
+
line_other: (how, where, after, anyTime) => `If someone you know is thinking about hurting themselves, they can ${how} in ${where}${after ? ` and ${after}` : ''}${anyTime ? ', any time' : ''}.`,
|
|
768
|
+
danger: (number) => `If you're in danger right now, call ${number}.`,
|
|
769
|
+
danger_two: (first, firstIs, second, secondIs) => `If you're in danger right now, call ${first} (${firstIs}) or ${second} (${secondIs}).`,
|
|
770
|
+
police: 'police',
|
|
771
|
+
ambulance: 'ambulance',
|
|
772
|
+
police_name: 'Police',
|
|
773
|
+
ambulance_name: 'Ambulance',
|
|
774
|
+
emergency_name: 'Emergency services',
|
|
775
|
+
generic: "If you're thinking about hurting yourself, you can talk to someone now.",
|
|
776
|
+
generic_other: 'If someone you know is thinking about hurting themselves, they can talk to someone now.',
|
|
777
|
+
generic_us: 'In the US, call or text 988, any time.',
|
|
778
|
+
directory_unknown: `Elsewhere, ${CRISIS_DIRECTORY.name} lists free helplines in your country.`,
|
|
779
|
+
directory_known: `${CRISIS_DIRECTORY.name} lists free helplines in your country.`,
|
|
780
|
+
generic_danger: "If you're in danger right now, call your local emergency number.",
|
|
781
|
+
},
|
|
782
|
+
es: {
|
|
783
|
+
title: 'Habla con alguien ahora',
|
|
784
|
+
title_danger: 'Pide ayuda ahora',
|
|
785
|
+
line: (how, where, after, anyTime) => `Si estás pensando en hacerte daño, puedes ${how} en ${where}${after ? ` y ${after}` : ''}${anyTime ? ', a cualquier hora' : ''}.`,
|
|
786
|
+
line_other: (how, where, after, anyTime) => `Si alguien cercano piensa en hacerse daño, puede ${how} en ${where}${after ? ` y ${after}` : ''}${anyTime ? ', a cualquier hora' : ''}.`,
|
|
787
|
+
danger: (number) => `Si estás en peligro ahora mismo, llama al ${number}.`,
|
|
788
|
+
danger_two: (first, firstIs, second, secondIs) => `Si estás en peligro ahora mismo, llama al ${first} (${firstIs}) o al ${second} (${secondIs}).`,
|
|
789
|
+
police: 'policía',
|
|
790
|
+
ambulance: 'ambulancia',
|
|
791
|
+
police_name: 'Policía',
|
|
792
|
+
ambulance_name: 'Ambulancia',
|
|
793
|
+
emergency_name: 'Emergencias',
|
|
794
|
+
generic: 'Si estás pensando en hacerte daño, puedes hablar con alguien ahora.',
|
|
795
|
+
generic_other: 'Si alguien cercano piensa en hacerse daño, puede hablar con alguien ahora.',
|
|
796
|
+
generic_us: 'En EE. UU., llama o envía un mensaje de texto al 988, a cualquier hora (en español: oprime 2, o envía AYUDA).',
|
|
797
|
+
directory_unknown: `En otros países, ${CRISIS_DIRECTORY.name} reúne líneas de ayuda gratuitas de cada país.`,
|
|
798
|
+
directory_known: `${CRISIS_DIRECTORY.name} reúne líneas de ayuda gratuitas de tu país.`,
|
|
799
|
+
generic_danger: 'Si estás en peligro ahora mismo, llama al número de emergencias de tu país.',
|
|
800
|
+
},
|
|
801
|
+
};
|
|
802
|
+
const FOCUS_SUFFIX = { self_harm: '', danger: '.danger', someone_else: '.other' };
|
|
803
|
+
/**
|
|
804
|
+
* The emergency line and phones. Where the country's emergency number reaches an ambulance alone, both it and the
|
|
805
|
+
* police number show: police first on a danger card, the ambulance first otherwise.
|
|
806
|
+
*/
|
|
807
|
+
function emergencyFor(entry, language, focus) {
|
|
808
|
+
const copy = CRISIS_COPY[language];
|
|
809
|
+
const police = entry.police;
|
|
810
|
+
if (!police) {
|
|
811
|
+
return { line: copy.danger(entry.emergency), phones: [emergencyPhoneOf(copy.emergency_name, entry.emergency, entry.emergency_sources)] };
|
|
812
|
+
}
|
|
813
|
+
const policePhone = emergencyPhoneOf(copy.police_name, police.number, police.sources);
|
|
814
|
+
const ambulancePhone = emergencyPhoneOf(copy.ambulance_name, entry.emergency, entry.emergency_sources);
|
|
815
|
+
return focus === 'danger'
|
|
816
|
+
? { line: copy.danger_two(police.number, copy.police, entry.emergency, copy.ambulance), phones: [policePhone, ambulancePhone] }
|
|
817
|
+
: { line: copy.danger_two(entry.emergency, copy.ambulance, police.number, copy.police), phones: [ambulancePhone, policePhone] };
|
|
818
|
+
}
|
|
819
|
+
function crisisCard(country, language, focus) {
|
|
820
|
+
const copy = CRISIS_COPY[language];
|
|
821
|
+
const entry = entryFor(CRISIS_LINES, country);
|
|
822
|
+
const suffix = FOCUS_SUFFIX[focus];
|
|
823
|
+
const title = focus === 'danger' ? copy.title_danger : copy.title;
|
|
824
|
+
const lineCopy = focus === 'someone_else' ? copy.line_other : copy.line;
|
|
825
|
+
const directory = { label: CRISIS_DIRECTORY.name, url: CRISIS_DIRECTORY.url, sources: [...CRISIS_DIRECTORY.sources] };
|
|
826
|
+
if (entry) {
|
|
827
|
+
const [first] = entry.lines;
|
|
828
|
+
const help = [lineCopy(reach(first, language), entry.name[language], first.after_dialling?.[language] ?? null, first.always_open)];
|
|
829
|
+
const extra = first.extra_line?.[language];
|
|
830
|
+
if (extra)
|
|
831
|
+
help.push(extra);
|
|
832
|
+
const emergency = emergencyFor(entry, language, focus);
|
|
833
|
+
return {
|
|
834
|
+
kind: 'crisis',
|
|
835
|
+
id: `crisis.${country}${suffix}`,
|
|
836
|
+
country,
|
|
837
|
+
language,
|
|
838
|
+
title,
|
|
839
|
+
lines: focus === 'danger' ? [emergency.line, ...help] : [...help, emergency.line],
|
|
840
|
+
phones: focus === 'danger' ? [...emergency.phones, phoneOf(first)] : [phoneOf(first), ...emergency.phones],
|
|
841
|
+
links: [],
|
|
842
|
+
as_of: FIXED_CARD_DATA_AS_OF,
|
|
843
|
+
};
|
|
844
|
+
}
|
|
845
|
+
// No checked line for this country (or no country): the US line, labelled, only when the country is unknown.
|
|
846
|
+
const us = CRISIS_LINES.US.lines[0];
|
|
847
|
+
const generic = focus === 'someone_else' ? copy.generic_other : copy.generic;
|
|
848
|
+
const help = country === null
|
|
849
|
+
? [generic, copy.generic_us, copy.directory_unknown]
|
|
850
|
+
: [generic, copy.directory_known];
|
|
851
|
+
return {
|
|
852
|
+
kind: 'crisis',
|
|
853
|
+
id: `crisis.${country === null ? 'generic' : 'intl'}${suffix}`,
|
|
854
|
+
country,
|
|
855
|
+
language,
|
|
856
|
+
title,
|
|
857
|
+
lines: focus === 'danger' ? [copy.generic_danger, ...help] : [...help, copy.generic_danger],
|
|
858
|
+
phones: country === null ? [phoneOf(us)] : [],
|
|
859
|
+
links: [directory],
|
|
860
|
+
as_of: FIXED_CARD_DATA_AS_OF,
|
|
861
|
+
};
|
|
862
|
+
}
|
|
863
|
+
const ELECTION_COPY = {
|
|
864
|
+
en: {
|
|
865
|
+
title: 'Where, when and how to vote',
|
|
866
|
+
us: "For where, when and how to vote, use your state's official election site.",
|
|
867
|
+
us_find: (sites) => `Find your state's site at ${sites}.`,
|
|
868
|
+
us_rules: 'Rules and deadlines differ by state, so check with your state or local election office.',
|
|
869
|
+
intl: "For where, when and how to vote, use your country's official election site.",
|
|
870
|
+
country: (where, sites) => `In ${where}: ${sites}.`,
|
|
871
|
+
local: "For a regional or local vote, check with that area's election office.",
|
|
872
|
+
generic_us: (sites) => `In the US, find your state's site at ${sites}.`,
|
|
873
|
+
or: ' or ',
|
|
874
|
+
},
|
|
875
|
+
es: {
|
|
876
|
+
title: 'Dónde, cuándo y cómo votar',
|
|
877
|
+
us: 'Para saber dónde, cuándo y cómo votar, consulta el sitio oficial de elecciones de tu estado.',
|
|
878
|
+
us_find: (sites) => `Encuentra el sitio de tu estado en ${sites}.`,
|
|
879
|
+
us_rules: 'Las reglas y los plazos cambian según el estado; confírmalos con la oficina electoral de tu estado o condado.',
|
|
880
|
+
intl: 'Para saber dónde, cuándo y cómo votar, consulta el sitio oficial de elecciones de tu país.',
|
|
881
|
+
country: (where, sites) => `En ${where}: ${sites}.`,
|
|
882
|
+
local: 'Para elecciones regionales o locales, consulta la oficina electoral de esa zona.',
|
|
883
|
+
generic_us: (sites) => `En EE. UU., encuentra el sitio de tu estado en ${sites}.`,
|
|
884
|
+
or: ' o ',
|
|
885
|
+
},
|
|
886
|
+
};
|
|
887
|
+
const linksOf = (entry) => entry.sites.map((site) => ({ label: site.name, url: site.url, sources: [...site.sources] }));
|
|
888
|
+
function electionCard(country, language) {
|
|
889
|
+
const copy = ELECTION_COPY[language];
|
|
890
|
+
const base = { kind: 'election', country, language, title: copy.title, phones: [], as_of: FIXED_CARD_DATA_AS_OF };
|
|
891
|
+
const us = ELECTION_SITES.US;
|
|
892
|
+
const usSites = us.sites.map((site) => site.name).join(copy.or);
|
|
893
|
+
if (country === 'US') {
|
|
894
|
+
return { ...base, id: 'election.US', lines: [copy.us, copy.us_find(usSites), copy.us_rules], links: linksOf(us) };
|
|
895
|
+
}
|
|
896
|
+
const entry = entryFor(ELECTION_SITES, country);
|
|
897
|
+
if (entry) {
|
|
898
|
+
const sites = entry.sites.map((site) => `${site.name} (${hostOf(site.url)})`).join(copy.or);
|
|
899
|
+
return { ...base, id: `election.${country}`, lines: [copy.intl, copy.country(entry.name[language], sites), copy.local], links: linksOf(entry) };
|
|
900
|
+
}
|
|
901
|
+
if (country !== null)
|
|
902
|
+
return { ...base, id: 'election.intl', lines: [copy.intl, copy.local], links: [] };
|
|
903
|
+
return { ...base, id: 'election.generic', lines: [copy.intl, copy.generic_us(usSites)], links: linksOf(us) };
|
|
904
|
+
}
|