384 lines
13 KiB
JavaScript
384 lines
13 KiB
JavaScript
const {
|
|
parseTl,
|
|
serializeBytes,
|
|
serializeDate
|
|
} = require('./generationHelpers')
|
|
const { readBufferFromBigInt,toSignedLittleBuffer } = require('../Helpers')
|
|
|
|
const tlContent = require('./apiTl.js')
|
|
const schemeContent = require('./schemaTl.js')
|
|
|
|
/*CONTEST
|
|
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'
|
|
])
|
|
|
|
*/
|
|
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 mergeWithNamespaces(
|
|
createClasses('constructor', definitions.constructors),
|
|
createClasses('request', definitions.requests)
|
|
)
|
|
}
|
|
|
|
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 { constructors, requests }
|
|
}
|
|
|
|
function mergeWithNamespaces(obj1, obj2) {
|
|
const result = { ...obj1 }
|
|
|
|
Object.keys(obj2).forEach((key) => {
|
|
if (typeof obj2[key] === 'function' || !result[key]) {
|
|
result[key] = obj2[key]
|
|
} else {
|
|
Object.assign(result[key], obj2[key])
|
|
}
|
|
})
|
|
|
|
return result
|
|
}
|
|
|
|
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) {
|
|
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:
|
|
return x.getBytes()
|
|
}
|
|
}
|
|
/*
|
|
CONTEST
|
|
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 createClasses(classesType, params) {
|
|
const classes = {}
|
|
for (const classParams of params) {
|
|
const { name, constructorId, subclassOfId, argsConfig, namespace, result } = classParams
|
|
const fullName = [namespace, name].join('.').replace(/^\./, '')
|
|
|
|
class VirtualClass {
|
|
static CONSTRUCTOR_ID = constructorId
|
|
static SUBCLASS_OF_ID = subclassOfId
|
|
static className = fullName
|
|
static classType = classesType
|
|
|
|
CONSTRUCTOR_ID = constructorId
|
|
SUBCLASS_OF_ID = subclassOfId
|
|
className = fullName
|
|
classType = classesType
|
|
|
|
constructor(args) {
|
|
args = args || {}
|
|
Object.keys(args)
|
|
.forEach((argName) => {
|
|
this[argName] = args[argName]
|
|
})
|
|
}
|
|
|
|
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['flags'] & 1 << arg.flagIndex)
|
|
continue
|
|
}
|
|
if (args['flags'] & 1 << arg.flagIndex) {
|
|
args[argName] = getArgFromReader(reader, arg)
|
|
} else {
|
|
args[argName] = null
|
|
}
|
|
} else {
|
|
if (arg.flagIndicator) {
|
|
arg.name = 'flags'
|
|
}
|
|
args[argName] = getArgFromReader(reader, arg)
|
|
}
|
|
}
|
|
}
|
|
return new VirtualClass(args)
|
|
}
|
|
|
|
getBytes() {
|
|
// The next is pseudo-code:
|
|
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==="true") || this[arg]===null || this[arg]===undefined){
|
|
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))))
|
|
} 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) {
|
|
if ((this[f]===false&&argsConfig[f].type==="true") || 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))
|
|
|
|
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 (classesType !== 'request') {
|
|
throw new Error('`readResult()` called for non-request instance')
|
|
}
|
|
|
|
const m = result.match(/Vector<(int|long)>/)
|
|
if (m) {
|
|
reader.readInt()
|
|
let temp = []
|
|
let 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()
|
|
}
|
|
}
|
|
|
|
/*CONTEST
|
|
async resolve(client, utils) {
|
|
|
|
if (classesType !== 'request') {
|
|
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)
|
|
}
|
|
}
|
|
}
|
|
}*/
|
|
}
|
|
|
|
if (namespace) {
|
|
if (!classes[namespace]) {
|
|
classes[namespace] = {}
|
|
}
|
|
classes[namespace][name] = VirtualClass
|
|
|
|
} else {
|
|
classes[name] = VirtualClass
|
|
}
|
|
}
|
|
|
|
return classes
|
|
}
|
|
|
|
module.exports = buildApiFromTlSchema()
|