_helpers.js
1 import { CommandExecutionError, EmptyResultError } from '@jackwener/opencli/errors'; 2 export const SITE = 'lesswrong'; 3 export const DOMAIN = 'www.lesswrong.com'; 4 const GRAPHQL_URL = `https://${DOMAIN}/graphql`; 5 // eslint-disable-next-line @typescript-eslint/no-explicit-any -- GraphQL responses vary per query 6 export async function gqlRequest(query) { 7 const resp = await fetch(GRAPHQL_URL, { 8 method: 'POST', 9 headers: { 10 'Content-Type': 'application/json', 11 Accept: 'application/json', 12 }, 13 body: JSON.stringify({ query }), 14 signal: AbortSignal.timeout(15000), 15 }); 16 if (!resp.ok) { 17 throw new CommandExecutionError(`LessWrong API returned HTTP ${resp.status}`); 18 } 19 const json = (await resp.json()); 20 if (json.errors?.length) { 21 throw new CommandExecutionError(json.errors[0]?.message ?? 'Unknown GraphQL error'); 22 } 23 return json.data; 24 } 25 export function gqlEscape(str) { 26 return str.replace(/\\/g, '\\\\').replace(/"/g, '\\"'); 27 } 28 export function stripHtml(html) { 29 if (!html) 30 return ''; 31 return html 32 .replace(/<script[^>]*>.*?<\/script>/gis, ' ') 33 .replace(/<style[^>]*>.*?<\/style>/gis, ' ') 34 .replace(/<[^>]+>/g, ' ') 35 .replace(/\s+/g, ' ') 36 .trim(); 37 } 38 export function daysAgo(n) { 39 const d = new Date(); 40 d.setDate(d.getDate() - n); 41 return d.toISOString(); 42 } 43 export async function resolveTagId(slug) { 44 const normalized = gqlEscape(slug.toLowerCase().trim().replace(/\s+/g, '-')); 45 const query = `query TagBySlug { 46 tags(input: {terms: {view: "tagBySlug", slug: "${normalized}"}}) { 47 results { _id name slug } 48 } 49 }`; 50 const data = await gqlRequest(query); 51 const tag = data?.tags?.results?.[0]; 52 if (!tag?._id || !tag?.name) 53 return null; 54 return { _id: tag._id, name: tag.name }; 55 } 56 export function resolveUserId(slug) { 57 const normalized = gqlEscape(slug.toLowerCase()); 58 const query = `query UserProfile { 59 user(input: {selector: {slug: "${normalized}"}}) { 60 result { _id displayName slug } 61 } 62 }`; 63 return gqlRequest(query).then((data) => { 64 const user = data?.user?.result; 65 if (!user?._id) { 66 throw new EmptyResultError(`lesswrong user ${slug}`, 'Check the username — LessWrong slugs are lowercase (e.g. "zvi", "eliezer-yudkowsky")'); 67 } 68 return { _id: user._id, displayName: (user.displayName ?? '') }; 69 }); 70 } 71 export function parsePostId(urlOrId) { 72 const trimmed = urlOrId.trim(); 73 const match = trimmed.match(/posts\/([a-zA-Z0-9]+)/); 74 return match ? match[1] : trimmed; 75 }