2024-08-13 04:53:07 +03:00
|
|
|
import type {
|
|
|
|
TlEntry,
|
|
|
|
TlFullSchema,
|
|
|
|
} from '@mtcute/tl-utils'
|
2024-12-03 09:55:37 +03:00
|
|
|
import type { TlPackedSchema } from './schema.js'
|
|
|
|
import { readFile, writeFile } from 'node:fs/promises'
|
|
|
|
|
|
|
|
import { createInterface } from 'node:readline'
|
|
|
|
import { fileURLToPath } from 'node:url'
|
|
|
|
import { ffetchAddons, ffetchBase } from '@fuman/fetch'
|
|
|
|
import { asyncPool } from '@fuman/utils'
|
2022-08-12 20:11:27 +03:00
|
|
|
import {
|
|
|
|
camelToPascal,
|
2024-02-05 01:44:51 +03:00
|
|
|
jsComment,
|
2024-12-03 09:55:37 +03:00
|
|
|
PRIMITIVE_TO_TS,
|
2023-06-05 03:30:48 +03:00
|
|
|
snakeToCamel,
|
|
|
|
splitNameToNamespace,
|
2022-08-12 20:11:27 +03:00
|
|
|
} from '@mtcute/tl-utils'
|
2024-12-03 09:55:37 +03:00
|
|
|
import * as cheerio from 'cheerio'
|
2023-06-05 03:30:48 +03:00
|
|
|
|
2024-12-03 09:55:37 +03:00
|
|
|
import jsYaml from 'js-yaml'
|
2021-11-23 00:03:59 +03:00
|
|
|
import {
|
|
|
|
API_SCHEMA_JSON_FILE,
|
2024-02-05 01:44:51 +03:00
|
|
|
APP_CONFIG_JSON_FILE,
|
2022-08-12 16:16:44 +03:00
|
|
|
BLOGFORK_DOMAIN,
|
2024-08-13 04:53:07 +03:00
|
|
|
CORE_DOMAIN,
|
2024-12-03 09:55:37 +03:00
|
|
|
COREFORK_DOMAIN,
|
2021-11-23 00:03:59 +03:00
|
|
|
DESCRIPTIONS_YAML_FILE,
|
|
|
|
DOC_CACHE_FILE,
|
2023-10-16 19:23:53 +03:00
|
|
|
} from './constants.js'
|
|
|
|
import { applyDescriptionsYamlFile } from './process-descriptions-yaml.js'
|
2024-08-13 04:53:07 +03:00
|
|
|
import { packTlSchema, unpackTlSchema } from './schema.js'
|
2021-11-23 00:03:59 +03:00
|
|
|
|
|
|
|
export interface CachedDocumentationEntry {
|
|
|
|
comment?: string
|
|
|
|
arguments?: Record<string, string>
|
|
|
|
throws?: TlEntry['throws']
|
|
|
|
available?: TlEntry['available']
|
|
|
|
}
|
|
|
|
|
|
|
|
export interface CachedDocumentation {
|
|
|
|
updated: string
|
|
|
|
classes: Record<string, CachedDocumentationEntry>
|
|
|
|
methods: Record<string, CachedDocumentationEntry>
|
|
|
|
unions: Record<string, string>
|
|
|
|
}
|
|
|
|
|
2024-11-19 18:35:02 +03:00
|
|
|
const ffetch = ffetchBase.extend({
|
|
|
|
addons: [
|
|
|
|
ffetchAddons.retry(),
|
|
|
|
ffetchAddons.timeout(),
|
|
|
|
],
|
|
|
|
timeout: 30_000,
|
|
|
|
})
|
|
|
|
|
2023-09-24 01:32:22 +03:00
|
|
|
function normalizeLinks(url: string, el: cheerio.Cheerio<cheerio.Element>): void {
|
2021-11-23 00:03:59 +03:00
|
|
|
el.find('a').each((i, _it) => {
|
2023-06-05 03:30:48 +03:00
|
|
|
const it = cheerio.default(_it)
|
|
|
|
let href = it.attr('href')
|
|
|
|
if (!href) return
|
2021-11-23 00:03:59 +03:00
|
|
|
|
2023-06-05 03:30:48 +03:00
|
|
|
if (href[0] === '#') return
|
2021-11-23 00:03:59 +03:00
|
|
|
|
2023-06-05 03:30:48 +03:00
|
|
|
href = new URL(href, url).href
|
|
|
|
it.attr('href', href)
|
2021-11-23 00:03:59 +03:00
|
|
|
|
|
|
|
let m
|
2023-06-05 03:30:48 +03:00
|
|
|
|
2023-09-24 01:32:22 +03:00
|
|
|
if ((m = href.match(/\/(constructor|method|union)\/([^#?]+)(?:\?|#|$)/))) {
|
2023-06-05 03:30:48 +03:00
|
|
|
const [, type, name] = m
|
2022-05-09 17:18:18 +03:00
|
|
|
const [ns, n] = splitNameToNamespace(name)
|
2022-08-12 17:46:04 +03:00
|
|
|
|
|
|
|
if (PRIMITIVE_TO_TS[n]) {
|
|
|
|
it.replaceWith(PRIMITIVE_TO_TS[n])
|
2023-06-05 03:30:48 +03:00
|
|
|
|
2022-08-12 17:46:04 +03:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2022-05-09 17:18:18 +03:00
|
|
|
let q = camelToPascal(snakeToCamel(n))
|
|
|
|
|
|
|
|
if (type === 'method' || type === 'constructor') {
|
2024-08-13 04:53:07 +03:00
|
|
|
q = `Raw${q}${type === 'method' ? 'Request' : ''}`
|
2022-05-09 17:18:18 +03:00
|
|
|
} else {
|
2024-08-13 04:53:07 +03:00
|
|
|
q = `Type${q}`
|
2021-11-23 00:03:59 +03:00
|
|
|
}
|
|
|
|
|
2024-08-13 04:53:07 +03:00
|
|
|
const fullName = ns ? `${ns}.${q}` : q
|
2022-05-09 17:18:18 +03:00
|
|
|
|
2023-06-05 03:30:48 +03:00
|
|
|
it.replaceWith(`{@link ${fullName}}`)
|
2021-11-23 00:03:59 +03:00
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2023-10-03 19:34:12 +03:00
|
|
|
function unescapeHtml(text: string) {
|
|
|
|
return text
|
|
|
|
.replace(/</g, '<')
|
|
|
|
.replace(/>/g, '>')
|
|
|
|
.replace(/"/g, '"')
|
|
|
|
.replace(/ /g, ' ')
|
|
|
|
.trim()
|
|
|
|
}
|
|
|
|
|
2023-06-05 03:30:48 +03:00
|
|
|
function extractDescription($: cheerio.CheerioAPI) {
|
2021-11-23 00:03:59 +03:00
|
|
|
return $('.page_scheme')
|
|
|
|
.prevAll('p')
|
|
|
|
.get()
|
|
|
|
.reverse()
|
2024-08-13 04:53:07 +03:00
|
|
|
.map(el => $(el).html()?.trim())
|
2023-06-05 03:30:48 +03:00
|
|
|
.filter(Boolean)
|
2021-11-23 00:03:59 +03:00
|
|
|
.join('\n\n')
|
|
|
|
.trim()
|
|
|
|
}
|
|
|
|
|
2024-02-05 01:44:51 +03:00
|
|
|
function htmlAll($: cheerio.CheerioAPI, search: cheerio.Cheerio<cheerio.Element>) {
|
|
|
|
return search
|
|
|
|
.get()
|
2024-08-13 04:53:07 +03:00
|
|
|
.map(el => $(el).html() ?? '')
|
2024-02-05 01:44:51 +03:00
|
|
|
.join('')
|
|
|
|
}
|
|
|
|
|
2021-11-23 00:03:59 +03:00
|
|
|
// from https://github.com/sindresorhus/cli-spinners/blob/main/spinners.json
|
|
|
|
const PROGRESS_CHARS = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏']
|
|
|
|
|
2023-09-24 01:32:22 +03:00
|
|
|
async function chooseDomainForDocs(headers: Record<string, string>): Promise<[number, string]> {
|
2022-08-12 16:16:44 +03:00
|
|
|
let maxLayer = 0
|
|
|
|
let maxDomain = ''
|
|
|
|
|
|
|
|
for (const domain of [CORE_DOMAIN, COREFORK_DOMAIN, BLOGFORK_DOMAIN]) {
|
2024-11-19 18:35:02 +03:00
|
|
|
const index = await ffetch(`${domain}/schema`, { headers }).text()
|
2023-06-05 03:30:48 +03:00
|
|
|
const layerMatch = cheerio
|
|
|
|
.load(index)('.dev_layer_select .dropdown-toggle')
|
|
|
|
.text()
|
|
|
|
.match(/layer (\d+)/i)
|
|
|
|
|
|
|
|
if (!layerMatch) {
|
|
|
|
throw new Error(`Failed to parse layer from ${domain}`)
|
|
|
|
}
|
|
|
|
|
2024-08-13 04:53:07 +03:00
|
|
|
const actualLayer = Number.parseInt(layerMatch[1])
|
2022-08-12 16:16:44 +03:00
|
|
|
|
|
|
|
if (actualLayer > maxLayer) {
|
|
|
|
maxLayer = actualLayer
|
|
|
|
maxDomain = domain
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return [maxLayer, maxDomain]
|
|
|
|
}
|
|
|
|
|
2024-02-05 01:44:51 +03:00
|
|
|
function lastParensGroup(text: string): string | undefined {
|
|
|
|
const groups = []
|
|
|
|
let depth = 0
|
|
|
|
let current = ''
|
|
|
|
|
|
|
|
for (let i = 0; i < text.length; i++) {
|
|
|
|
if (text[i] === ')') depth--
|
|
|
|
|
|
|
|
if (depth > 0) {
|
|
|
|
current += text[i]
|
|
|
|
}
|
|
|
|
|
|
|
|
if (text[i] === '(') depth++
|
|
|
|
|
|
|
|
if (current && depth === 0) {
|
|
|
|
groups.push(current)
|
|
|
|
current = ''
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return groups[groups.length - 1]
|
|
|
|
}
|
|
|
|
|
|
|
|
async function fetchAppConfigDocumentation() {
|
|
|
|
const headers = {
|
|
|
|
'User-Agent':
|
2024-08-13 04:53:07 +03:00
|
|
|
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) '
|
|
|
|
+ 'Chrome/87.0.4280.88 Safari/537.36',
|
2024-02-05 01:44:51 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
const [, domain] = await chooseDomainForDocs(headers)
|
|
|
|
|
2024-11-19 18:35:02 +03:00
|
|
|
const page = await ffetch(`${domain}/api/config`, { headers }).text()
|
2024-02-05 01:44:51 +03:00
|
|
|
const $ = cheerio.load(page)
|
|
|
|
|
|
|
|
const fields = $('p:icontains(typical fields included)').nextUntil('h3')
|
|
|
|
normalizeLinks(`${domain}/api/config`, fields)
|
|
|
|
const fieldNames = fields.filter('h4')
|
|
|
|
|
|
|
|
const _example = $('p:icontains(example value)').next('pre').find('code')
|
|
|
|
const example = JSON.parse(_example.text().trim()) as Record<string, unknown>
|
|
|
|
|
|
|
|
const result: Record<string, unknown> = {}
|
|
|
|
|
|
|
|
function valueToTypescript(value: unknown, record = false): string {
|
|
|
|
if (value === undefined) return 'unknown'
|
|
|
|
if (value === null) return 'null'
|
|
|
|
|
|
|
|
if (Array.isArray(value)) {
|
2024-08-13 04:53:07 +03:00
|
|
|
const types = new Set(value.map(v => typeof v))
|
2024-02-05 01:44:51 +03:00
|
|
|
|
|
|
|
if (types.size === 1) {
|
2024-08-13 04:53:07 +03:00
|
|
|
return `${valueToTypescript(value[0])}[]`
|
2024-02-05 01:44:51 +03:00
|
|
|
}
|
|
|
|
|
2024-08-13 04:53:07 +03:00
|
|
|
return `(${value.map(v => valueToTypescript(v)).join(' | ')})[]`
|
2024-02-05 01:44:51 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
if (typeof value === 'object') {
|
|
|
|
if (record) {
|
|
|
|
const inner = Object.values(value)[0] as unknown
|
|
|
|
|
|
|
|
return `Record<string, ${valueToTypescript(inner)}>`
|
|
|
|
}
|
|
|
|
|
|
|
|
return (
|
2024-08-13 04:53:07 +03:00
|
|
|
`{\n${
|
2024-02-05 01:44:51 +03:00
|
|
|
Object.entries(value)
|
|
|
|
.map(([k, v]) => ` ${k}: ${valueToTypescript(v)}`)
|
2024-08-13 04:53:07 +03:00
|
|
|
.join('\n')
|
|
|
|
}\n}`
|
2024-02-05 01:44:51 +03:00
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
return typeof value
|
|
|
|
}
|
|
|
|
|
|
|
|
function docsTypeToTypescript(field: string, type: string): string {
|
|
|
|
let m
|
|
|
|
|
|
|
|
if ((m = type.match(/(.*), defaults to .+$/i))) {
|
|
|
|
return docsTypeToTypescript(field, m[1])
|
|
|
|
}
|
|
|
|
|
2024-08-13 04:53:07 +03:00
|
|
|
if ((m = type.match(/^array of (.+?)s?$/i))) {
|
|
|
|
return `${docsTypeToTypescript(field, m[1])}[]`
|
2024-02-05 01:44:51 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
switch (type) {
|
|
|
|
case 'integer':
|
|
|
|
return 'number'
|
|
|
|
case 'itneger':
|
|
|
|
return 'number'
|
|
|
|
case 'float':
|
|
|
|
return 'number'
|
|
|
|
case 'string':
|
|
|
|
return 'string'
|
|
|
|
case 'string emoji':
|
|
|
|
return 'string'
|
|
|
|
case 'boolean':
|
|
|
|
return 'boolean'
|
|
|
|
case 'bool':
|
|
|
|
return 'boolean'
|
|
|
|
}
|
|
|
|
|
|
|
|
if (type.match(/^object with .+? keys|^map of/i)) {
|
|
|
|
return valueToTypescript(example[field], true)
|
|
|
|
}
|
|
|
|
|
|
|
|
if (type.match(/^strings?, /)) {
|
|
|
|
if (type.includes('or')) {
|
|
|
|
const options = type.slice(8).split(/, | or /)
|
|
|
|
|
2024-08-13 04:53:07 +03:00
|
|
|
return options.map(o => (o[0] === '"' ? o : JSON.stringify(o))).join(' | ')
|
2024-02-05 01:44:51 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
return 'string'
|
|
|
|
}
|
|
|
|
|
|
|
|
if (type.includes(',')) {
|
|
|
|
return docsTypeToTypescript(field, type.split(',')[0])
|
|
|
|
}
|
|
|
|
|
|
|
|
if (type.match(/^numeric string/)) {
|
|
|
|
return 'string'
|
|
|
|
}
|
|
|
|
|
|
|
|
if (type.includes('as described')) {
|
|
|
|
return valueToTypescript(example[field])
|
|
|
|
}
|
|
|
|
|
|
|
|
console.log(`Failed to parse type at ${field}: ${type}`)
|
|
|
|
|
|
|
|
return valueToTypescript(example[field])
|
|
|
|
}
|
|
|
|
|
|
|
|
for (const fieldName of fieldNames.toArray()) {
|
|
|
|
const name = $(fieldName).text().trim()
|
|
|
|
const description = htmlAll($, $(fieldName).nextUntil('h3, h4'))
|
|
|
|
let type = 'unknown'
|
|
|
|
|
|
|
|
let typeStr = lastParensGroup(description)
|
|
|
|
|
|
|
|
if (!typeStr) {
|
|
|
|
typeStr = description.match(/\s+\((.+?)(?:\)|\.|\)\.)$/)?.[1]
|
|
|
|
}
|
|
|
|
|
|
|
|
if (typeStr) {
|
|
|
|
type = docsTypeToTypescript(name, typeStr)
|
|
|
|
} else if (name in example) {
|
|
|
|
type = valueToTypescript(example[name])
|
|
|
|
}
|
|
|
|
|
|
|
|
result[name] = {
|
|
|
|
type,
|
|
|
|
description: jsComment(description),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return result
|
|
|
|
}
|
|
|
|
|
2021-11-23 00:03:59 +03:00
|
|
|
export async function fetchDocumentation(
|
|
|
|
schema: TlFullSchema,
|
|
|
|
layer: number,
|
2024-08-18 09:31:23 +03:00
|
|
|
silent: boolean = !process.stdout.isTTY,
|
2021-11-23 00:03:59 +03:00
|
|
|
): Promise<CachedDocumentation> {
|
|
|
|
const headers = {
|
2024-08-13 04:53:07 +03:00
|
|
|
'cookie': `stel_dev_layer=${layer}`,
|
2021-11-23 00:03:59 +03:00
|
|
|
'User-Agent':
|
2024-08-13 04:53:07 +03:00
|
|
|
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) '
|
|
|
|
+ 'Chrome/87.0.4280.88 Safari/537.36',
|
2021-11-23 00:03:59 +03:00
|
|
|
}
|
|
|
|
|
2022-08-12 16:16:44 +03:00
|
|
|
const [actualLayer, domain] = await chooseDomainForDocs(headers)
|
|
|
|
|
|
|
|
console.log('Using domain %s (has layer %s)', domain, actualLayer)
|
2022-05-09 17:18:18 +03:00
|
|
|
|
2021-11-23 00:03:59 +03:00
|
|
|
const ret: CachedDocumentation = {
|
2023-09-24 01:32:22 +03:00
|
|
|
updated: `${new Date().toLocaleString('ru-RU')} (layer ${actualLayer}) - from ${domain}`,
|
2021-11-23 00:03:59 +03:00
|
|
|
classes: {},
|
|
|
|
methods: {},
|
|
|
|
unions: {},
|
|
|
|
}
|
|
|
|
|
|
|
|
let prevSize = 0
|
|
|
|
let logPos = 0
|
|
|
|
|
|
|
|
function log(str: string) {
|
|
|
|
if (silent) return
|
2023-11-17 17:26:45 +03:00
|
|
|
const oldPrevSize = prevSize
|
|
|
|
prevSize = str.length
|
|
|
|
while (str.length < oldPrevSize) str += ' '
|
2021-11-23 00:03:59 +03:00
|
|
|
|
2024-08-13 04:53:07 +03:00
|
|
|
process.stdout.write(`\r${PROGRESS_CHARS[logPos]} ${str}`)
|
2021-11-23 00:03:59 +03:00
|
|
|
|
|
|
|
logPos = (logPos + 1) % PROGRESS_CHARS.length
|
|
|
|
}
|
|
|
|
|
2023-11-17 17:26:45 +03:00
|
|
|
async function fetchDocsForEntry(entry: TlEntry) {
|
2023-09-24 01:32:22 +03:00
|
|
|
const url = `${domain}/${entry.kind === 'class' ? 'constructor' : 'method'}/${entry.name}`
|
2021-11-23 00:03:59 +03:00
|
|
|
|
2024-11-19 18:35:02 +03:00
|
|
|
const html = await ffetch(url, { headers }).text()
|
2021-11-23 00:03:59 +03:00
|
|
|
const $ = cheerio.load(html)
|
|
|
|
const content = $('#dev_page_content')
|
|
|
|
|
2023-11-17 17:26:45 +03:00
|
|
|
if (content.text().trim() === 'The page has not been saved') return
|
2021-11-23 00:03:59 +03:00
|
|
|
|
|
|
|
normalizeLinks(url, content)
|
|
|
|
|
|
|
|
const retClass: CachedDocumentationEntry = {}
|
|
|
|
|
2023-10-03 19:34:12 +03:00
|
|
|
const description = unescapeHtml(extractDescription($))
|
2021-11-23 00:03:59 +03:00
|
|
|
|
|
|
|
if (description) {
|
|
|
|
retClass.comment = description
|
|
|
|
}
|
|
|
|
|
|
|
|
const parametersTable = $('#parameters').parent().next('table')
|
|
|
|
parametersTable.find('tr').each((idx, _el) => {
|
|
|
|
const el = $(_el)
|
|
|
|
const cols = el.find('td')
|
|
|
|
if (!cols.length) return // <thead>
|
|
|
|
|
2022-05-09 17:18:18 +03:00
|
|
|
const name = cols.first().text().trim()
|
2023-10-03 19:34:12 +03:00
|
|
|
const description = unescapeHtml(cols.last().html() ?? '')
|
2021-11-23 00:03:59 +03:00
|
|
|
|
|
|
|
if (description) {
|
|
|
|
if (!retClass.arguments) retClass.arguments = {}
|
|
|
|
retClass.arguments[name] = description
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
if (entry.kind === 'method') {
|
|
|
|
const errorsTable = $('#possible-errors').parent().next('table')
|
|
|
|
|
|
|
|
let userBotRequired = false
|
|
|
|
|
|
|
|
errorsTable.find('tr').each((idx, _el) => {
|
|
|
|
const el = $(_el)
|
2023-06-05 03:30:48 +03:00
|
|
|
const cols = el.find('td')
|
2021-11-23 00:03:59 +03:00
|
|
|
if (!cols.length) return // <thead>
|
|
|
|
|
2024-08-13 04:53:07 +03:00
|
|
|
const code = Number.parseInt($(cols[0]).text())
|
2023-06-05 03:30:48 +03:00
|
|
|
const name = $(cols[1]).text()
|
|
|
|
const comment = $(cols[2]).text()
|
2021-11-23 00:03:59 +03:00
|
|
|
|
|
|
|
if (name === 'USER_BOT_REQUIRED') userBotRequired = true
|
|
|
|
|
|
|
|
if (!retClass.throws) retClass.throws = []
|
|
|
|
retClass.throws.push({ code, name, comment })
|
|
|
|
})
|
|
|
|
|
2023-06-05 03:30:48 +03:00
|
|
|
const botsCanUse = Boolean($('#bots-can-use-this-method').length)
|
2024-08-13 04:53:07 +03:00
|
|
|
const onlyBotsCanUse
|
|
|
|
= botsCanUse && (Boolean(description.match(/[,;]( for)? bots only$/)) || userBotRequired)
|
2021-11-23 00:03:59 +03:00
|
|
|
|
2023-06-05 03:30:48 +03:00
|
|
|
if (onlyBotsCanUse) {
|
|
|
|
retClass.available = 'bot'
|
|
|
|
} else if (botsCanUse) {
|
|
|
|
retClass.available = 'both'
|
|
|
|
} else {
|
|
|
|
retClass.available = 'user'
|
|
|
|
}
|
2021-11-23 00:03:59 +03:00
|
|
|
}
|
|
|
|
|
2023-09-24 01:32:22 +03:00
|
|
|
ret[entry.kind === 'class' ? 'classes' : 'methods'][entry.name] = retClass
|
2024-09-07 18:52:21 +03:00
|
|
|
|
|
|
|
log(`📥 ${entry.kind} ${entry.name}`)
|
2021-11-23 00:03:59 +03:00
|
|
|
}
|
|
|
|
|
2023-11-17 17:26:45 +03:00
|
|
|
async function fetchDocsForUnion(name: string) {
|
2021-11-23 00:03:59 +03:00
|
|
|
log(`📥 union ${name}`)
|
|
|
|
|
2022-08-12 16:16:44 +03:00
|
|
|
const url = `${domain}/type/${name}`
|
2021-11-23 00:03:59 +03:00
|
|
|
|
2024-11-19 18:35:02 +03:00
|
|
|
const html = await ffetch(url, { headers }).text()
|
2021-11-23 00:03:59 +03:00
|
|
|
const $ = cheerio.load(html)
|
|
|
|
const content = $('#dev_page_content')
|
|
|
|
|
2023-11-17 17:26:45 +03:00
|
|
|
if (content.text().trim() === 'The page has not been saved') return
|
2021-11-23 00:03:59 +03:00
|
|
|
|
|
|
|
normalizeLinks(url, content)
|
|
|
|
|
|
|
|
const description = extractDescription($)
|
|
|
|
if (description) ret.unions[name] = description
|
2024-09-07 18:52:21 +03:00
|
|
|
|
|
|
|
log(`📥 union ${name}`)
|
2021-11-23 00:03:59 +03:00
|
|
|
}
|
|
|
|
|
2024-09-07 18:52:21 +03:00
|
|
|
await asyncPool(
|
2023-11-17 17:26:45 +03:00
|
|
|
schema.entries,
|
2024-09-07 18:52:21 +03:00
|
|
|
fetchDocsForEntry,
|
|
|
|
{
|
|
|
|
limit: 16,
|
|
|
|
onError: (item, error) => {
|
|
|
|
console.log(`❌ ${item.kind} ${item.name} (${error})`)
|
|
|
|
return 'throw'
|
|
|
|
},
|
2023-11-17 17:26:45 +03:00
|
|
|
},
|
|
|
|
)
|
|
|
|
|
2024-09-07 18:52:21 +03:00
|
|
|
await asyncPool(
|
2023-11-17 17:26:45 +03:00
|
|
|
Object.keys(schema.unions),
|
2024-09-07 18:52:21 +03:00
|
|
|
fetchDocsForUnion,
|
|
|
|
{
|
|
|
|
limit: 16,
|
|
|
|
onError: (item, error) => {
|
|
|
|
console.log(`❌ union ${item} (${error})`)
|
|
|
|
return 'throw'
|
|
|
|
},
|
2023-11-17 17:26:45 +03:00
|
|
|
},
|
|
|
|
)
|
|
|
|
|
2021-11-23 00:03:59 +03:00
|
|
|
log('✨ Patching descriptions')
|
|
|
|
|
2023-09-24 01:32:22 +03:00
|
|
|
const descriptionsYaml = jsYaml.load(await readFile(DESCRIPTIONS_YAML_FILE, 'utf8'))
|
2021-11-23 00:03:59 +03:00
|
|
|
applyDescriptionsYamlFile(ret, descriptionsYaml)
|
|
|
|
|
|
|
|
log('🔄 Writing to file')
|
|
|
|
|
|
|
|
await writeFile(DOC_CACHE_FILE, JSON.stringify(ret))
|
|
|
|
|
|
|
|
if (!silent) process.stdout.write('\n')
|
|
|
|
|
|
|
|
return ret
|
|
|
|
}
|
|
|
|
|
2024-08-18 09:31:23 +03:00
|
|
|
export function applyDocumentation(schema: TlFullSchema, docs: CachedDocumentation): void {
|
2021-11-23 00:03:59 +03:00
|
|
|
for (let i = 0; i < 2; i++) {
|
|
|
|
const kind = i === 0 ? 'classes' : 'methods'
|
|
|
|
|
|
|
|
const objIndex = schema[kind]
|
|
|
|
const docIndex = docs[kind]
|
|
|
|
|
2023-06-05 03:30:48 +03:00
|
|
|
for (const name in docIndex) {
|
2021-11-23 00:03:59 +03:00
|
|
|
if (!(name in objIndex)) continue
|
|
|
|
|
|
|
|
const obj = objIndex[name]
|
|
|
|
const doc = docIndex[name]
|
|
|
|
|
2023-10-03 19:34:12 +03:00
|
|
|
obj.comment = doc.comment
|
2021-11-23 00:03:59 +03:00
|
|
|
if (doc.throws) obj.throws = doc.throws
|
|
|
|
if (doc.available) obj.available = doc.available
|
2023-06-05 03:30:48 +03:00
|
|
|
|
2021-11-23 00:03:59 +03:00
|
|
|
if (doc.arguments) {
|
2023-06-05 03:30:48 +03:00
|
|
|
const args = doc.arguments
|
2021-11-23 00:03:59 +03:00
|
|
|
obj.arguments.forEach((arg) => {
|
2023-06-05 03:30:48 +03:00
|
|
|
if (arg.name in args) {
|
|
|
|
arg.comment = args[arg.name]
|
2021-11-23 00:03:59 +03:00
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-06-05 03:30:48 +03:00
|
|
|
for (const name in schema.unions) {
|
2021-11-23 00:03:59 +03:00
|
|
|
if (!(name in docs.unions)) continue
|
|
|
|
|
|
|
|
schema.unions[name].comment = docs.unions[name]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export async function getCachedDocumentation(): Promise<CachedDocumentation | null> {
|
|
|
|
try {
|
|
|
|
const file = await readFile(DOC_CACHE_FILE, 'utf8')
|
2023-06-05 03:30:48 +03:00
|
|
|
|
2023-09-03 02:37:51 +03:00
|
|
|
return JSON.parse(file) as CachedDocumentation
|
2023-06-05 03:30:48 +03:00
|
|
|
} catch (e: unknown) {
|
|
|
|
if (e && typeof e === 'object' && 'code' in e && e.code === 'ENOENT') {
|
2021-11-23 00:03:59 +03:00
|
|
|
return null
|
|
|
|
}
|
|
|
|
throw e
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
async function main() {
|
|
|
|
let cached = await getCachedDocumentation()
|
|
|
|
|
|
|
|
if (cached) {
|
2023-10-03 19:34:12 +03:00
|
|
|
console.log('Cached documentation: %s', cached.updated)
|
2021-11-23 00:03:59 +03:00
|
|
|
}
|
|
|
|
|
2023-06-05 03:30:48 +03:00
|
|
|
const rl = createInterface({
|
2021-11-23 00:03:59 +03:00
|
|
|
input: process.stdin,
|
|
|
|
output: process.stdout,
|
|
|
|
})
|
2024-08-13 04:53:07 +03:00
|
|
|
const input = (q: string): Promise<string> => new Promise(res => rl.question(q, res))
|
2021-11-23 00:03:59 +03:00
|
|
|
|
|
|
|
while (true) {
|
|
|
|
console.log('Choose action:')
|
|
|
|
console.log('0. Exit')
|
|
|
|
console.log('1. Update documentation')
|
|
|
|
console.log('2. Apply descriptions.yaml')
|
|
|
|
console.log('3. Apply documentation to schema')
|
2024-02-05 01:44:51 +03:00
|
|
|
console.log('4. Fetch app config documentation')
|
2021-11-23 00:03:59 +03:00
|
|
|
|
2024-08-13 04:53:07 +03:00
|
|
|
const act = Number.parseInt(await input('[0-4] > '))
|
2023-06-05 03:30:48 +03:00
|
|
|
|
2024-08-13 04:53:07 +03:00
|
|
|
if (Number.isNaN(act) || act < 0 || act > 4) {
|
2021-11-23 00:03:59 +03:00
|
|
|
console.log('Invalid action')
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2023-10-03 19:34:12 +03:00
|
|
|
if (act === 0) {
|
|
|
|
rl.close()
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
2021-11-23 00:03:59 +03:00
|
|
|
|
|
|
|
if (act === 1) {
|
|
|
|
const [schema, layer] = unpackTlSchema(
|
2023-09-24 01:32:22 +03:00
|
|
|
JSON.parse(await readFile(API_SCHEMA_JSON_FILE, 'utf8')) as TlPackedSchema,
|
2021-11-23 00:03:59 +03:00
|
|
|
)
|
|
|
|
cached = await fetchDocumentation(schema, layer)
|
|
|
|
}
|
|
|
|
|
|
|
|
if (act === 2) {
|
|
|
|
if (!cached) {
|
|
|
|
console.log('No schema available, fetch it first')
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2023-09-24 01:32:22 +03:00
|
|
|
const descriptionsYaml = jsYaml.load(await readFile(DESCRIPTIONS_YAML_FILE, 'utf8'))
|
2021-11-23 00:03:59 +03:00
|
|
|
applyDescriptionsYamlFile(cached, descriptionsYaml)
|
|
|
|
|
|
|
|
await writeFile(DOC_CACHE_FILE, JSON.stringify(cached))
|
|
|
|
}
|
|
|
|
|
|
|
|
if (act === 3) {
|
|
|
|
if (!cached) {
|
|
|
|
console.log('No schema available, fetch it first')
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
const [schema, layer] = unpackTlSchema(
|
2023-09-24 01:32:22 +03:00
|
|
|
JSON.parse(await readFile(API_SCHEMA_JSON_FILE, 'utf8')) as TlPackedSchema,
|
2021-11-23 00:03:59 +03:00
|
|
|
)
|
|
|
|
|
|
|
|
applyDocumentation(schema, cached)
|
2023-09-24 01:32:22 +03:00
|
|
|
await writeFile(API_SCHEMA_JSON_FILE, JSON.stringify(packTlSchema(schema, layer)))
|
2021-11-23 00:03:59 +03:00
|
|
|
}
|
2024-02-05 01:44:51 +03:00
|
|
|
|
|
|
|
if (act === 4) {
|
|
|
|
const appConfig = await fetchAppConfigDocumentation()
|
|
|
|
|
|
|
|
console.log('Fetched app config documentation')
|
|
|
|
await writeFile(APP_CONFIG_JSON_FILE, JSON.stringify(appConfig))
|
|
|
|
}
|
2021-11-23 00:03:59 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-10-16 19:23:53 +03:00
|
|
|
if (import.meta.url.startsWith('file:')) {
|
|
|
|
const modulePath = fileURLToPath(import.meta.url)
|
|
|
|
|
|
|
|
if (process.argv[1] === modulePath) {
|
|
|
|
main().catch((err) => {
|
|
|
|
console.error(err)
|
|
|
|
process.exit(1)
|
|
|
|
})
|
|
|
|
}
|
2021-11-23 00:03:59 +03:00
|
|
|
}
|