dograma
Version:
NodeJS/Browser MTProto API Telegram client library,
614 lines (574 loc) • 21.9 kB
JavaScript
const { inspect } = require("../inspect");
const bigInt = require("big-integer");
const {
generateRandomBytes,
readBigIntFromBuffer,
isArrayLike,
betterConsoleLog,
} = require("../Helpers");
const tlContent = require("./apiTl.js");
const schemeContent = require("./schemaTl.js");
function generateRandomBigInt() {
return readBigIntFromBuffer(generateRandomBytes(8), false, true);
}
const {
parseTl,
serializeBytes,
serializeDate,
} = require("./generationHelpers");
const { toSignedLittleBuffer } = require("../Helpers");
const NAMED_AUTO_CASTS = new Set(["chatId,int"]);
const NAMED_BLACKLIST = new Set(["discardEncryption"]);
const AUTO_CASTS = new Set([
"InputPeer",
"InputChannel",
"InputUser",
"InputDialogPeer",
"InputNotifyPeer",
"InputMedia",
"InputPhoto",
"InputMessage",
"InputDocument",
"InputChatPhoto",
]);
class CastError extends Error {
constructor(objectName, expected, actual, ...params) {
// Pass remaining arguments (including vendor specific ones) to parent constructor
const message =
"Found wrong type for " +
objectName +
". expected " +
expected +
" but received " +
actual +
".If you think this is a mistake please report it.";
super(message, ...params);
// Maintains proper stack trace for where our error was thrown (only available on V8)
if (Error.captureStackTrace) {
Error.captureStackTrace(this, CastError);
}
this.name = "CastError";
// Custom debugging information
}
}
const CACHING_SUPPORTED =
typeof self !== "undefined" && self.localStorage !== undefined;
const CACHE_KEY = "GramJs:apiCache";
function buildApiFromTlSchema() {
let definitions;
const fromCache = CACHING_SUPPORTED && loadFromCache();
if (fromCache) {
definitions = fromCache;
} else {
definitions = loadFromTlSchemas();
if (CACHING_SUPPORTED) {
localStorage.setItem(CACHE_KEY, JSON.stringify(definitions));
}
}
return createClasses("all", definitions);
}
function loadFromCache() {
const jsonCache = localStorage.getItem(CACHE_KEY);
return jsonCache && JSON.parse(jsonCache);
}
function loadFromTlSchemas() {
const [constructorParamsApi, functionParamsApi] = extractParams(tlContent);
const [constructorParamsSchema, functionParamsSchema] =
extractParams(schemeContent);
const constructors = [].concat(
constructorParamsApi,
constructorParamsSchema
);
const requests = [].concat(functionParamsApi, functionParamsSchema);
return [].concat(constructors, requests);
}
function extractParams(fileContent) {
const f = parseTl(fileContent, 109);
const constructors = [];
const functions = [];
for (const d of f) {
d.isFunction ? functions.push(d) : constructors.push(d);
}
return [constructors, functions];
}
function argToBytes(x, type, argName, requestName) {
switch (type) {
case "int":
const i = Buffer.alloc(4);
i.writeInt32LE(x, 0);
return i;
case "long":
return toSignedLittleBuffer(x, 8);
case "int128":
return toSignedLittleBuffer(x, 16);
case "int256":
return toSignedLittleBuffer(x, 32);
case "double":
const d = Buffer.alloc(8);
d.writeDoubleLE(x, 0);
return d;
case "string":
return serializeBytes(x);
case "Bool":
return x
? Buffer.from("b5757299", "hex")
: Buffer.from("379779bc", "hex");
case "true":
return Buffer.alloc(0);
case "bytes":
return serializeBytes(x);
case "date":
return serializeDate(x);
default:
if (x === undefined || typeof x.getBytes !== "function") {
throw new Error(
`Required object ${argName} of ${requestName} is undefined`
);
}
return x.getBytes();
}
}
async function getInputFromResolve(utils, client, peer, peerType) {
switch (peerType) {
case "InputPeer":
return utils.getInputPeer(await client.getInputEntity(peer));
case "InputChannel":
return utils.getInputChannel(await client.getInputEntity(peer));
case "InputUser":
return utils.getInputUser(await client.getInputEntity(peer));
case "InputDialogPeer":
return await client._getInputDialog(peer);
case "InputNotifyPeer":
return await client._getInputNotify(peer);
case "InputMedia":
return utils.getInputMedia(peer);
case "InputPhoto":
return utils.getInputPhoto(peer);
case "InputMessage":
return utils.getInputMessage(peer);
case "InputDocument":
return utils.getInputDocument(peer);
case "InputChatPhoto":
return utils.getInputChatPhoto(peer);
case "chatId,int":
return await client.getPeerId(peer, false);
default:
throw new Error("unsupported peer type : " + peerType);
}
}
function getArgFromReader(reader, arg) {
if (arg.isVector) {
if (arg.useVectorId) {
reader.readInt();
}
const temp = [];
const len = reader.readInt();
arg.isVector = false;
for (let i = 0; i < len; i++) {
temp.push(getArgFromReader(reader, arg));
}
arg.isVector = true;
return temp;
} else if (arg.flagIndicator) {
return reader.readInt();
} else {
switch (arg.type) {
case "int":
return reader.readInt();
case "long":
return reader.readLong();
case "int128":
return reader.readLargeInt(128);
case "int256":
return reader.readLargeInt(256);
case "double":
return reader.readDouble();
case "string":
return reader.tgReadString();
case "Bool":
return reader.tgReadBool();
case "true":
return true;
case "bytes":
return reader.tgReadBytes();
case "date":
return reader.tgReadDate();
default:
if (!arg.skipConstructorId) {
return reader.tgReadObject();
} else {
return api.constructors[arg.type].fromReader(reader);
}
}
}
}
function compareType(value, type) {
let correct = true;
switch (type) {
case "number":
correct = typeof value === "number" || value === undefined;
break;
case "string":
case "boolean":
correct = typeof value === type;
break;
case "bigInt":
correct =
bigInt.isInstance(value) ||
typeof value === "bigint" ||
typeof value === "number" ||
typeof value === "string" ||
value === undefined;
break;
case "true":
// true value is always correct
break;
case "buffer":
correct = Buffer.isBuffer(value);
break;
case "date":
correct =
(value &&
Object.prototype.toString.call(value) === "[object Date]" &&
!isNaN(value)) ||
typeof value === "number";
break;
default:
console.error(new Error("Unknown type." + type));
}
return correct;
}
function createClasses(classesType, params) {
const classes = {};
for (const classParams of params) {
const {
name,
constructorId,
subclassOfId,
argsConfig,
namespace,
isFunction,
result,
} = classParams;
const fullName = [namespace, name].join(".").replace(/^\./, "");
class VirtualClass {
static CONSTRUCTOR_ID = constructorId;
static SUBCLASS_OF_ID = subclassOfId;
static className = fullName;
static classType = isFunction ? "request" : "constructor";
CONSTRUCTOR_ID = constructorId;
SUBCLASS_OF_ID = subclassOfId;
className = fullName;
classType = isFunction ? "request" : "constructor";
constructor(args) {
args = args || {};
this.originalArgs = args;
this.init(args);
for (const argName in argsConfig) {
if (argName === "randomId" && !args[argName]) {
if (argsConfig[argName].isVector) {
const rands = [];
for (let i = 0; i < args["id"].length; i++) {
rands.push(generateRandomBigInt());
}
this[argName] = rands;
} else {
this[argName] = generateRandomBigInt();
}
} else {
this[argName] = args[argName];
}
}
}
init(args) {}
static fromReader(reader) {
const args = {};
for (const argName in argsConfig) {
if (argsConfig.hasOwnProperty(argName)) {
const arg = argsConfig[argName];
if (arg.isFlag) {
if (arg.type === "true") {
args[argName] = Boolean(
args[arg.flagName] & (1 << arg.flagIndex)
);
continue;
}
if (args[arg.flagName] & (1 << arg.flagIndex)) {
args[argName] = getArgFromReader(reader, arg);
} else {
args[argName] = null;
}
} else {
if (arg.flagIndicator) {
arg.name = argName;
}
args[argName] = getArgFromReader(reader, arg);
}
}
}
return new this(args);
}
validate() {
for (const arg in argsConfig) {
if (argsConfig.hasOwnProperty(arg)) {
if (
argsConfig[arg].flagIndicator ||
argsConfig[arg].isFlag
) {
// we don't care about flags
continue;
}
const currentValue = this[arg];
this.assertType(arg, argsConfig[arg], currentValue);
}
}
}
assertType(objectName, object, value) {
let expected;
if (object["isVector"]) {
if (!isArrayLike(value)) {
console.error(
new CastError(objectName, "array", value)
);
}
if (value == undefined) {
value = [];
}
for (const o of value) {
this.assertType(
objectName,
{ ...object, isVector: false },
o
);
}
} else {
switch (object["type"]) {
case "int":
expected = "number";
break;
case "long":
case "int128":
case "int256":
expected = "bigInt";
break;
case "double":
expected = "number";
break;
case "string":
expected = "string";
break;
case "Bool":
expected = "boolean";
break;
case "true":
expected = "true";
break;
case "bytes":
expected = "buffer";
break;
case "date":
expected = "date";
break;
default:
expected = "object";
}
if (expected === "object") {
// will be validated in get byte();
} else {
const isCorrectType = compareType(value, expected);
if (isCorrectType !== true) {
console.error(
new CastError(objectName, expected, value)
);
}
}
}
}
getBytes() {
try {
this.validate();
} catch (e) {
// feature still in alpha so errors are expected.
}
const idForBytes = this.CONSTRUCTOR_ID;
const c = Buffer.alloc(4);
c.writeUInt32LE(idForBytes, 0);
const buffers = [c];
for (const arg in argsConfig) {
if (argsConfig.hasOwnProperty(arg)) {
if (argsConfig[arg].isFlag) {
if (
(this[arg] === false &&
argsConfig[arg].type !== "Bool") ||
this[arg] === null ||
this[arg] === undefined ||
argsConfig[arg].type === "true"
) {
continue;
}
}
if (argsConfig[arg].isVector) {
if (argsConfig[arg].useVectorId) {
buffers.push(Buffer.from("15c4b51c", "hex"));
}
const l = Buffer.alloc(4);
l.writeInt32LE(this[arg].length, 0);
buffers.push(
l,
Buffer.concat(
this[arg].map((x) =>
argToBytes(
x,
argsConfig[arg].type,
fullName
)
)
)
);
} else if (argsConfig[arg].flagIndicator) {
if (
!Object.values(argsConfig).some((f) => f.isFlag)
) {
buffers.push(Buffer.alloc(4));
} else {
let flagCalculate = 0;
for (const f in argsConfig) {
if (
argsConfig[f].isFlag &&
arg === argsConfig[f].flagName
) {
if (
(this[f] === false &&
argsConfig[f].type !==
"Bool") ||
this[f] === undefined ||
this[f] === null
) {
flagCalculate |= 0;
} else {
flagCalculate |=
1 << argsConfig[f].flagIndex;
}
}
}
const f = Buffer.alloc(4);
f.writeUInt32LE(flagCalculate, 0);
buffers.push(f);
}
} else {
buffers.push(
argToBytes(
this[arg],
argsConfig[arg].type,
arg,
fullName
)
);
if (
this[arg] &&
typeof this[arg].getBytes === "function"
) {
let boxed = argsConfig[arg].type.charAt(
argsConfig[arg].type.indexOf(".") + 1
);
boxed = boxed === boxed.toUpperCase();
if (!boxed) {
buffers.shift();
}
}
}
}
}
return Buffer.concat(buffers);
}
readResult(reader) {
if (!isFunction) {
throw new Error(
"`readResult()` called for non-request instance"
);
}
const m = result.match(/Vector<(int|long)>/);
if (m) {
reader.readInt();
const temp = [];
const len = reader.readInt();
if (m[1] === "int") {
for (let i = 0; i < len; i++) {
temp.push(reader.readInt());
}
} else {
for (let i = 0; i < len; i++) {
temp.push(reader.readLong());
}
}
return temp;
} else {
return reader.tgReadObject();
}
}
async resolve(client, utils) {
if (!isFunction) {
throw new Error(
"`resolve()` called for non-request instance"
);
}
for (const arg in argsConfig) {
if (argsConfig.hasOwnProperty(arg)) {
if (!AUTO_CASTS.has(argsConfig[arg].type)) {
if (
!NAMED_AUTO_CASTS.has(
`${argsConfig[arg].name},${argsConfig[arg].type}`
)
) {
continue;
}
}
if (argsConfig[arg].isFlag) {
if (!this[arg]) {
continue;
}
}
if (argsConfig[arg].isVector) {
const temp = [];
for (const x of this[arg]) {
temp.push(
await getInputFromResolve(
utils,
client,
x,
argsConfig[arg].type
)
);
}
this[arg] = temp;
} else {
this[arg] = await getInputFromResolve(
utils,
client,
this[arg],
argsConfig[arg].type
);
}
}
}
}
[inspect.custom]() {
return betterConsoleLog(this);
}
toJSON() {
return { ...this.originalArgs, className: fullName };
}
}
if (namespace) {
if (!classes[namespace]) {
classes[namespace] = {};
}
classes[namespace][name] = VirtualClass;
} else {
classes[name] = VirtualClass;
}
}
return classes;
}
const api = buildApiFromTlSchema();
module.exports = { Api: api };