const fmtStrings = (...objects) => {
for (const object of objects) {
for (const [k, v] of Object.entries(object)) {
if (['null', 'true', 'false'].includes(v)) {
object[k] = `${v}`
} else {
object[k] = v.replace(/((['"]).*\2)/, (_, g) => `${g}`)
}
}
}
}
const KNOWN_NAMED_EXAMPLES = {
'message,string': '\'Hello there!\'',
'expires_at,date': 'datetime.timedelta(minutes=5)',
'until_date,date': 'datetime.timedelta(days=14)',
'view_messages,true': 'None',
'send_messages,true': 'None',
'limit,int': '100',
'hash,int': '0',
'hash,string': '\'A4LmkR23G0IGxBE71zZfo1\'',
'min_id,int': '0',
'max_id,int': '0',
'min_id,long': '0',
'max_id,long': '0',
'add_offset,int': '0',
'title,string': '\'My awesome title\'',
'device_model,string': '\'ASUS Laptop\'',
'system_version,string': '\'Arch Linux\'',
'app_version,string': '\'1.0\'',
'system_lang_code,string': '\'en\'',
'lang_pack,string': '\'\'',
'lang_code,string': '\'en\'',
'chat_id,int': '478614198',
'client_id,long': 'random.randrange(-2**63, 2**63)',
}
const KNOWN_TYPED_EXAMPLES = {
int128: 'int.from_bytes(crypto.randomBytes(16), \'big\')',
bytes: 'b\'arbitrary\\x7f data \\xfa here\'',
long: '-12398745604826',
string: '\'some string here\'',
int: '42',
date: 'datetime.datetime(2018, 6, 25)',
double: '7.13',
Bool: 'False',
true: 'True',
InputChatPhoto: 'client.upload_file(\'/path/to/photo.jpg\')',
InputFile: 'client.upload_file(\'/path/to/file.jpg\')',
InputPeer: '\'username\'',
}
fmtStrings(KNOWN_NAMED_EXAMPLES, KNOWN_TYPED_EXAMPLES)
const SYNONYMS = {
InputUser: 'InputPeer',
InputChannel: 'InputPeer',
InputDialogPeer: 'InputPeer',
InputNotifyPeer: 'InputPeer',
InputMessage: 'int',
}
// These are flags that are cleaner to leave off
const OMITTED_EXAMPLES = [
'silent',
'background',
'clear_draft',
'reply_to_msg_id',
'random_id',
'reply_markup',
'entities',
'embed_links',
'hash',
'min_id',
'max_id',
'add_offset',
'grouped',
'broadcast',
'admins',
'edit',
'delete',
]
/**
* Initializes a new .tl argument
* :param name: The name of the .tl argument
* :param argType: The type of the .tl argument
* :param genericDefinition: Is the argument a generic definition?
* (i.e. {X:Type})
*/
class TLArg {
constructor(name, argType, genericDefinition) {
this.name = name === 'self' ? 'is_self' : name
// Default values
this.isVector = false
this.isFlag = false
this.skipConstructorId = false
this.flagIndex = -1
this.cls = null
// Special case: some types can be inferred, which makes it
// less annoying to type. Currently the only type that can
// be inferred is if the name is 'random_id', to which a
// random ID will be assigned if left as None (the default)
this.canBeInferred = name === 'random_id'
// The type can be an indicator that other arguments will be flags
if (argType === '#') {
this.flagIndicator = true
this.type = null
this.isGeneric = false
} else {
this.flagIndicator = false
this.isGeneric = argType.startsWith('!')
// Strip the exclamation mark always to have only the name
this.type = argType.replace(/^!+/, '')
// The type may be a flag (flags.IDX?REAL_TYPE)
// Note that 'flags' is NOT the flags name; this
// is determined by a previous argument
// However, we assume that the argument will always be called 'flags'
const flagMatch = this.type.match(/flags.(\d+)\?([\w<>.]+)/)
if (flagMatch) {
this.isFlag = true
this.flagIndex = Number(flagMatch[1]);
// Update the type to match the exact type, not the "flagged" one
[, , this.type] = flagMatch
}
// Then check if the type is a Vector
const vectorMatch = this.type.match(/[Vv]ector<([\w\d.]+)>/)
if (vectorMatch) {
this.isVector = true
// If the type's first letter is not uppercase, then
// it is a constructor and we use (read/write) its ID.
this.useVectorId = this.type.charAt(0) === 'V';
// Update the type to match the one inside the vector
[, this.type] = vectorMatch
}
// See use_vector_id. An example of such case is ipPort in
// help.configSpecial
if (
/^[a-z]$/.test(
this.type
.split('.')
.pop()
.charAt(0),
)
) {
this.skipConstructorId = true
}
// The name may contain "date" in it, if this is the case and
// the type is "int", we can safely assume that this should be
// treated as a "date" object. Note that this is not a valid
// Telegram object, but it's easier to work with
// if (
// this.type === 'int' &&
// (/(\b|_)([dr]ate|until|since)(\b|_)/.test(name) ||
// ['expires', 'expires_at', 'was_online'].includes(name))
// ) {
// this.type = 'date';
// }
}
this.genericDefinition = genericDefinition
}
typeHint() {
let cls = this.type
if (cls.includes('.')) {
[, cls] = cls.split('.')
}
let result = {
int: 'int',
long: 'int',
int128: 'int',
int256: 'int',
double: 'float',
string: 'str',
date: 'Optional[datetime]', // None date = 0 timestamp
bytes: 'bytes',
Bool: 'bool',
true: 'bool',
}
result = result[cls] || `'Type${cls}'`
if (this.isVector) {
result = `List[${result}]`
}
if (this.isFlag && cls !== 'date') {
result = `Optional[${result}]`
}
return result
}
realType() {
// Find the real type representation by updating it as required
let realType = this.flagIndicator ? '#' : this.type
if (this.isVector) {
if (this.useVectorId) {
realType = `Vector<${realType}>`
} else {
realType = `vector<${realType}>`
}
}
if (this.isGeneric) {
realType = `!${realType}`
}
if (this.isFlag) {
realType = `flags.${this.flagIndex}?${realType}`
}
return realType
}
toString() {
if (this.genericDefinition) {
return `{${this.name}:${this.realType()}}`
} else {
return `${this.name}:${this.realType()}`
}
}
toJson() {
return {
name: this.name.replace('is_self', 'self'),
type: this.realType().replace(/\bdate$/, 'int'),
}
}
asExample(f, indent) {
if (this.isGeneric) {
f.write('other_request')
return
}
const known =
KNOWN_NAMED_EXAMPLES[`${this.name},${this.type}`] ||
KNOWN_TYPED_EXAMPLES[this.type] ||
KNOWN_TYPED_EXAMPLES[SYNONYMS[this.type]]
if (known) {
f.write(known)
return
}
// assert self.omit_example() or self.cls, 'TODO handle ' + str(self)
// Pick an interesting example if any
for (const cls of this.cls) {
if (cls.isGoodExample()) {
cls.asExample(f, indent || 0)
return
}
}
// If no example is good, just pick the first
this.cls[0].asExample(f, indent || 0)
}
omitExample() {
return this.isFlag || (this.canBeInferred && OMITTED_EXAMPLES.includes(this.name))
}
}
module.exports = {
KNOWN_NAMED_EXAMPLES,
KNOWN_TYPED_EXAMPLES,
SYNONYMS,
OMITTED_EXAMPLES,
TLArg,
}