import https from 'https' import querystring from 'querystring' export interface GrammarMatch { offset: number length: number message: string shortMessage: string replacement: string | null ruleId: string categoryId: string } const IGNORED_RULE_IDS = new Set([ 'WHITESPACE_RULE', 'UNPAIRED_BRACKETS', 'EN_QUOTES', 'DASH_RULE', 'WORD_CONTAINS_UNDERSCORE', ]) export async function checkGrammar(text: string, language = 'en-US'): Promise { return new Promise((resolve, reject) => { const body = querystring.stringify({ text, language, enabledOnly: 'false' }) const req = https.request( { hostname: 'api.languagetool.org', path: '/v2/check', method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'Content-Length': Buffer.byteLength(body), 'Accept': 'application/json', }, }, (res) => { let data = '' res.on('data', (chunk) => { data += chunk }) res.on('end', () => { try { const parsed = JSON.parse(data) as { matches: Array<{ offset: number length: number message: string shortMessage: string replacements: Array<{ value: string }> rule: { id: string; category: { id: string } } }> } const matches: GrammarMatch[] = parsed.matches .filter((m) => !IGNORED_RULE_IDS.has(m.rule.id)) .map((m) => ({ offset: m.offset, length: m.length, message: m.message, shortMessage: m.shortMessage || m.message, replacement: m.replacements[0]?.value ?? null, ruleId: m.rule.id, categoryId: m.rule.category.id, })) resolve(matches) } catch (e) { reject(new Error(`LanguageTool parse error: ${String(e)}`)) } }) } ) req.on('error', reject) req.setTimeout(15000, () => { req.destroy(); reject(new Error('Grammar check timed out')) }) req.write(body) req.end() }) }