/ clis / lesswrong / _helpers.js
_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  }