dograma
Version:
NodeJS/Browser MTProto API Telegram client library,
328 lines (299 loc) • 10.5 kB
JavaScript
// Not sure what they are for.
const WEIRD_TYPES = new Set(["Bool", "X", "Type"]);
module.exports = ({ types, constructors, functions }) => {
function groupByKey(collection, key) {
return collection.reduce((byKey, member) => {
const keyValue = member[key] || "_";
if (!byKey[keyValue]) {
byKey[keyValue] = [member];
} else {
byKey[keyValue].push(member);
}
return byKey;
}, {});
}
function getClassNameWithNameSpace(name, namespace) {
return namespace
? namespace.toLowerCase() + "." + upperFirst(name)
: upperFirst(name);
}
function renderTypes(types, indent) {
return types
.map(({ name, constructors }) =>
`
${!constructors.length ? "// " : ""}export type Type${upperFirst(
name
)} = ${constructors.map((name) => name).join(" | ")};
`.trim()
)
.join(`\n${indent}`);
}
function renderConstructors(constructors, indent) {
return constructors
.map((args) => {
// console.log(args);
const {
name,
namespace,
argsConfig,
constructorId,
subclassOfId,
} = args;
if (name === "Message") {
return `export class Message extends CustomMessage {
${indent}CONSTRUCTOR_ID: ${constructorId};
${indent}SUBCLASS_OF_ID: ${subclassOfId};
${indent}classType: "request";
${indent}className: "${getClassNameWithNameSpace(name, namespace)}";
${indent}static fromReader(reader: Reader): ${upperFirst(name)};
}`;
} else if (name === "MessageService") {
return `export class MessageService extends CustomMessage {
${indent}CONSTRUCTOR_ID: ${constructorId};
${indent}SUBCLASS_OF_ID: ${subclassOfId};
${indent}classType: "request";
${indent}className: "${getClassNameWithNameSpace(name, namespace)}";
${indent}static fromReader(reader: Reader): ${upperFirst(name)};
}`;
}
const argKeys = Object.keys(argsConfig);
// console.log(constructorId);
if (!argKeys.length) {
return `export class ${upperFirst(
name
)} extends VirtualClass<void> {
${indent}CONSTRUCTOR_ID: ${constructorId};
${indent}SUBCLASS_OF_ID: ${subclassOfId};
${indent}classType: "constructor";
${indent}className: "${getClassNameWithNameSpace(name, namespace)}";
${indent}static fromReader(reader: Reader): ${upperFirst(name)};
}`;
}
const hasRequiredArgs = argKeys.some(
(argName) =>
!argsConfig[argName].flagIndicator &&
!argsConfig[argName].isFlag
);
return `
export class ${upperFirst(name)} extends VirtualClass<{
${indent} ${Object.keys(argsConfig)
.map((argName) =>
`
${renderArg(argName, argsConfig[argName])};
`.trim()
)
.join(`\n${indent} `)}
${indent}}${!hasRequiredArgs ? "" : ""}> {
${indent}CONSTRUCTOR_ID: ${constructorId};
${indent}SUBCLASS_OF_ID: ${subclassOfId};
${indent}classType: "constructor";
${indent}className: "${getClassNameWithNameSpace(name, namespace)}";
${indent}static fromReader(reader: Reader): ${upperFirst(name)};
${indent} ${Object.keys(argsConfig)
.map((argName) =>
`
${renderArg(argName, argsConfig[argName])};
`.trim()
)
.join(`\n${indent} `)}
${indent}}`.trim();
})
.join(`\n${indent}`);
}
function renderRequests(requests, indent) {
return requests
.map((args) => {
const {
name,
argsConfig,
result,
constructorId,
namespace,
subclassOfId,
} = args;
const argKeys = Object.keys(argsConfig);
if (!argKeys.length) {
return `export class ${upperFirst(
name
)} extends Request<void, ${renderResult(result)}> {
${indent}CONSTRUCTOR_ID: ${constructorId};
${indent}SUBCLASS_OF_ID: ${subclassOfId};
${indent}classType: "request";
${indent}className: "${getClassNameWithNameSpace(name, namespace)}";
${indent}static fromReader(reader: Reader): ${upperFirst(name)};
}`;
}
const hasRequiredArgs = argKeys.some(
(argName) =>
!argsConfig[argName].flagIndicator &&
!argsConfig[argName].isFlag
);
return `
export class ${upperFirst(name)} extends Request<Partial<{
${indent} ${argKeys
.map((argName) =>
`
${renderArg(argName, argsConfig[argName])};
`.trim()
)
.join(`\n${indent} `)}
${indent}}${!hasRequiredArgs ? "" : ""}>, ${renderResult(result)}> {
${indent}CONSTRUCTOR_ID: ${constructorId};
${indent}SUBCLASS_OF_ID: ${subclassOfId};
${indent}classType: "request";
${indent}className: "${getClassNameWithNameSpace(name, namespace)}";
${indent}static fromReader(reader: Reader): ${upperFirst(name)};
${indent} ${argKeys
.map((argName) =>
`
${renderArg(argName, argsConfig[argName])};
`.trim()
)
.join(`\n${indent} `)}
${indent}}`.trim();
})
.join(`\n${indent}`);
}
function renderResult(result) {
const vectorMatch = result.match(/[Vv]ector<([\w\d.]+)>/);
const isVector = Boolean(vectorMatch);
const scalarValue = isVector ? vectorMatch[1] : result;
const isTlType =
Boolean(scalarValue.match(/^[A-Z]/)) || scalarValue.includes(".");
return renderValueType(scalarValue, isVector, isTlType);
}
function renderArg(argName, argConfig) {
const { isVector, isFlag, skipConstructorId, flagIndicator, type } =
argConfig;
const valueType = renderValueType(type, isVector, !skipConstructorId);
return `${flagIndicator ? "// " : ""}${argName}${
isFlag || (argName === "randomId" && type === "long" && !isVector)
? "?"
: ""
}: ${valueType}`;
}
function renderValueType(type, isVector, isTlType) {
if (WEIRD_TYPES.has(type)) {
return type;
}
let resType;
if (typeof type === "string" && isTlType) {
resType = renderTypeName(type);
} else {
resType = type;
}
if (resType === "true") {
resType = "boolean";
}
if (isVector) {
resType = `${resType}[]`;
}
return resType;
}
function renderTypeName(typeName) {
return typeName.includes(".")
? typeName.replace(".", ".Type")
: `Api.Type${typeName}`;
}
function upperFirst(str) {
return `${str[0].toUpperCase()}${str.slice(1)}`;
}
function lowerFirst(str) {
return `${str[0].toLowerCase()}${str.slice(1)}`;
}
const typesByNs = groupByKey(types, "namespace");
const constructorsByNs = groupByKey(constructors, "namespace");
const requestsByNs = groupByKey(functions, "namespace");
// language=TypeScript
return `
// This file is autogenerated. All changes will be overwritten.
import { BigInteger } from 'big-integer';
import {EntityLike,MessageIDLike} from "../define";
import { CustomMessage } from "./custom/message";
export namespace Api {
type AnyLiteral = Record<string, any> | void;
type Reader = any; // To be defined.
type Client = any; // To be defined.
type Utils = any; // To be defined.
type X = unknown;
type Type = unknown;
type Bool = boolean;
type int = number;
type double = number;
type float = number;
type int128 = BigInteger;
type int256 = BigInteger;
type long = BigInteger;
type bytes = Buffer;
class VirtualClass<Args extends AnyLiteral> {
static CONSTRUCTOR_ID: number;
static SUBCLASS_OF_ID: number;
static className: string;
static classType: 'constructor' | 'request';
static serializeBytes(data: Buffer | string): Buffer;
static serializeDate(date: Date | number): Buffer;
getBytes():Buffer;
CONSTRUCTOR_ID: number;
SUBCLASS_OF_ID: number;
className: string;
classType: 'constructor' | 'request';
constructor(args: Args);
originalArgs: Args;
toJSON(): Args;
}
class Request<Args, Response> extends VirtualClass<Partial<Args>> {
static readResult(reader: Reader): Buffer;
resolve(client: Client, utils: Utils): Promise<void>;
__response: Response;
}
${renderConstructors(constructorsByNs._, " ")}
${renderRequests(requestsByNs._, " ")}
// namespaces
${Object.keys(constructorsByNs)
.map((namespace) =>
namespace !== "_"
? `
export namespace ${namespace} {
${renderConstructors(constructorsByNs[namespace], " ")}
}`
: ""
)
.join("\n")}
${Object.keys(typesByNs)
.map((namespace) =>
namespace !== "_"
? `
export namespace ${namespace} {
${renderTypes(typesByNs[namespace], " ")}
}`
: ""
)
.join("\n")}
${Object.keys(requestsByNs)
.map((namespace) =>
namespace !== "_"
? `
export namespace ${namespace} {
${renderRequests(requestsByNs[namespace], " ")}
}`
: ""
)
.join("\n")}
// Types
export type TypeEntityLike = EntityLike;
${renderTypes(typesByNs._, " ")}
// All requests
export type AnyRequest = ${requestsByNs._.map(({ name }) =>
upperFirst(name)
).join(" | ")}
| ${Object.keys(requestsByNs)
.filter((ns) => ns !== "_")
.map((ns) =>
requestsByNs[ns]
.map(({ name }) => `${ns}.${upperFirst(name)}`)
.join(" | ")
)
.join("\n | ")};
}
`;
};