chore: improved eslint config

closes MTQ-54
This commit is contained in:
alina 🌸 2023-09-03 02:37:51 +03:00
parent e7171e32c7
commit 81ce550604
Signed by: teidesu
SSH key fingerprint: SHA256:uNeCpw6aTSU4aIObXLvHfLkDa82HWH9EiOj9AXOIRpI
97 changed files with 778 additions and 541 deletions

View file

@ -176,7 +176,7 @@ module.exports = {
files: ['**/*.ts', '**/*.tsx'],
env: { browser: true, es6: true, node: true },
extends: [
'plugin:@typescript-eslint/recommended',
'plugin:@typescript-eslint/strict-type-checked',
'plugin:import/typescript',
],
globals: { Atomics: 'readonly', SharedArrayBuffer: 'readonly' },
@ -213,6 +213,18 @@ module.exports = {
],
'@typescript-eslint/no-non-null-assertion': 'off', // todo MTQ-36
'@typescript-eslint/no-empty-function': 'off',
'@typescript-eslint/no-confusing-void-expression': 'off',
'@typescript-eslint/no-unnecessary-condition': 'off',
'@typescript-eslint/no-var-requires': 'off',
'@typescript-eslint/restrict-template-expressions': [
'error',
{ allowNever: true },
],
'@typescript-eslint/no-unsafe-enum-comparison': 'off',
'@typescript-eslint/no-invalid-void-type': 'off',
'@typescript-eslint/unbound-method': 'off',
'@typescript-eslint/no-dynamic-delete': 'off',
'@typescript-eslint/no-unsafe-member-access': 'off',
},
settings: {
'import/resolver': {

View file

@ -1,4 +1,4 @@
/* eslint-disable @typescript-eslint/no-unsafe-declaration-merging */
/* eslint-disable @typescript-eslint/no-unsafe-declaration-merging, @typescript-eslint/unified-signatures */
/* THIS FILE WAS AUTO-GENERATED */
import { Readable } from 'stream'

View file

@ -14,7 +14,8 @@ export async function logOut(this: TelegramClient): Promise<true> {
this._userId = null
this._isBot = false
// eslint-disable-next-line @typescript-eslint/no-explicit-any
// some implicit magic in favor of performance
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-assignment
this._pts = this._seq = this._date = undefined as any
this._selfUsername = null
this._selfChanged = true

View file

@ -97,7 +97,11 @@ export async function answerInlineQuery(
): Promise<void> {
if (!params) params = {}
const [gallery, tlResults] = await BotInline._convertToTl(this, results, params!.parseMode)
const [gallery, tlResults] = await BotInline._convertToTl(
this,
results,
params.parseMode,
)
await this.call({
_: 'messages.setInlineBotResults',

View file

@ -53,7 +53,7 @@ export async function getInlineGameHighScores(
messageId: string | tl.TypeInputBotInlineMessageID,
userId?: InputPeerLike,
): Promise<GameHighScore[]> {
const id = await normalizeInlineId(messageId)
const id = normalizeInlineId(messageId)
let user: tl.TypeInputUser

View file

@ -87,7 +87,7 @@ export async function setInlineGameScore(
const user = normalizeToInputUser(await this.resolvePeer(userId), userId)
const id = await normalizeInlineId(messageId)
const id = normalizeInlineId(messageId)
await this.call(
{

View file

@ -45,10 +45,7 @@ export async function addChatMembers(
const updates = await this.call({
_: 'channels.inviteToChannel',
channel: normalizeToInputChannel(chat),
users: await this.resolvePeerMany(
users as InputPeerLike[],
normalizeToInputUser,
),
users: await this.resolvePeerMany(users, normalizeToInputUser),
})
this._handleUpdate(updates)
} else throw new MtInvalidPeerTypeError(chatId, 'chat or channel')

View file

@ -24,10 +24,7 @@ export async function createGroup(
): Promise<Chat> {
if (!Array.isArray(users)) users = [users]
const peers = await this.resolvePeerMany(
users as InputPeerLike[],
normalizeToInputUser,
)
const peers = await this.resolvePeerMany(users, normalizeToInputUser)
const res = await this.call({
_: 'messages.createChat',

View file

@ -28,7 +28,7 @@ export function _pushConversationMessage(
const chatId = getMarkedPeerId(msg.raw.peerId)
const msgId = msg.raw.id
this._pendingConversations[chatId]?.forEach((conv) => {
this._pendingConversations[chatId].forEach((conv) => {
conv['_lastMessage'] = msgId
if (incoming) conv['_lastReceivedMessage'] = msgId
})

View file

@ -5,11 +5,10 @@ import {
MtUnsupportedError,
} from '../../types'
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let fs: any = null
let fs: typeof import('fs') | null = null
try {
fs = require('fs')
fs = require('fs') as typeof import('fs')
} catch (e) {}
/**
@ -39,7 +38,7 @@ export function downloadToFile(
const buf = params.location.location
return new Promise((resolve, reject) => {
fs.writeFile(filename, buf, (err?: Error) => {
fs!.writeFile(filename, buf, (err) => {
if (err) reject(err)
else resolve()
})

View file

@ -47,19 +47,20 @@ export async function* downloadAsIterable(
const input = params.location
let location: tl.TypeInputFileLocation | tl.TypeInputWebFileLocation
if (input instanceof FileLocation) {
if (typeof input.location === 'function') {
(input as tl.Mutable<FileLocation>).location = input.location()
let locationInner = input.location
if (typeof locationInner === 'function') {
locationInner = locationInner()
}
if (Buffer.isBuffer(input.location)) {
yield input.location
if (Buffer.isBuffer(locationInner)) {
yield locationInner
return
}
if (!dcId) dcId = input.dcId
if (!fileSize) fileSize = input.fileSize
// eslint-disable-next-line @typescript-eslint/no-explicit-any
location = input.location as any
location = locationInner
} else if (typeof input === 'string') {
const parsed = parseFileId(input)
@ -129,7 +130,7 @@ export async function* downloadAsIterable(
result = await this.call(
{
_: isWeb ? 'upload.getWebFile' : 'upload.getFile',
// eslint-disable-next-line @typescript-eslint/no-explicit-any
// eslint-disable-next-line
location: location as any,
offset: chunkSize * chunk,
limit: chunkSize,
@ -182,7 +183,7 @@ export async function* downloadAsIterable(
}
let error: unknown = undefined
Promise.all(
void Promise.all(
Array.from(
{ length: Math.min(poolSize * REQUESTS_PER_CONNECTION, numChunks) },
downloadChunk,
@ -202,6 +203,7 @@ export async function* downloadAsIterable(
while (position < limitBytes) {
await nextChunkCv.wait()
// eslint-disable-next-line @typescript-eslint/no-throw-literal
if (error) throw error
while (nextChunkIdx in buffer) {

View file

@ -26,6 +26,7 @@ export function downloadAsStream(
async read() {},
})
// eslint-disable-next-line @typescript-eslint/no-misused-promises
setTimeout(async () => {
try {
for await (const chunk of this.downloadAsIterable(params)) {

View file

@ -1,4 +1,3 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { fromBuffer as fileTypeFromBuffer } from 'file-type'
import type { ReadStream } from 'fs'
import { Readable } from 'stream'
@ -15,12 +14,12 @@ import {
readBytesFromStream,
} from '../../utils/stream-utils'
let fs: any = null
let path: any = null
let fs: typeof import('fs') | null = null
let path: typeof import('path') | null = null
try {
fs = require('fs')
path = require('path')
fs = require('fs') as typeof import('fs')
path = require('path') as typeof import('path')
} catch (e) {}
const OVERRIDE_MIME: Record<string, string> = {
@ -132,15 +131,12 @@ export async function uploadFile(
}
if (fs && file instanceof fs.ReadStream) {
fileName = path.basename((file as ReadStream).path.toString())
fileName = path!.basename(file.path.toString())
fileSize = await new Promise((res, rej) => {
fs.stat(
(file as ReadStream).path.toString(),
(err?: any, stat?: any) => {
fs!.stat((file as ReadStream).path.toString(), (err, stat) => {
if (err) rej(err)
res(stat.size)
},
)
})
})
// fs.ReadStream is a subclass of Readable, no conversion needed
}
@ -171,7 +167,7 @@ export async function uploadFile(
if (idx > -1) {
const raw = disposition.slice(idx + 9).split(';')[0]
fileName = JSON.parse(raw)
fileName = JSON.parse(raw) as string
}
}

View file

@ -91,7 +91,7 @@ export async function uploadMedia(
assertTypeIs('uploadMedia', res, 'messageMediaDocument')
assertTypeIs('uploadMedia', res.document!, 'document')
// eslint-disable-next-line @typescript-eslint/no-explicit-any
// eslint-disable-next-line
return parseDocument(this, res.document) as any
case 'inputMediaStory':
throw new MtArgumentError("This media (story) can't be uploaded")

View file

@ -76,7 +76,7 @@ export async function editInlineMessage(
let entities: tl.TypeMessageEntity[] | undefined
let media: tl.TypeInputMedia | undefined = undefined
const id = await normalizeInlineId(messageId)
const id = normalizeInlineId(messageId)
if (params.media) {
media = await this._normalizeInputMedia(params.media, params, true)

View file

@ -114,6 +114,6 @@ export async function editMessage(
media,
})
// eslint-disable-next-line @typescript-eslint/no-explicit-any
// eslint-disable-next-line
return this._findMessageInUpdate(res, true) as any
}

View file

@ -291,13 +291,18 @@ export async function forwardMessages(
},
): Promise<MaybeArray<Message>> {
if (!params) params = {}
const isSingle = !Array.isArray(messages)
if (isSingle) messages = [messages as number]
let isSingle = false
if (!Array.isArray(messages)) {
isSingle = true
messages = [messages]
}
// sending more than 100 will not result in a server-sent
// error, instead only first 100 IDs will be forwarded,
// which is definitely not the best outcome.
if ((messages as number[]).length > 100) {
if (messages.length > 100) {
throw new MtArgumentError(
'You can forward no more than 100 messages at once',
)
@ -338,12 +343,10 @@ export async function forwardMessages(
_: 'messages.forwardMessages',
toPeer,
fromPeer: await this.resolvePeer(fromChatId),
id: messages as number[],
id: messages,
silent: params.silent,
scheduleDate: normalizeDate(params.schedule),
randomId: [...Array((messages as number[]).length)].map(() =>
randomLong(),
),
randomId: Array.from({ length: messages.length }, () => randomLong()),
dropAuthor: params.noAuthor,
dropMediaCaptions: params.noCaption,
noforwards: params.forbidForwards,

View file

@ -56,7 +56,7 @@ export async function addStickerToSet(
maskCoords: sticker.maskPosition ?
{
_: 'maskCoords',
n: MASK_POS[sticker.maskPosition.point as keyof typeof MASK_POS],
n: MASK_POS[sticker.maskPosition.point],
x: sticker.maskPosition.x,
y: sticker.maskPosition.y,
zoom: sticker.maskPosition.scale,

View file

@ -106,7 +106,10 @@ export async function createStickerSet(
)
}
const owner = normalizeToInputUser(await this.resolvePeer(params.owner), params.owner)
const owner = normalizeToInputUser(
await this.resolvePeer(params.owner),
params.owner,
)
const inputStickers: tl.TypeInputStickerSetItem[] = []
@ -124,7 +127,7 @@ export async function createStickerSet(
maskCoords: sticker.maskPosition ?
{
_: 'maskCoords',
n: MASK_POS[sticker.maskPosition.point as keyof typeof MASK_POS],
n: MASK_POS[sticker.maskPosition.point],
x: sticker.maskPosition.x,
y: sticker.maskPosition.y,
zoom: sticker.maskPosition.scale,

View file

@ -33,6 +33,6 @@ export async function getCustomEmojis(
)
}
return doc as Sticker
return doc
})
}

View file

@ -940,7 +940,7 @@ function _fetchChannelDifferenceLater(
fallbackPts?: number,
force = false,
): void {
if (!requestedDiff[channelId]) {
if (!(channelId in requestedDiff)) {
requestedDiff[channelId] = _fetchChannelDifference
.call(this, channelId, fallbackPts, force)
.catch((err) => {
@ -1074,7 +1074,7 @@ function _fetchDifferenceLater(
this: TelegramClient,
requestedDiff: Record<number, Promise<void>>,
): void {
if (!requestedDiff[0]) {
if (!(0 in requestedDiff)) {
requestedDiff[0] = _fetchDifference
.call(this, requestedDiff)
.catch((err) => {

View file

@ -8,7 +8,11 @@ import {
import { tl } from '@mtcute/tl'
import { TelegramClient } from '../../client'
import { InputPeerLike, MtNotFoundError, MtTypeAssertionError } from '../../types'
import {
InputPeerLike,
MtNotFoundError,
MtTypeAssertionError,
} from '../../types'
import { normalizeToInputPeer } from '../../utils/peer-utils'
import { assertTypeIs } from '../../utils/type-assertion'
@ -109,7 +113,12 @@ export async function resolvePeer(
const found = res.chats.find((it) => it.id === id)
if (found) {
if (!(found._ === 'channel' || found._ === 'channelForbidden')) {
if (
!(
found._ === 'channel' ||
found._ === 'channelForbidden'
)
) {
// chats can't have usernames
// furthermore, our id is a channel id, so it must be a channel
// this should never happen, unless Telegram goes crazy
@ -205,7 +214,7 @@ export async function resolvePeer(
// break
}
case 'channel': {
const id = toggleChannelIdMark(peerId as number)
const id = toggleChannelIdMark(peerId)
const res = await this.call({
_: 'channels.getChannels',

View file

@ -251,6 +251,7 @@ export class Conversation {
this.stop()
// eslint-disable-next-line @typescript-eslint/no-throw-literal
if (err) throw err
return res!
@ -501,7 +502,7 @@ export class Conversation {
const it = this._queuedNewMessage.peekFront()!
// order does matter for new messages
this._lock.acquire().then(async () => {
void this._lock.acquire().then(async () => {
try {
if (!it.check || (await it.check(msg))) {
if (it.timeout) clearTimeout(it.timeout)

View file

@ -140,7 +140,7 @@ export class Sticker extends RawDocument {
*/
get customEmojiFree(): boolean {
return this.attr._ === 'documentAttributeCustomEmoji' ?
this.attr?.free ?? false :
this.attr.free ?? false :
false
}

View file

@ -329,7 +329,7 @@ export class Message {
get replyToMessageId(): number | null {
if (this.raw.replyTo?._ !== 'messageReplyHeader') return null
return this.raw.replyTo?.replyToMsgId ?? null
return this.raw.replyTo.replyToMsgId ?? null
}
/**
@ -339,7 +339,7 @@ export class Message {
get replyToThreadId(): number | null {
if (this.raw.replyTo?._ !== 'messageReplyHeader') return null
return this.raw.replyTo?.replyToTopId ?? null
return this.raw.replyTo.replyToTopId ?? null
}
/**

View file

@ -239,7 +239,7 @@ export class StickerSet {
*/
getStickersByEmoji(emoji: string): StickerInfo[] {
return this.stickers.filter(
(it) => it.alt === emoji || it.emoji.indexOf(emoji) !== -1,
(it) => it.alt === emoji || it.emoji.includes(emoji),
)
}
@ -271,8 +271,8 @@ export class StickerSet {
private _getInputDocument(idx: number): tl.TypeInputDocument {
if (!this.full) throw new MtEmptyError()
if (idx < 0) idx = this.full!.documents.length + idx
const doc = this.full!.documents[idx] as tl.RawDocument
if (idx < 0) idx = this.full.documents.length + idx
const doc = this.full.documents[idx] as tl.RawDocument
if (!doc) {
throw new RangeError(`Sticker set does not have sticker ${idx}`)

View file

@ -33,6 +33,7 @@ export class TakeoutSession {
message: MustEqual<T, tl.RpcMethod>,
params?: RpcCallOptions,
): Promise<tl.RpcCallReturn[T['_']]> {
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
return this.client.call(
{
_: 'invokeWithTakeout',
@ -76,6 +77,7 @@ export class TakeoutSession {
get(target, prop, receiver) {
if (prop === 'call') return boundCall
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
return Reflect.get(target, prop, receiver)
},
})

View file

@ -98,6 +98,7 @@ export class ChatPhotoSize extends FileLocation {
{
_: 'photo',
id: this.obj.photoId,
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
source: {
_: 'dialogPhoto',
big: this.big,

View file

@ -120,7 +120,7 @@ export class Chat {
}
}
return this._inputPeer!
return this._inputPeer
}
private _chatType?: ChatType
@ -148,7 +148,7 @@ export class Chat {
}
}
return this._chatType!
return this._chatType
}
/**
@ -594,7 +594,7 @@ export class Chat {
return new FormattedString(
this.client.getParseMode(parseMode).unparse(text, [
{
// eslint-disable-next-line @typescript-eslint/no-explicit-any
// eslint-disable-next-line
raw: undefined as any,
type: 'text_link',
offset: 0,
@ -602,7 +602,7 @@ export class Chat {
url: `https://t.me/${this.username}`,
},
]),
parseMode!,
parseMode,
)
}

View file

@ -6,8 +6,8 @@ const ERROR_MSG =
'Given peer is not available in this index. This is most likely an internal library error.'
export class PeersIndex {
readonly users: Record<number, tl.TypeUser> = Object.create(null)
readonly chats: Record<number, tl.TypeChat> = Object.create(null)
readonly users: Record<number, tl.TypeUser> = {}
readonly chats: Record<number, tl.TypeChat> = {}
hasMin = false

View file

@ -324,7 +324,7 @@ export class User {
return new FormattedString(
this.client.getParseMode(parseMode).unparse(text, [
{
// eslint-disable-next-line @typescript-eslint/no-explicit-any
// eslint-disable-next-line
raw: undefined as any,
type: 'text_mention',
offset: 0,
@ -332,7 +332,7 @@ export class User {
userId: this.raw.id,
},
]),
parseMode!,
parseMode,
)
}
@ -383,7 +383,7 @@ export class User {
return new FormattedString(
this.client.getParseMode(parseMode).unparse(text, [
{
// eslint-disable-next-line @typescript-eslint/no-explicit-any
// eslint-disable-next-line
raw: undefined as any,
type: 'text_link',
offset: 0,
@ -393,7 +393,7 @@ export class User {
}&hash=${this.raw.accessHash.toString(16)}`,
},
]),
parseMode!,
parseMode,
)
}

View file

@ -1,6 +1,6 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
// ^^ will be looked into in MTQ-35
import { getMarkedPeerId } from '@mtcute/core'
import { tl } from '@mtcute/tl'
import { TelegramClient } from '../../client'
@ -37,6 +37,26 @@ export type ChatMemberUpdateType =
| 'new_owner'
| 'other'
function extractPeerId(
raw?: tl.TypeChatParticipant | tl.TypeChannelParticipant,
) {
if (!raw) return 0
if (tl.isAnyChatParticipant(raw)) {
return raw.userId
}
switch (raw._) {
case 'channelParticipant':
case 'channelParticipantSelf':
case 'channelParticipantCreator':
case 'channelParticipantAdmin':
return raw.userId
default:
return getMarkedPeerId(raw.peer)
}
}
/**
* Update representing a change in the status
* of a chat/channel member.
@ -82,12 +102,8 @@ export class ChatMemberUpdate {
const old = this.raw.prevParticipant
const cur = this.raw.newParticipant
const oldId =
(old && ((old as any).userId || (old as any).peer.userId)) ||
null
const curId =
(cur && ((cur as any).userId || (cur as any).peer.userId)) ||
null
const oldId = extractPeerId(old)
const curId = extractPeerId(cur)
const actorId = this.raw.actorId

View file

@ -1,4 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/no-explicit-any,@typescript-eslint/no-unsafe-argument */
import { tl } from '@mtcute/tl'
import { TelegramClient } from '../../client'
@ -19,13 +19,14 @@ import {
PollVoteUpdate,
PreCheckoutQuery,
UserStatusUpdate,
UserTypingUpdate } from '../index'
UserTypingUpdate,
} from '../index'
type ParserFunction = (
client: TelegramClient,
upd: tl.TypeUpdate | tl.TypeMessage,
peers: PeersIndex
) => any
) => ParsedUpdate['data']
type UpdateParser = [ParsedUpdate['name'], ParserFunction]
const baseMessageParser: ParserFunction = (
@ -35,7 +36,9 @@ const baseMessageParser: ParserFunction = (
) =>
new Message(
client,
tl.isAnyMessage(upd) ? upd : (upd as any).message,
tl.isAnyMessage(upd) ?
upd :
(upd as { message: tl.TypeMessage }).message,
peers,
upd._ === 'updateNewScheduledMessage',
)
@ -142,7 +145,7 @@ export function _parseUpdate(
return {
name: pair[0],
data: pair[1](client, update, peers),
}
} as ParsedUpdate
}
return null

View file

@ -1,14 +1,15 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/no-explicit-any,@typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-argument */
import { MaybeAsync } from '@mtcute/core'
export type MaybeDynamic<T> = MaybeAsync<T> | (() => MaybeAsync<T>)
export type ArrayWithTotal<T> = T[] & { total: number }
let util: any | null = null
let util: typeof import('util') | null = null
try {
util = require('util')
util = require('util') as typeof import('util')
} catch (e) {}
// get all property names. unlike Object.getOwnPropertyNames,
@ -21,7 +22,7 @@ function getAllGettersNames(obj: object): string[] {
if (
prop !== '__proto__' &&
Object.getOwnPropertyDescriptor(obj, prop)?.get &&
getters.indexOf(prop) === -1
!getters.includes(prop)
) {
getters.push(prop)
}
@ -53,10 +54,11 @@ export function makeInspectable(
const getters: string[] = props ? props : []
for (const key of getAllGettersNames(obj.prototype)) {
if (!hide || hide.indexOf(key) === -1) getters.push(key)
if (!hide || !hide.includes(key)) getters.push(key)
}
// dirty hack to set name for inspect result
// eslint-disable-next-line @typescript-eslint/no-implied-eval
const proto = new Function(`return function ${obj.name}(){}`)().prototype
obj.prototype.toJSON = function (nested = false) {
@ -86,6 +88,7 @@ export function makeInspectable(
Buffer.prototype.toJSON = bufferToJsonOriginal
}
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
return ret
}
if (util) {

View file

@ -21,7 +21,9 @@ class NodeReadable extends Readable {
this._reading = true
const doRead = () => {
this._reader.read().then((res) => {
this._reader
.read()
.then((res) => {
if (this._doneReading) {
this._reading = false
this._reader.releaseLock()
@ -42,6 +44,7 @@ class NodeReadable extends Readable {
this._reading = false
this._reader.releaseLock()
})
.catch((err) => this.emit('error', err))
}
doRead()
}
@ -51,9 +54,11 @@ class NodeReadable extends Readable {
const promise = new Promise<void>((resolve) => {
this._doneReading = resolve
})
promise.then(() => {
promise
.then(() => {
this._handleDestroy(err, callback)
})
.catch((err) => this.emit('error', err))
} else {
this._handleDestroy(err, callback)
}
@ -63,8 +68,10 @@ class NodeReadable extends Readable {
err: Error | null,
callback: (error?: Error | null) => void,
) {
this._webStream.cancel()
super._destroy(err, callback)
this._webStream
.cancel()
.then(() => super._destroy(err, callback))
.catch((err: Error) => callback(err))
}
}
@ -90,12 +97,12 @@ export async function readBytesFromStream(
): Promise<Buffer | null> {
if (stream.readableEnded) return null
let res = stream.read(size)
let res = stream.read(size) as Buffer
if (!res) {
return new Promise((resolve, reject) => {
stream.on('readable', function handler() {
res = stream.read(size)
res = stream.read(size) as Buffer
if (res) {
stream.off('readable', handler)

View file

@ -4,6 +4,7 @@
"outDir": "./dist",
},
"include": [
"./src"
"./src",
"./tests"
]
}

View file

@ -18,17 +18,18 @@
"./storage/json-file.js": false
},
"dependencies": {
"@types/node": "18.16.0",
"@types/events": "3.0.0",
"@mtcute/tl": "workspace:^160.0.0",
"@mtcute/tl-runtime": "workspace:^1.0.0",
"@types/events": "3.0.0",
"@types/node": "18.16.0",
"big-integer": "1.6.51",
"long": "5.2.3",
"events": "3.2.0"
"events": "3.2.0",
"long": "5.2.3"
},
"devDependencies": {
"@mtcute/dispatcher": "workspace:^1.0.0",
"@types/ws": "8.5.4",
"exit-hook": "^4.0.0",
"ws": "8.13.0"
}
}

View file

@ -388,7 +388,7 @@ export class BaseTelegramClient extends EventEmitter {
promise.resolve()
this._connected = true
})
.catch((err) => this._emitError(err))
.catch((err: Error) => this._emitError(err))
}
/**
@ -401,7 +401,7 @@ export class BaseTelegramClient extends EventEmitter {
* Close all connections and finalize the client.
*/
async close(): Promise<void> {
await this._onClose()
this._onClose()
this._config.destroy()
this.network.destroy()
@ -436,6 +436,7 @@ export class BaseTelegramClient extends EventEmitter {
const res = await this.network.call(message, params, stack)
await this._cachePeersFrom(res)
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
return res
}
@ -487,7 +488,7 @@ export class BaseTelegramClient extends EventEmitter {
let hadMin = false
let count = 0
for (const peer of getAllPeersFrom(obj as any)) {
for (const peer of getAllPeersFrom(obj as tl.TlObject)) {
if ((peer as any).min) {
// absolutely incredible min peer handling, courtesy of levlam.
// see this thread: https://t.me/tdlibchat/15084

View file

@ -178,6 +178,7 @@ async function rsaPad(
const decryptedDataBigint = bufferToBigInt(decryptedData)
if (decryptedDataBigint.geq(keyModulus)) {
console.log('retrying because decrypted data is too big')
continue
}
@ -224,6 +225,7 @@ export async function doAuthorization(
const session = connection['_session']
const readerMap = session._readerMap
const writerMap = session._writerMap
const log = connection.log.create('auth')
function sendPlainMessage(message: mtp.TlObject): Promise<void> {
const length = TlSerializationCounter.countNeededBytes(
@ -234,6 +236,7 @@ export async function doAuthorization(
const messageId = session.getMessageId()
log.verbose('[PLAIN] >>> %j', message)
writer.long(Long.ZERO)
writer.long(messageId)
writer.uint(length)
@ -243,14 +246,17 @@ export async function doAuthorization(
}
async function readNext(): Promise<mtp.TlObject> {
return TlBinaryReader.deserializeObject(
const res = TlBinaryReader.deserializeObject<mtp.TlObject>(
readerMap,
await connection.waitForUnencryptedMessage(),
20, // skip mtproto header
)
log.verbose('[PLAIN] <<< %j', res)
return res
}
const log = connection.log.create('auth')
if (expiresIn) log.prefix = '[PFS] '
const nonce = randomBytes(16)
@ -508,8 +514,7 @@ export async function doAuthorization(
if (!buffersEqual(expectedHash.slice(4, 20), dhGen.newNonceHash2)) {
throw Error('Step 4: invalid retry nonce hash from server')
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
retryId = Long.fromBytesLE(authKeyAuxHash as any)
retryId = Long.fromBytesLE(authKeyAuxHash as unknown as number[])
continue
}

View file

@ -32,7 +32,7 @@ export class ConfigManager {
if (this._updateTimeout) clearTimeout(this._updateTimeout)
this._updateTimeout = setTimeout(
() => this.update(),
() => void this.update().catch(() => {}),
(config.expires - Date.now() / 1000) * 1000,
)

View file

@ -250,7 +250,7 @@ export class DcConnectionManager {
private _setupMulti(kind: ConnectionKind): void {
const connection = this[kind]
connection.on('key-change', (idx, key) => {
connection.on('key-change', (idx, key: Buffer | null) => {
if (kind !== 'main') {
// main connection is responsible for authorization,
// and keys are then sent to other connections
@ -266,20 +266,24 @@ export class DcConnectionManager {
this.dcId,
idx,
)
this.manager._storage.setAuthKeyFor(this.dcId, key)
// send key to other connections
Promise.all([
this.manager._storage.setAuthKeyFor(this.dcId, key),
this.upload.setAuthKey(key),
this.download.setAuthKey(key),
this.downloadSmall.setAuthKey(key),
]).then(() => {
])
.then(() => {
this.upload.notifyKeyChange()
this.download.notifyKeyChange()
this.downloadSmall.notifyKeyChange()
})
.catch((e: Error) => this.manager.params._emitError(e))
})
connection.on('tmp-key-change', (idx, key, expires) => {
connection.on(
'tmp-key-change',
(idx: number, key: Buffer | null, expires: number) => {
if (kind !== 'main') {
this.manager._log.warn(
'got tmp-key-change from non-main connection, ignoring',
@ -293,24 +297,27 @@ export class DcConnectionManager {
this.dcId,
idx,
)
// send key to other connections
Promise.all([
this.manager._storage.setTempAuthKeyFor(
this.dcId,
idx,
key,
expires * 1000,
)
// send key to other connections
Promise.all([
),
this.upload.setAuthKey(key, true),
this.download.setAuthKey(key, true),
this.downloadSmall.setAuthKey(key, true),
]).then(() => {
])
.then(() => {
this.upload.notifyKeyChange()
this.download.notifyKeyChange()
this.downloadSmall.notifyKeyChange()
})
})
.catch((e: Error) => this.manager.params._emitError(e))
},
)
connection.on('auth-begin', () => {
// we need to propagate auth-begin to all connections
@ -334,7 +341,7 @@ export class DcConnectionManager {
this.main.requestAuth()
})
connection.on('error', (err, conn) => {
connection.on('error', (err: Error, conn: SessionConnection) => {
this.manager.params._emitError(err, conn)
})
}
@ -429,19 +436,21 @@ export class NetworkManager {
let deviceModel = 'mtcute on '
let appVersion = 'unknown'
if (typeof process !== 'undefined' && typeof require !== 'undefined') {
// eslint-disable-next-line @typescript-eslint/no-var-requires
const os = require('os')
const os = require('os') as typeof import('os')
deviceModel += `${os.type()} ${os.arch()} ${os.release()}`
try {
// for production builds
// eslint-disable-next-line @typescript-eslint/no-var-requires
appVersion = require('../package.json').version
appVersion = (require('../package.json') as { version: string })
.version
} catch (e) {
try {
// for development builds (additional /src/ in path)
// eslint-disable-next-line @typescript-eslint/no-var-requires
appVersion = require('../../package.json').version
appVersion = (
require('../../package.json') as { version: string }
).version
} catch (e) {}
}
} else if (typeof navigator !== 'undefined') {
@ -458,7 +467,7 @@ export class NetworkManager {
langCode: 'en',
...(params.initConnectionOptions ?? {}),
apiId: params.apiId,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
// eslint-disable-next-line
query: null as any,
}
@ -484,7 +493,7 @@ export class NetworkManager {
this._lastUpdateTime = Date.now()
if (this._keepAliveInterval) clearInterval(this._keepAliveInterval)
this._keepAliveInterval = setInterval(async () => {
this._keepAliveInterval = setInterval(() => {
if (Date.now() - this._lastUpdateTime > 900_000) {
// telegram asks to fetch pending updates if there are no updates for 15 minutes.
// it is up to the user to decide whether to do it or not
@ -494,27 +503,21 @@ export class NetworkManager {
}
}, 60_000)
Promise.resolve(this._storage.getSelf()).then((self) => {
Promise.resolve(this._storage.getSelf())
.then((self) => {
if (self?.isBot) {
// bots may receive tmpSessions, which we should respect
this.config.update(true).catch((e) => {
this.params._emitError(e)
})
return this.config.update(true)
}
})
.catch((e: Error) => this.params._emitError(e))
})
dc.main.on('update', (update) => {
dc.main.on('update', (update: tl.TypeUpdates) => {
this._lastUpdateTime = Date.now()
this._updateHandler(update)
})
dc.loadKeys()
.catch((e) => {
this.params._emitError(e)
})
.then(() => {
dc.main.ensureConnected()
})
return dc.loadKeys().then(() => dc.main.ensureConnected())
}
private _dcCreationPromise: Record<number, Promise<void>> = {}
@ -574,7 +577,7 @@ export class NetworkManager {
const dc = new DcConnectionManager(this, defaultDc.id, defaultDc)
this._dcConnections[defaultDc.id] = dc
this._switchPrimaryDc(dc)
await this._switchPrimaryDc(dc)
}
private async _exportAuthTo(manager: DcConnectionManager): Promise<void> {
@ -681,9 +684,9 @@ export class NetworkManager {
)
}
this._storage.setDefaultDc(option)
await this._storage.setDefaultDc(option)
this._switchPrimaryDc(this._dcConnections[newDc])
await this._switchPrimaryDc(this._dcConnections[newDc])
}
private _floodWaitedRequests: Record<string, number> = {}
@ -738,10 +741,11 @@ export class NetworkManager {
this._lastUpdateTime = Date.now()
}
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
return res
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} catch (e: any) {
lastError = e
lastError = e as Error
if (e.code && !(e.code in CLIENT_ERRORS)) {
this._log.warn(
@ -811,7 +815,7 @@ export class NetworkManager {
}
}
throw lastError
throw lastError!
}
setUpdateHandler(handler: NetworkManager['_updateHandler']): void {

View file

@ -115,7 +115,7 @@ export class SessionConnection extends PersistentConnection {
this._isPfsBindingPending = false
this._isPfsBindingPendingInBackground = false
this._session._authKeyTemp.reset()
clearTimeout(this._pfsUpdateTimeout!)
clearTimeout(this._pfsUpdateTimeout)
}
this._resetSession()
@ -296,7 +296,7 @@ export class SessionConnection extends PersistentConnection {
}
this.onConnectionUsable()
})
.catch((err) => {
.catch((err: Error) => {
this._session.authorizationPending = false
this.log.error('Authorization error: %s', err.message)
this.onError(err)
@ -338,7 +338,7 @@ export class SessionConnection extends PersistentConnection {
return
}
const tempKey = await this._session._authKeyTempSecondary
const tempKey = this._session._authKeyTempSecondary
await tempKey.setup(tempAuthKey)
const msgId = this._session.getMessageId()
@ -490,7 +490,7 @@ export class SessionConnection extends PersistentConnection {
this._authorizePfs(true)
}, (TEMP_AUTH_KEY_EXPIRY - 60) * 1000)
})
.catch((err) => {
.catch((err: Error) => {
this.log.error('PFS Authorization error: %s', err.message)
if (this._isPfsBindingPendingInBackground) {
@ -699,6 +699,8 @@ export class SessionConnection extends PersistentConnection {
this._rescheduleInactivity()
}
this.log.verbose('<<< %j', message)
if (this.params.disableUpdates) {
this.log.warn(
'received updates, but updates are disabled',
@ -727,6 +729,7 @@ export class SessionConnection extends PersistentConnection {
let resultType
try {
// eslint-disable-next-line
resultType = (message.object() as any)._
} catch (err) {
resultType = message.peekUint()
@ -745,6 +748,7 @@ export class SessionConnection extends PersistentConnection {
let result
try {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
result = message.object() as any
} catch (err) {
result = '[failed to parse]'
@ -1490,6 +1494,7 @@ export class SessionConnection extends PersistentConnection {
const pending: PendingRpc = {
method,
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
promise: undefined as any, // because we need the object to make a promise
data: content,
stack,
@ -1599,7 +1604,7 @@ export class SessionConnection extends PersistentConnection {
try {
this._doFlush()
} catch (e: any) {
this.log.error('flush error: %s', e.stack)
this.log.error('flush error: %s', (e as Error).stack)
// should not happen unless there's a bug in the code
}
@ -1718,7 +1723,7 @@ export class SessionConnection extends PersistentConnection {
}
const idx = this._session.getStateSchedule.index(
{ getState: now } as any,
{ getState: now } as PendingRpc,
true,
)
@ -2035,7 +2040,7 @@ export class SessionConnection extends PersistentConnection {
this._session
.encryptMessage(result)
.then((enc) => this.send(enc))
.catch((err) => {
.catch((err: Error) => {
this.log.error(
'error while sending pending messages (root msg_id = %l): %s',
rootMsgId,

View file

@ -98,8 +98,13 @@ export class ObfuscatedPacketCodec
feed(data: Buffer): void {
const dec = this._decryptor!.decrypt(data)
if (Buffer.isBuffer(dec)) this._inner.feed(dec)
else dec.then((dec) => this._inner.feed(dec))
else {
dec.then((dec) => this._inner.feed(dec)).catch((err) =>
this.emit('error', err),
)
}
}
reset(): void {

View file

@ -96,10 +96,11 @@ export abstract class BaseTcpTransport
this.emit('error', error)
}
async handleConnect(): Promise<void> {
handleConnect(): void {
this.log.info('connected')
const initialMessage = await this._packetCodec.tag()
Promise.resolve(this._packetCodec.tag())
.then((initialMessage) => {
if (initialMessage.length) {
this._socket!.write(initialMessage, (err) => {
if (err) {
@ -118,6 +119,8 @@ export abstract class BaseTcpTransport
this._state = TransportState.Ready
this.emit('ready')
}
})
.catch((err) => this.emit('error', err))
}
async send(bytes: Buffer): Promise<void> {

View file

@ -1,5 +1,4 @@
import EventEmitter from 'events'
import type WebSocket from 'ws'
import { tl } from '@mtcute/tl'
@ -14,13 +13,13 @@ let ws: {
if (typeof window === 'undefined' || typeof window.WebSocket === 'undefined') {
try {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
ws = require('ws')
} catch (e) {
ws = null
}
} else {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
ws = window.WebSocket as any
ws = window.WebSocket
}
const subdomainsMap: Record<string, string> = {
@ -93,7 +92,9 @@ export abstract class BaseWebSocketTransport
}
connect(dc: tl.RawDcOption, testMode: boolean): void {
if (this._state !== TransportState.Idle) { throw new Error('Transport is not IDLE') }
if (this._state !== TransportState.Idle) {
throw new Error('Transport is not IDLE')
}
if (!this.packetCodecInitialized) {
this._packetCodec.setup?.(this._crypto, this.log)
@ -117,8 +118,9 @@ export abstract class BaseWebSocketTransport
this._socket.binaryType = 'arraybuffer'
this._socket.addEventListener('message', (evt) =>
// eslint-disable-next-line @typescript-eslint/no-explicit-any
this._packetCodec.feed(typedArrayToBuffer(evt.data as any)),
this._packetCodec.feed(
typedArrayToBuffer(evt.data as NodeJS.TypedArray),
),
)
this._socket.addEventListener('open', this.handleConnect.bind(this))
this._socket.addEventListener('error', this.handleError.bind(this))
@ -138,22 +140,32 @@ export abstract class BaseWebSocketTransport
this._packetCodec.reset()
}
async handleError({ error }: { error: Error }): Promise<void> {
handleError(event: Event | { error: Error }): void {
const error =
'error' in event ?
event.error :
new Error('unknown WebSocket error')
this.log.error('error: %s', error.stack)
this.emit('error', error)
}
async handleConnect(): Promise<void> {
handleConnect(): void {
this.log.info('connected')
const initialMessage = await this._packetCodec.tag()
Promise.resolve(this._packetCodec.tag())
.then((initialMessage) => {
this._socket!.send(initialMessage)
this._state = TransportState.Ready
this.emit('ready')
})
.catch((err) => this.emit('error', err))
}
async send(bytes: Buffer): Promise<void> {
if (this._state !== TransportState.Ready) { throw new Error('Transport is not READY') }
if (this._state !== TransportState.Ready) {
throw new Error('Transport is not READY')
}
const framed = await this._packetCodec.encode(bytes)

View file

@ -1,16 +1,20 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import type * as exitHookNs from 'exit-hook'
import type * as fsNs from 'fs'
import { JsonMemoryStorage } from './json'
let fs: any = null
type fs = typeof fsNs
let fs: fs | null = null
try {
fs = require('fs')
fs = require('fs') as fs
} catch (e) {}
let exitHook: any = null
type exitHook = typeof exitHookNs
let exitHook: exitHook | null = null
try {
exitHook = require('exit-hook')
exitHook = require('exit-hook') as exitHook
} catch (e) {}
export class JsonFileStorage extends JsonMemoryStorage {
@ -47,7 +51,9 @@ export class JsonFileStorage extends JsonMemoryStorage {
) {
super()
if (!fs || !fs.readFile) { throw new Error('Node fs module is not available!') }
if (!fs || !fs.readFile) {
throw new Error('Node fs module is not available!')
}
this._filename = filename
this._safe = params?.safe ?? true
@ -60,7 +66,9 @@ export class JsonFileStorage extends JsonMemoryStorage {
}
if (this._cleanup) {
this._unsubscribe = exitHook(this._onProcessExit.bind(this))
this._unsubscribe = exitHook!.default(
this._onProcessExit.bind(this),
)
}
}
@ -68,12 +76,8 @@ export class JsonFileStorage extends JsonMemoryStorage {
try {
this._loadJson(
await new Promise((res, rej) =>
fs.readFile(
this._filename,
'utf-8',
(err?: Error, data?: string) =>
err ? rej(err) : res(data!),
fs!.readFile(this._filename, 'utf-8', (err, data) =>
err ? rej(err) : res(data),
),
),
)
@ -82,16 +86,16 @@ export class JsonFileStorage extends JsonMemoryStorage {
save(): Promise<void> {
return new Promise((resolve, reject) => {
fs.writeFile(
fs!.writeFile(
this._safe ? this._filename + '.tmp' : this._filename,
this._saveJson(),
(err?: Error) => {
(err) => {
if (err) reject(err)
else if (this._safe) {
fs.rename(
fs!.rename(
this._filename + '.tmp',
this._filename,
(err?: any) => {
(err) => {
if (err && err.code !== 'ENOENT') reject(err)
else resolve()
},
@ -106,12 +110,12 @@ export class JsonFileStorage extends JsonMemoryStorage {
// on exit handler must be synchronous, thus we use sync methods here
try {
fs.writeFileSync(this._filename, this._saveJson())
fs!.writeFileSync(this._filename, this._saveJson())
} catch (e) {}
if (this._safe) {
try {
fs.unlinkSync(this._filename + '.tmp')
fs!.unlinkSync(this._filename + '.tmp')
} catch (e) {}
}
}

View file

@ -1,5 +1,8 @@
/* eslint-disable @typescript-eslint/no-unsafe-return */
import { tl } from '@mtcute/tl'
import { longFromFastString, longToFastString } from '../utils'
import { MemoryStorage } from './memory'
import { MemorySessionState, MemoryStorage } from './memory'
/**
* Helper class that provides json serialization functions
@ -12,7 +15,7 @@ export class JsonMemoryStorage extends MemoryStorage {
if (key === 'authKeys') {
const ret: Record<string, Buffer> = {}
value.split('|').forEach((pair: string) => {
;(value as string).split('|').forEach((pair: string) => {
const [dcId, b64] = pair.split(',')
ret[dcId] = Buffer.from(b64, 'base64')
})
@ -21,27 +24,26 @@ export class JsonMemoryStorage extends MemoryStorage {
}
if (key === 'accessHash') {
return longFromFastString(value)
return longFromFastString(value as string)
}
return value
}),
}) as MemorySessionState,
)
}
protected _saveJson(): string {
return JSON.stringify(this._state, (key, value) => {
if (key === 'authKeys') {
return Object.entries(value)
.filter((it) => it[1] !== null)
.map(
([dcId, key]) =>
dcId + ',' + (key as Buffer).toString('base64'),
)
const value_ = value as Record<string, Buffer | null>
return Object.entries(value_)
.filter((it): it is [string, Buffer] => it[1] !== null)
.map(([dcId, key]) => dcId + ',' + key.toString('base64'))
.join('|')
}
if (key === 'accessHash') {
return longToFastString(value)
return longToFastString(value as tl.Long)
}
return value

View file

@ -15,7 +15,7 @@ export class LocalstorageStorage extends JsonMemoryStorage {
load(): void {
try {
this._loadJson(localStorage[this._key])
this._loadJson(localStorage[this._key] as string)
} catch (e) {}
}

View file

@ -210,7 +210,9 @@ export class MemoryStorage implements ITelegramStorage, IStateStorage {
if (tempIndex !== undefined) {
const k = `${dcId}:${tempIndex}`
if (Date.now() > (this._state.authKeysTempExpiry[k] ?? 0)) { return null }
if (Date.now() > (this._state.authKeysTempExpiry[k] ?? 0)) {
return null
}
return this._state.authKeysTemp[k]
}
@ -248,7 +250,9 @@ export class MemoryStorage implements ITelegramStorage, IStateStorage {
}
}
if (peer.username) { this._state.usernameIndex[peer.username.toLowerCase()] = peer.id }
if (peer.username) {
this._state.usernameIndex[peer.username.toLowerCase()] = peer.id
}
if (peer.phone) this._state.phoneIndex[peer.phone] = peer.id
this._state.entities[peer.id] = peer
}
@ -357,7 +361,7 @@ export class MemoryStorage implements ITelegramStorage, IStateStorage {
// IStateStorage implementation
getState(key: string): unknown | null {
getState(key: string): unknown {
const val = this._state.fsm[key]
if (!val) return null

View file

@ -35,9 +35,10 @@ export class AsyncLock {
return this.acquire()
.then(() => func())
.catch((e) => (err = e))
.catch((e) => void (err = e))
.then(() => {
this.release()
// eslint-disable-next-line @typescript-eslint/no-throw-literal
if (err) throw err
})
}

View file

@ -1,3 +1,5 @@
import type * as forgeNs from 'node-forge'
import { MaybeAsync } from '../../types'
import {
BaseCryptoProvider,
@ -5,11 +7,11 @@ import {
IEncryptionScheme,
} from './abstract'
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let forge: any = null
type forge = typeof forgeNs
let forge: forge | null = null
try {
forge = require('node-forge')
forge = require('node-forge') as forge
} catch (e) {}
export class ForgeCryptoProvider
@ -26,14 +28,14 @@ export class ForgeCryptoProvider
}
createAesCtr(key: Buffer, iv: Buffer, encrypt: boolean): IEncryptionScheme {
const cipher = forge.cipher[
const cipher = forge!.cipher[
encrypt ? 'createCipher' : 'createDecipher'
]('AES-CTR', key.toString('binary'))
cipher.start({ iv: iv.toString('binary') })
const update = (data: Buffer): Buffer => {
cipher.output.data = ''
cipher.update(forge.util.createBuffer(data.toString('binary')))
cipher.update(forge!.util.createBuffer(data.toString('binary')))
return Buffer.from(cipher.output.data, 'binary')
}
@ -49,19 +51,24 @@ export class ForgeCryptoProvider
return {
encrypt(data: Buffer) {
const cipher = forge.cipher.createCipher('AES-ECB', keyBuffer)
const cipher = forge!.cipher.createCipher('AES-ECB', keyBuffer)
cipher.start({})
// @ts-expect-error wrong types
cipher.mode.pad = cipher.mode.unpad = false
cipher.update(forge.util.createBuffer(data.toString('binary')))
cipher.update(forge!.util.createBuffer(data.toString('binary')))
cipher.finish()
return Buffer.from(cipher.output.data, 'binary')
},
decrypt(data: Buffer) {
const cipher = forge.cipher.createDecipher('AES-ECB', keyBuffer)
const cipher = forge!.cipher.createDecipher(
'AES-ECB',
keyBuffer,
)
cipher.start({})
// @ts-expect-error wrong types
cipher.mode.pad = cipher.mode.unpad = false
cipher.update(forge.util.createBuffer(data.toString('binary')))
cipher.update(forge!.util.createBuffer(data.toString('binary')))
cipher.finish()
return Buffer.from(cipher.output.data, 'binary')
@ -77,12 +84,13 @@ export class ForgeCryptoProvider
algo = 'sha512',
): MaybeAsync<Buffer> {
return new Promise((resolve, reject) =>
forge.pkcs5.pbkdf2(
forge!.pkcs5.pbkdf2(
password.toString('binary'),
salt.toString('binary'),
iterations,
keylen,
forge.md[algo].create(),
// eslint-disable-next-line
(forge!.md as any)[algo].create(),
(err: Error | null, buf: string) =>
err !== null ?
reject(err) :
@ -93,7 +101,7 @@ export class ForgeCryptoProvider
sha1(data: Buffer): MaybeAsync<Buffer> {
return Buffer.from(
forge.md.sha1.create().update(data.toString('binary')).digest()
forge!.md.sha1.create().update(data.toString('binary')).digest()
.data,
'binary',
)
@ -101,14 +109,14 @@ export class ForgeCryptoProvider
sha256(data: Buffer): MaybeAsync<Buffer> {
return Buffer.from(
forge.md.sha256.create().update(data.toString('binary')).digest()
forge!.md.sha256.create().update(data.toString('binary')).digest()
.data,
'binary',
)
}
hmacSha256(data: Buffer, key: Buffer): MaybeAsync<Buffer> {
const hmac = forge.hmac.create()
const hmac = forge!.hmac.create()
hmac.start('sha256', key.toString('binary'))
hmac.update(data.toString('binary'))

View file

@ -16,10 +16,6 @@ import {
export class NodeCryptoProvider
extends BaseCryptoProvider
implements ICryptoProvider {
constructor() {
super()
}
createAesCtr(key: Buffer, iv: Buffer, encrypt: boolean): IEncryptionScheme {
const cipher = (encrypt ? createCipheriv : createDecipheriv)(
`aes-${key.length * 8}-ctr`,

View file

@ -81,7 +81,7 @@ export async function computeSrpParams(
// nice naming thx durov
if (
!request.currentAlgo ||
request.currentAlgo?._ !==
request.currentAlgo._ !==
'passwordKdfAlgoSHA256SHA256PBKDF2HMACSHA512iter100000SHA256ModPow'
) {
throw new Error(`Unknown algo ${request.currentAlgo?._}`)

View file

@ -51,7 +51,7 @@ export class Deque<T> {
}
}
this._elements = new Array(capacity)
this._elements = new Array<T | undefined>(capacity)
this._capacity = capacity
}
@ -63,7 +63,7 @@ export class Deque<T> {
const newCapacity = n << 1
if (newCapacity < 0) throw new Error('Deque is too big')
const arr = new Array(newCapacity)
const arr = new Array<T | undefined>(newCapacity)
// copy items to the new array
// copy head till the end of arr
@ -251,7 +251,7 @@ export class Deque<T> {
}
clear(): void {
this._elements = new Array(this._capacity)
this._elements = new Array<T | undefined>(this._capacity)
this._head = this._tail = 0
}
}

View file

@ -9,7 +9,7 @@ if (typeof process !== 'undefined') {
defaultLogLevel = envLogLevel
}
} else if (typeof localStorage !== 'undefined') {
const localLogLevel = parseInt(localStorage.MTCUTE_LOG_LEVEL)
const localLogLevel = parseInt(localStorage.MTCUTE_LOG_LEVEL as string)
if (!isNaN(localLogLevel)) {
defaultLogLevel = localLogLevel
@ -62,10 +62,10 @@ export class Logger {
// custom formatters
if (
fmt.indexOf('%h') > -1 ||
fmt.indexOf('%b') > -1 ||
fmt.indexOf('%j') > -1 ||
fmt.indexOf('%l') > -1
fmt.includes('%h') ||
fmt.includes('%b') ||
fmt.includes('%j') ||
fmt.includes('%l')
) {
let idx = 0
fmt = fmt.replace(FORMATTER_RE, (m) => {
@ -89,7 +89,9 @@ export class Logger {
v.type === 'Buffer' &&
Array.isArray(v.data)
) {
let str = Buffer.from(v.data).toString('base64')
let str = Buffer.from(
v.data as number[],
).toString('base64')
if (str.length > 300) {
str = str.slice(0, 300) + '...'
@ -98,6 +100,7 @@ export class Logger {
return str
}
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
return v
})
}
@ -152,7 +155,7 @@ export class LogManager extends Logger {
constructor(tag = 'base') {
// workaround because we cant pass this to super
// eslint-disable-next-line @typescript-eslint/no-explicit-any
// eslint-disable-next-line @typescript-eslint/no-explicit-any,@typescript-eslint/no-unsafe-argument
super(null as any, tag)
// eslint-disable-next-line @typescript-eslint/no-explicit-any
;(this as any).mgr = this

View file

@ -1,4 +1,5 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/no-explicit-any,@typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/no-unsafe-argument,@typescript-eslint/no-unsafe-return */
// ^^ because of performance reasons
import Long from 'long'
@ -84,7 +85,9 @@ export function longFromFastString(val: string, unsigned = false): Long {
const low = parseInt(parts[0])
const high = parseInt(parts[1])
if (isNaN(low) || isNaN(high)) { throw new Error(`Invalid long fast string: ${val}`) }
if (isNaN(low) || isNaN(high)) {
throw new Error(`Invalid long fast string: ${val}`)
}
return new Long(low, high, unsigned)
}
@ -166,19 +169,19 @@ export class LongMap<V> {
}
private _setForObj(key: Long, value: V): void {
this._obj![longToFastString(key)] = value
this._obj[longToFastString(key)] = value
}
private _hasForObj(key: Long): boolean {
return longToFastString(key) in this._obj!
return longToFastString(key) in this._obj
}
private _getForObj(key: Long): V | undefined {
return this._obj![longToFastString(key)]
return this._obj[longToFastString(key)]
}
private _deleteForObj(key: Long): void {
delete this._obj![longToFastString(key)]
delete this._obj[longToFastString(key)]
}
private *_keysForObj(unsigned?: boolean): IterableIterator<Long> {
@ -188,7 +191,7 @@ export class LongMap<V> {
}
private *_valuesForObj(): IterableIterator<V> {
yield* Object.values(this._obj!) as any
yield* Object.values(this._obj) as any
}
private _clearForObj(): void {
@ -254,22 +257,22 @@ export class LongSet {
private _addForObj(val: Long) {
const k = longToFastString(val)
if (k in this._obj!) return
if (k in this._obj) return
this._obj![k] = true
this._obj[k] = true
this._objSize! += 1
}
private _deleteForObj(val: Long) {
const k = longToFastString(val)
if (!(k in this._obj!)) return
if (!(k in this._obj)) return
delete this._obj![k]
delete this._obj[k]
this._objSize! -= 1
}
private _hasForObj(val: Long) {
return longToFastString(val) in this._obj!
return longToFastString(val) in this._obj
}
private _clearForObj() {

View file

@ -1,4 +1,5 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/no-explicit-any,@typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/no-unsafe-argument,@typescript-eslint/no-unsafe-return */
// ^^ because of performance reasons
import { LongMap } from './long-utils'
@ -130,7 +131,7 @@ export class LruMap<K extends string | number, V> {
if (oldest) {
if (oldest.p) {
this._last = oldest.p
this._last!.n = undefined
this._last.n = undefined
} else {
// exhausted
this._last = undefined

View file

@ -1,4 +1,5 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/no-explicit-any,@typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/no-unsafe-argument */
// ^^ because of performance reasons
import Long from 'long'

View file

@ -12,7 +12,7 @@ describe('IntermediatePacketCodec', () => {
it('should correctly parse immediate framing', (done) => {
const codec = new IntermediatePacketCodec()
codec.on('packet', (data) => {
codec.on('packet', (data: Buffer) => {
expect([...data]).eql([5, 1, 2, 3, 4])
done()
})
@ -21,7 +21,7 @@ describe('IntermediatePacketCodec', () => {
it('should correctly parse incomplete framing', (done) => {
const codec = new IntermediatePacketCodec()
codec.on('packet', (data) => {
codec.on('packet', (data: Buffer) => {
expect([...data]).eql([5, 1, 2, 3, 4])
done()
})
@ -34,7 +34,7 @@ describe('IntermediatePacketCodec', () => {
let number = 0
codec.on('packet', (data) => {
codec.on('packet', (data: Buffer) => {
if (number === 0) {
expect([...data]).eql([5, 1, 2, 3, 4])
number = 1
@ -51,7 +51,7 @@ describe('IntermediatePacketCodec', () => {
it('should correctly parse transport errors', (done) => {
const codec = new IntermediatePacketCodec()
codec.on('error', (err) => {
codec.on('error', (err: TransportError) => {
expect(err).to.have.instanceOf(TransportError)
expect(err.code).eq(404)
done()
@ -63,7 +63,7 @@ describe('IntermediatePacketCodec', () => {
it('should reset when called reset()', (done) => {
const codec = new IntermediatePacketCodec()
codec.on('packet', (data) => {
codec.on('packet', (data: Buffer) => {
expect([...data]).eql([1, 2, 3, 4, 5])
done()
})

View file

@ -4,6 +4,7 @@
"outDir": "./dist"
},
"include": [
"./src"
"./src",
"./tests"
]
}

View file

@ -36,7 +36,7 @@ export class CallbackDataBuilder<T extends string> {
.map((f) => {
const val = obj[f]
if (val.indexOf(this.sep) > -1) {
if (val.includes(this.sep)) {
throw new MtArgumentError(
`Value for ${f} ${val} contains separator ${this.sep} and cannot be used.`,
)

View file

@ -1,4 +1,4 @@
/* eslint-disable max-depth */
/* eslint-disable */
/* eslint-disable @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types */
// ^^ will be looked into in MTQ-29
@ -122,7 +122,7 @@ export class Dispatcher<State = never, SceneName extends string = string> {
constructor(
client?: TelegramClient | IStateStorage | StateKeyDelegate,
storage?: IStateStorage | StateKeyDelegate,
key?: StateKeyDelegate,
key?: StateKeyDelegate
) {
this.dispatchRawUpdate = this.dispatchRawUpdate.bind(this)
this.dispatchUpdate = this.dispatchUpdate.bind(this)
@ -196,15 +196,14 @@ export class Dispatcher<State = never, SceneName extends string = string> {
*/
dispatchRawUpdate(
update: tl.TypeUpdate | tl.TypeMessage,
peers: PeersIndex,
peers: PeersIndex
): void {
if (!this._client) return
// order does not matter in the dispatcher,
// so we can handle each update in its own task
this.dispatchRawUpdateNow(update, peers).catch((err) =>
// eslint-disable-next-line dot-notation
this._client!['_emitError'](err),
this._client!['_emitError'](err)
)
}
@ -222,7 +221,7 @@ export class Dispatcher<State = never, SceneName extends string = string> {
*/
async dispatchRawUpdateNow(
update: tl.TypeUpdate | tl.TypeMessage,
peers: PeersIndex,
peers: PeersIndex
): Promise<boolean> {
if (!this._client) return false
@ -282,8 +281,7 @@ export class Dispatcher<State = never, SceneName extends string = string> {
// order does not matter in the dispatcher,
// so we can handle each update in its own task
this.dispatchUpdateNow(update).catch((err) =>
// eslint-disable-next-line dot-notation
this._client!['_emitError'](err),
this._client!['_emitError'](err)
)
}
@ -307,7 +305,7 @@ export class Dispatcher<State = never, SceneName extends string = string> {
// this is getting a bit crazy lol
parsedState?: UpdateState<State, SceneName> | null,
parsedScene?: string | null,
forceScene?: true,
forceScene?: true
): Promise<boolean> {
if (!this._client) return false
@ -349,7 +347,7 @@ export class Dispatcher<State = never, SceneName extends string = string> {
update,
parsedState,
parsedScene,
true,
true
)
}
}
@ -369,17 +367,16 @@ export class Dispatcher<State = never, SceneName extends string = string> {
if (
!this._customStateKeyDelegate ||
(customKey = await this._customStateKeyDelegate(
update.data,
update.data
))
) {
parsedState = new UpdateState(
this._storage!,
this._storage,
key,
this._scene ?? null,
this._sceneScoped,
this._customStorage,
customKey,
customKey
)
}
} else {
@ -421,12 +418,12 @@ export class Dispatcher<State = never, SceneName extends string = string> {
!h.check ||
(await h.check(
update.data as any,
parsedState as never,
parsedState as never
))
) {
result = await h.callback(
update.data as any,
parsedState as never,
parsedState as never
)
handled = true
} else continue
@ -443,16 +440,15 @@ export class Dispatcher<State = never, SceneName extends string = string> {
case 'scene': {
if (!parsedState) {
throw new MtArgumentError(
'Cannot use ToScene without state',
'Cannot use ToScene without state'
)
}
// eslint-disable-next-line dot-notation
const scene = parsedState['_scene']
if (!scene) {
throw new MtArgumentError(
'Cannot use ToScene without entering a scene',
'Cannot use ToScene without entering a scene'
)
}
@ -462,7 +458,7 @@ export class Dispatcher<State = never, SceneName extends string = string> {
update,
undefined,
scene,
true,
true
)
}
}
@ -474,7 +470,7 @@ export class Dispatcher<State = never, SceneName extends string = string> {
const handled = await this._errorHandler(
e,
update,
parsedState as never,
parsedState as never
)
if (!handled) throw e
} else {
@ -527,7 +523,7 @@ export class Dispatcher<State = never, SceneName extends string = string> {
*/
removeUpdateHandler(
handler: UpdateHandler | UpdateHandler['name'] | 'all',
group = 0,
group = 0
): void {
if (group !== -1 && !(group in this._groups)) {
return
@ -581,7 +577,7 @@ export class Dispatcher<State = never, SceneName extends string = string> {
update: ParsedUpdate & T,
state?: UpdateState<State, SceneName>
) => MaybeAsync<boolean>)
| null,
| null
): void {
if (handler) this._errorHandler = handler
else this._errorHandler = undefined
@ -605,7 +601,7 @@ export class Dispatcher<State = never, SceneName extends string = string> {
update: ParsedUpdate & T,
state?: UpdateState<State, SceneName>
) => MaybeAsync<PropagationAction | void>)
| null,
| null
): void {
if (handler) this._preUpdateHandler = handler
else this._preUpdateHandler = undefined
@ -630,7 +626,7 @@ export class Dispatcher<State = never, SceneName extends string = string> {
update: ParsedUpdate & T,
state?: UpdateState<State, SceneName>
) => MaybeAsync<void>)
| null,
| null
): void {
if (handler) this._postUpdateHandler = handler
else this._postUpdateHandler = undefined
@ -643,9 +639,11 @@ export class Dispatcher<State = never, SceneName extends string = string> {
propagateErrorToParent(
err: Error,
update: ParsedUpdate,
state?: UpdateState<State, SceneName>,
state?: UpdateState<State, SceneName>
): MaybeAsync<boolean> {
if (!this.parent) { throw new MtArgumentError('This dispatcher is not a child') }
if (!this.parent) {
throw new MtArgumentError('This dispatcher is not a child')
}
if (this.parent._errorHandler) {
return this.parent._errorHandler(err, update, state)
@ -667,9 +665,9 @@ export class Dispatcher<State = never, SceneName extends string = string> {
if (child._client) {
throw new MtArgumentError(
'Provided dispatcher is ' +
(child._parent ?
'already a child. Use parent.removeChild() before calling addChild()' :
'already bound to a client. Use unbind() before calling addChild()'),
(child._parent
? 'already a child. Use parent.removeChild() before calling addChild()'
: 'already bound to a client. Use unbind() before calling addChild()')
)
}
@ -695,7 +693,7 @@ export class Dispatcher<State = never, SceneName extends string = string> {
* @param child Other dispatcher
*/
addChild(child: Dispatcher<State, SceneName>): void {
if (this._children.indexOf(child) > -1) return
if (this._children.includes(child)) return
this._prepareChild(child)
this._children.push(child)
@ -740,13 +738,13 @@ export class Dispatcher<State = never, SceneName extends string = string> {
addScene(
uid: SceneName,
scene: Dispatcher<any, SceneName>,
scoped = true,
scoped = true
): void {
if (!this._scenes) this._scenes = {}
if (uid in this._scenes) {
throw new MtArgumentError(
`Scene with UID ${uid} is already registered!`,
`Scene with UID ${uid} is already registered!`
)
}
@ -756,7 +754,7 @@ export class Dispatcher<State = never, SceneName extends string = string> {
if (scene._scene) {
throw new MtArgumentError(
`This dispatcher is already registered as scene ${scene._scene}`,
`This dispatcher is already registered as scene ${scene._scene}`
)
}
@ -807,7 +805,7 @@ export class Dispatcher<State = never, SceneName extends string = string> {
extend(other: Dispatcher<State, SceneName>): void {
if (other._customStorage || other._customStateKeyDelegate) {
throw new MtArgumentError(
'Provided dispatcher has custom storage and cannot be extended from.',
'Provided dispatcher has custom storage and cannot be extended from.'
)
}
@ -849,7 +847,7 @@ export class Dispatcher<State = never, SceneName extends string = string> {
this.addScene(
key as any,
myScenes[key] as any,
myScenes[key]._sceneScoped as any,
myScenes[key]._sceneScoped as any
)
})
}
@ -903,7 +901,7 @@ export class Dispatcher<State = never, SceneName extends string = string> {
key as any,
scene as any,
this._scenes![key]._sceneScoped as any,
this._scenes![key]._sceneScoped as any
)
})
}
@ -937,22 +935,21 @@ export class Dispatcher<State = never, SceneName extends string = string> {
object: Parameters<StateKeyDelegate>[0]
): Promise<UpdateState<S, SceneName>>
getState<S = State>(
object: string | Parameters<StateKeyDelegate>[0],
object: string | Parameters<StateKeyDelegate>[0]
): MaybeAsync<UpdateState<S, SceneName>> {
if (!this._storage) {
throw new MtArgumentError(
'Cannot use getUpdateState() filter without state storage',
'Cannot use getUpdateState() filter without state storage'
)
}
if (typeof object === 'string') {
return new UpdateState(
this._storage!,
this._storage,
object,
this._scene ?? null,
this._sceneScoped,
this._customStorage,
this._customStorage
)
}
@ -963,12 +960,11 @@ export class Dispatcher<State = never, SceneName extends string = string> {
if (!this._customStateKeyDelegate) {
return new UpdateState(
this._storage!,
key,
this._scene ?? null,
this._sceneScoped,
this._customStorage,
this._customStorage
)
}
@ -976,20 +972,19 @@ export class Dispatcher<State = never, SceneName extends string = string> {
(customKey) => {
if (!customKey) {
throw new MtArgumentError(
'Cannot derive custom key from given object',
'Cannot derive custom key from given object'
)
}
return new UpdateState(
this._storage!,
key,
this._scene ?? null,
this._sceneScoped,
this._customStorage,
customKey,
customKey
)
},
}
)
})
}
@ -1001,7 +996,7 @@ export class Dispatcher<State = never, SceneName extends string = string> {
* ignoring local custom storage, key delegate and scene scope.
*/
getGlobalState<T>(
object: Parameters<StateKeyDelegate>[0],
object: Parameters<StateKeyDelegate>[0]
): Promise<UpdateState<T, SceneName>> {
if (!this._parent) {
throw new MtArgumentError('This dispatcher does not have a parent')
@ -1013,11 +1008,10 @@ export class Dispatcher<State = never, SceneName extends string = string> {
}
return new UpdateState(
this._storage!,
key,
this._scene ?? null,
false,
false
)
})
}
@ -1028,7 +1022,7 @@ export class Dispatcher<State = never, SceneName extends string = string> {
name: UpdateHandler['name'],
filter: any,
handler?: any,
group?: number,
group?: number
): void {
if (typeof handler === 'number' || typeof handler === 'undefined') {
this.addUpdateHandler(
@ -1036,7 +1030,7 @@ export class Dispatcher<State = never, SceneName extends string = string> {
name,
callback: filter,
},
handler,
handler
)
} else {
this.addUpdateHandler(
@ -1045,7 +1039,7 @@ export class Dispatcher<State = never, SceneName extends string = string> {
callback: handler,
check: filter,
},
group,
group
)
}
}

View file

@ -209,18 +209,18 @@ export namespace filters {
fn2: UpdateFilter<Base, Mod2, State2>,
): UpdateFilter<Base, Mod1 & Mod2, State1 | State2> {
return (upd, state) => {
const res1 = fn1(upd, state as any)
const res1 = fn1(upd, state as UpdateState<State1>)
if (typeof res1 === 'boolean') {
if (!res1) return false
return fn2(upd, state as any)
return fn2(upd, state as UpdateState<State2>)
}
return res1.then((r1) => {
if (!r1) return false
return fn2(upd, state as any)
return fn2(upd, state as UpdateState<State2>)
})
}
}
@ -247,18 +247,18 @@ export namespace filters {
fn2: UpdateFilter<Base, Mod2, State2>,
): UpdateFilter<Base, Mod1 | Mod2, State1 | State2> {
return (upd, state) => {
const res1 = fn1(upd, state as any)
const res1 = fn1(upd, state as UpdateState<State1>)
if (typeof res1 === 'boolean') {
if (res1) return true
return fn2(upd, state as any)
return fn2(upd, state as UpdateState<State2>)
}
return res1.then((r1) => {
if (r1) return true
return fn2(upd, state as any)
return fn2(upd, state as UpdateState<State2>)
})
}
}
@ -947,7 +947,7 @@ export namespace filters {
const m = txt.match(regex)
if (m) {
(obj as any).match = m
(obj as typeof obj & { match: RegExpMatchArray }).match = m
return true
}
@ -1002,14 +1002,14 @@ export namespace filters {
return (obj) => {
const txt = extractText(obj)
return txt != null && txt.toLowerCase().indexOf(str) > -1
return txt != null && txt.toLowerCase().includes(str)
}
}
return (obj) => {
const txt = extractText(obj)
return txt != null && txt.indexOf(str) > -1
return txt != null && txt.includes(str)
}
}
@ -1157,13 +1157,16 @@ export namespace filters {
// we use .replace to iterate over global regex, not to replace the text
withoutPrefix
.slice(m[0].length)
.replace(argumentsRe, ($0, $1, $2, $3) => {
.replace(
argumentsRe,
($0, $1, $2: string, $3: string) => {
match.push(
($2 || $3 || '').replace(unescapeRe, '$1'),
)
return ''
})
},
)
;(msg as Message & { command: string[] }).command = match
return true

View file

@ -18,7 +18,7 @@ export interface IStateStorage {
*
* @param key Key of the state, as defined by {@link StateKeyDelegate}
*/
getState(key: string): MaybeAsync<unknown | null>
getState(key: string): MaybeAsync<unknown>
/**
* Save state to the storage

View file

@ -4,6 +4,7 @@
"outDir": "./dist"
},
"include": [
"./src"
"./src",
"./tests"
]
}

View file

@ -4,6 +4,7 @@
"outDir": "./dist"
},
"include": [
"./src"
"./src",
"./tests",
]
}

View file

@ -28,9 +28,14 @@ export function html(
if (typeof it === 'boolean' || !it) return
if (typeof it === 'string') {
it = HtmlMessageEntityParser.escape(it, Boolean(str.match(/=['"]$/)))
it = HtmlMessageEntityParser.escape(
it,
Boolean(str.match(/=['"]$/)),
)
} else {
if (it.mode && it.mode !== 'html') { throw new Error(`Incompatible parse mode: ${it.mode}`) }
if (it.mode && it.mode !== 'html') {
throw new Error(`Incompatible parse mode: ${it.mode}`)
}
it = it.value
}
@ -92,7 +97,7 @@ export class HtmlMessageEntityParser implements IMessageEntityParser {
function processPendingText(tagEnd = false) {
if (!pendingText.length) return
if (!stacks.pre?.length) {
if (!stacks.pre.length) {
pendingText = pendingText.replace(/[^\S\u00A0]+/gs, ' ')
if (tagEnd) pendingText = pendingText.trimEnd()
@ -119,7 +124,7 @@ export class HtmlMessageEntityParser implements IMessageEntityParser {
processPendingText()
// ignore tags inside pre (except pre)
if (name !== 'pre' && stacks.pre?.length) return
if (name !== 'pre' && stacks.pre.length) return
let entity: tl.TypeMessageEntity
@ -262,9 +267,9 @@ export class HtmlMessageEntityParser implements IMessageEntityParser {
name = name.toLowerCase()
// ignore tags inside pre (except pre)
if (name !== 'pre' && stacks.pre?.length) return
if (name !== 'pre' && stacks.pre.length) return
const entity = stacks[name]?.pop()
const entity = stacks[name].pop()
if (!entity) return // unmatched close tag
@ -338,7 +343,9 @@ export class HtmlMessageEntityParser implements IMessageEntityParser {
relativeOffset = lastOffset
}
if (length <= 0 || relativeOffset >= end || relativeOffset < 0) { continue }
if (length <= 0 || relativeOffset >= end || relativeOffset < 0) {
continue
}
let skip = false

View file

@ -620,7 +620,7 @@ describe('HtmlMessageEntityParser', () => {
expect(() => html`${unsafeString}`.value).not.throw(Error)
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
// @ts-expect-error
expect(() => html`${unsafeString2}`.value).throw(Error)
})
})

View file

@ -4,6 +4,7 @@
"outDir": "./dist"
},
"include": [
"./src"
"./src",
"./tests",
]
}

View file

@ -79,7 +79,9 @@ export abstract class BaseHttpProxyTcpTransport extends BaseTcpTransport {
}
connect(dc: tl.RawDcOption): void {
if (this._state !== TransportState.Idle) { throw new Error('Transport is not IDLE') }
if (this._state !== TransportState.Idle) {
throw new Error('Transport is not IDLE')
}
if (!this.packetCodecInitialized) {
this._packetCodec.on('error', (err) => this.emit('error', err))
@ -133,9 +135,10 @@ export abstract class BaseHttpProxyTcpTransport extends BaseTcpTransport {
}
headers['Proxy-Connection'] = 'Keep-Alive'
const packet = `CONNECT ${ip} HTTP/1.1${Object.keys(headers).map(
(k) => `\r\n${k}: ${headers[k]}`,
)}\r\n\r\n`
const headersStr = Object.keys(headers)
.map((k) => `\r\n${k}: ${headers[k]}`)
.join('')
const packet = `CONNECT ${ip} HTTP/1.1${headersStr}\r\n\r\n`
this._socket!.write(packet)
this._socket!.once('data', (msg) => {

View file

@ -1,12 +1,11 @@
import type { BotChatJoinRequestUpdate, BotStoppedUpdate, CallbackQuery, ChatMemberUpdate, ChosenInlineResult, InlineQuery, Message, ParsedUpdate, PollVoteUpdate, User } from '@mtcute/client'
import type * as clientNs from '@mtcute/client'
import { I18nStrings, I18nValue } from './types'
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let client: any
let client: typeof clientNs
try {
client = require('@mtcute/client')
client = require('@mtcute/client') as typeof clientNs
} catch (e) {}
/**
@ -43,7 +42,7 @@ export function createI18nStringsIndex(
* @param update Update to extract language from
*/
export function extractLanguageFromUpdate(
update: ParsedUpdate['data'],
update: clientNs.ParsedUpdate['data'],
): string | null | undefined {
if (!client) {
throw new Error(
@ -54,23 +53,26 @@ export function extractLanguageFromUpdate(
switch (update.constructor) {
case client.Message:
// if sender is Chat it will just be undefined
return ((update as Message).sender as User).language
return ((update as clientNs.Message).sender as clientNs.User)
.language
case client.PollVoteUpdate:
// if peer is Chat it will just be undefined
return ((update as clientNs.PollVoteUpdate).peer as clientNs.User)
.language
case client.ChatMemberUpdate:
case client.InlineQuery:
case client.ChosenInlineResult:
case client.CallbackQuery:
case client.PollVoteUpdate:
case client.BotStoppedUpdate:
case client.BotChatJoinRequestUpdate:
return (
update as
| ChatMemberUpdate
| InlineQuery
| ChosenInlineResult
| CallbackQuery
| PollVoteUpdate
| BotStoppedUpdate
| BotChatJoinRequestUpdate
| clientNs.ChatMemberUpdate
| clientNs.InlineQuery
| clientNs.ChosenInlineResult
| clientNs.CallbackQuery
| clientNs.BotStoppedUpdate
| clientNs.BotChatJoinRequestUpdate
).user.language
}

View file

@ -4,6 +4,7 @@
"outDir": "./dist"
},
"include": [
"./src"
"./src",
"./tests",
]
}

View file

@ -1,11 +1,15 @@
import Long from 'long'
import type { FormattedString, IMessageEntityParser, MessageEntity, tl } from '@mtcute/client'
import type {
FormattedString,
IMessageEntityParser,
MessageEntity,
tl,
} from '@mtcute/client'
const MENTION_REGEX =
/^tg:\/\/user\?id=(\d+)(?:&hash=(-?[0-9a-fA-F]+)(?:&|$)|&|$)/
const EMOJI_REGEX =
/^tg:\/\/emoji\?id=(-?\d+)/
const EMOJI_REGEX = /^tg:\/\/emoji\?id=(-?\d+)/
const TAG_BOLD = '**'
const TAG_ITALIC = '__'
@ -41,7 +45,9 @@ export function md(
if (typeof it === 'string') it = MarkdownMessageEntityParser.escape(it)
else {
if (it.mode && it.mode !== 'markdown') { throw new Error(`Incompatible parse mode: ${it.mode}`) }
if (it.mode && it.mode !== 'markdown') {
throw new Error(`Incompatible parse mode: ${it.mode}`)
}
it = it.value
}
@ -165,7 +171,9 @@ export class MarkdownMessageEntityParser implements IMessageEntityParser {
pos += 1 // )
if (pos > text.length) { throw new Error('Malformed LINK entity, expected )') }
if (pos > text.length) {
throw new Error('Malformed LINK entity, expected )')
}
if (url.length) {
ent.length = result.length - ent.offset
@ -200,9 +208,8 @@ export class MarkdownMessageEntityParser implements IMessageEntityParser {
).userId = userId
}
} else if ((m = EMOJI_REGEX.exec(url))) {
(
ent as tl.Mutable<tl.RawMessageEntityCustomEmoji>
)._ = 'messageEntityCustomEmoji'
(ent as tl.Mutable<tl.RawMessageEntityCustomEmoji>)._ =
'messageEntityCustomEmoji'
;(
ent as tl.Mutable<tl.RawMessageEntityCustomEmoji>
).documentId = Long.fromString(m[1])
@ -224,6 +231,7 @@ export class MarkdownMessageEntityParser implements IMessageEntityParser {
pos += 1
insideLink = true
if (!('link' in stacks)) stacks.link = []
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
stacks.link.push({
offset: result.length,
length: 0,
@ -308,9 +316,7 @@ export class MarkdownMessageEntityParser implements IMessageEntityParser {
if (isBegin) {
stacks[type].push({
// this is valid, but idk how to make typescript happy
// eslint-disable-next-line @typescript-eslint/no-explicit-any
_: ('messageEntity' + type) as any,
_: `messageEntity${type}`,
offset: result.length,
length: 0,
})
@ -379,7 +385,8 @@ export class MarkdownMessageEntityParser implements IMessageEntityParser {
end += escapedPos
}
let startTag; let endTag: string
let startTag
let endTag: string
switch (type) {
case 'bold':
@ -420,7 +427,7 @@ export class MarkdownMessageEntityParser implements IMessageEntityParser {
break
case 'emoji':
startTag = '['
endTag = `](tg://emoji?id=${entity.emojiId})`
endTag = `](tg://emoji?id=${entity.emojiId!.toString()})`
break
default:
continue

View file

@ -689,7 +689,7 @@ describe('MarkdownMessageEntityParser', () => {
expect(() => md`${unsafeString}`.value).not.throw(Error)
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
// @ts-expect-error
expect(() => md`${unsafeString2}`.value).throw(Error)
})
})

View file

@ -4,6 +4,7 @@
"outDir": "./dist"
},
"include": [
"./src"
"./src",
"./tests",
]
}

View file

@ -112,8 +112,7 @@ export class MtProxyTcpTransport extends BaseTcpTransport {
this.packetCodecInitialized = false
this._packetCodec.reset()
this._packetCodec.removeAllListeners()
// eslint-disable-next-line @typescript-eslint/no-explicit-any
delete (this as any)._packetCodec
delete (this as Partial<MtProxyTcpTransport>)._packetCodec
}
if (!this._packetCodec) {
@ -155,12 +154,16 @@ export class MtProxyTcpTransport extends BaseTcpTransport {
this._socket = connect(
this._proxy.port,
this._proxy.host,
// MTQ-55
// eslint-disable-next-line @typescript-eslint/no-misused-promises
this._handleConnectFakeTls.bind(this),
)
} else {
this._socket = connect(
this._proxy.port,
this._proxy.host,
// MTQ-55
this.handleConnect.bind(this),
)
this._socket.on('data', (data) => this._packetCodec.feed(data))
@ -219,18 +222,17 @@ export class MtProxyTcpTransport extends BaseTcpTransport {
}
}
const packetHandler = async (buf: Buffer): Promise<void> => {
try {
await checkHelloResponse(buf)
const packetHandler = (buf: Buffer): void => {
checkHelloResponse(buf)
.then(() => {
this._socket!.off('data', packetHandler)
this._socket!.on('data', (data) =>
this._packetCodec.feed(data),
)
this._socket!.off('data', packetHandler)
this.handleConnect()
} catch (e) {
this._socket!.emit('error', e)
}
return this.handleConnect()
})
.catch((err) => this._socket!.emit('error', err))
}
this._socket!.write(hello)

View file

@ -16,7 +16,7 @@ export { SqliteStorage }
let nativeCrypto: any
try {
// eslint-disable-next-line @typescript-eslint/no-var-requires
// eslint-disable-next-line
nativeCrypto = require('@mtcute/crypto-node').NodeNativeCryptoProvider
} catch (e) {}
@ -57,6 +57,7 @@ export interface NodeTelegramClientOptions
export class NodeTelegramClient extends TelegramClient {
constructor(opts: NodeTelegramClientOptions) {
super({
// eslint-disable-next-line
crypto: nativeCrypto ? () => new nativeCrypto() : undefined,
...opts,
storage:
@ -68,7 +69,9 @@ export class NodeTelegramClient extends TelegramClient {
this.registerParseMode(new HtmlMessageEntityParser())
this.registerParseMode(new MarkdownMessageEntityParser())
if (opts.defaultParseMode) { this.setDefaultParseMode(opts.defaultParseMode) }
if (opts.defaultParseMode) {
this.setDefaultParseMode(opts.defaultParseMode)
}
}
private _rl?: RlInterface
@ -98,7 +101,9 @@ export class NodeTelegramClient extends TelegramClient {
if (!params.phone) params.phone = () => this.input('Phone > ')
if (!params.code) params.code = () => this.input('Code > ')
if (!params.password) { params.password = () => this.input('2FA password > ') }
if (!params.password) {
params.password = () => this.input('2FA password > ')
}
}
return super.start(params).then((user) => {

View file

@ -1,7 +1,7 @@
// ^^ because of this._socket. we know it's not null, almost everywhere, but TS doesn't
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
// @ts-expect-error
import { normalize } from 'ip6'
import { connect } from 'net'
@ -115,8 +115,12 @@ function buildSocks5Auth(username: string, password: string) {
const usernameBuf = Buffer.from(username)
const passwordBuf = Buffer.from(password)
if (usernameBuf.length > 255) { throw new Error(`Too long username (${usernameBuf.length} > 255)`) }
if (passwordBuf.length > 255) { throw new Error(`Too long password (${passwordBuf.length} > 255)`) }
if (usernameBuf.length > 255) {
throw new Error(`Too long username (${usernameBuf.length} > 255)`)
}
if (passwordBuf.length > 255) {
throw new Error(`Too long password (${passwordBuf.length} > 255)`)
}
const buf = Buffer.alloc(3 + usernameBuf.length + passwordBuf.length)
buf[0] = 0x01 // VER of auth
@ -129,7 +133,8 @@ function buildSocks5Auth(username: string, password: string) {
}
function writeIpv6(ip: string, buf: Buffer, offset: number): void {
ip = normalize(ip)
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
ip = normalize(ip) as string
const parts = ip.split(':')
if (parts.length !== 8) {
@ -193,9 +198,14 @@ export abstract class BaseSocksTcpTransport extends BaseTcpTransport {
constructor(proxy: SocksProxySettings) {
super()
if (proxy.version != null && proxy.version !== 4 && proxy.version !== 5) {
if (
proxy.version != null &&
proxy.version !== 4 &&
proxy.version !== 5
) {
throw new SocksProxyConnectionError(
proxy,
`Invalid SOCKS version: ${proxy.version}`,
)
}
@ -204,7 +214,9 @@ export abstract class BaseSocksTcpTransport extends BaseTcpTransport {
}
connect(dc: tl.RawDcOption): void {
if (this._state !== TransportState.Idle) { throw new Error('Transport is not IDLE') }
if (this._state !== TransportState.Idle) {
throw new Error('Transport is not IDLE')
}
if (!this.packetCodecInitialized) {
this._packetCodec.on('error', (err) => this.emit('error', err))

View file

@ -359,7 +359,7 @@ export class SqliteStorage implements ITelegramStorage, IStateStorage {
private _getFromKv<T>(key: string): T | null {
const row = this._statements.getKv.get(key) as { value: string } | null
return row ? JSON.parse(row.value) : null
return row ? (JSON.parse(row.value) as T) : null
}
private _setToKv(key: string, value: unknown, now = false): void {
@ -375,7 +375,7 @@ export class SqliteStorage implements ITelegramStorage, IStateStorage {
}
private _runMany!: (stmts: [sqlite3.Statement, unknown[]][]) => void
private _updateManyPeers!: (updates: unknown[]) => void
private _updateManyPeers!: (updates: unknown[][]) => void
private _upgradeDatabase(from: number): void {
if (from < 2 || from > CURRENT_VERSION) {
@ -461,14 +461,16 @@ export class SqliteStorage implements ITelegramStorage, IStateStorage {
}
// helper methods
this._runMany = this._db.transaction((stmts) => {
stmts.forEach((stmt: [sqlite3.Statement, unknown[]]) => {
this._runMany = this._db.transaction(
(stmts: [sqlite3.Statement, unknown[]][]) => {
stmts.forEach((stmt) => {
stmt[0].run(stmt[1])
})
})
},
)
this._updateManyPeers = this._db.transaction((data) => {
data.forEach((it: unknown[]) => {
this._updateManyPeers = this._db.transaction((data: unknown[][]) => {
data.forEach((it: unknown) => {
this._statements.updateCachedEnt.run(it)
})
})
@ -727,7 +729,7 @@ export class SqliteStorage implements ITelegramStorage, IStateStorage {
// IStateStorage implementation
getState(key: string, parse = true): unknown | null {
getState(key: string, parse = true): unknown {
let val: FsmItem | undefined = this._fsmCache?.get(key)
const cached = val

View file

@ -10,6 +10,8 @@ export function gzipInflate(buf: Buffer): Buffer {
return typedArrayToBuffer(inflate(buf))
}
const ERROR_SIZE_LIMIT_REACHED = 'ERR_SIZE_LIMIT_REACHED'
class DeflateLimited extends Deflate {
constructor(readonly limit: number) {
super()
@ -21,7 +23,9 @@ class DeflateLimited extends Deflate {
this._size += (chunk as Uint8Array).length
if (this._size > this.limit) {
throw 'ERR_SIZE'
// caught locally
// eslint-disable-next-line @typescript-eslint/no-throw-literal
throw ERROR_SIZE_LIMIT_REACHED
}
super.onData(chunk)
@ -36,9 +40,9 @@ export function gzipDeflate(buf: Buffer, maxRatio?: number): Buffer | null {
try {
deflator.push(buf, true)
} catch (e) {
if (e === 'ERR_SIZE') return null
if (e === ERROR_SIZE_LIMIT_REACHED) return null
throw e
}
return typedArrayToBuffer(deflator.result as Uint8Array)
return typedArrayToBuffer(deflator.result)
}

View file

@ -1,3 +1,5 @@
// eslint-disable-next-line max-len
/* eslint-disable @typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-return */
import { expect } from 'chai'
import { randomBytes } from 'crypto'
import Long from 'long'

View file

@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-unsafe-call */
import { expect } from 'chai'
import { randomBytes } from 'crypto'
import Long from 'long'

View file

@ -5,5 +5,6 @@
},
"include": [
"./src",
"./tests",
]
}

View file

@ -188,6 +188,23 @@ export function generateReaderCodeForTlEntries(
ret += generateReaderCodeForTlEntry(entry, params) + '\n'
})
const usedInBareVector: Record<string, 1> = {}
ret.replace(
new RegExp(`(?<=r\\.vector\\(${variableName}\\[)(\\d+)(?=])`, 'g'),
(_, id: string) => {
usedInBareVector[id] = 1
return _
},
)
for (const id of Object.keys(usedInBareVector)) {
ret = ret.replace(
new RegExp(`(?<=^${id}:function\\()r(?=\\))`, 'gm'),
'r=this',
)
}
if (params.includeMethodResults) {
ret += '_results:{\n'

View file

@ -60,7 +60,10 @@ export function generateWriterCodeForTlEntry(
if (entry.id === 0) entry.id = computeConstructorIdFromEntry(entry)
const name = bare ? entry.id : `'${entry.name}'`
let ret = `${name}:function(w${entry.arguments.length ? ',v' : ''}){`
const defaultWriter = bare ? '=this' : ''
let ret = `${name}:function(w${defaultWriter}${
entry.arguments.length ? ',v' : ''
}){`
if (!bare) ret += `w.uint(${entry.id});`

View file

@ -5,7 +5,8 @@ import { generateWriterCodeForTlEntries } from './codegen/writer'
import { parseTlToEntries } from './parse'
function evalForResult<T>(js: string): T {
return new Function(js)()
// eslint-disable-next-line @typescript-eslint/no-implied-eval
return new Function(js)() as T
}
/**
@ -57,7 +58,7 @@ export function patchRuntimeTlSchema(
},
// ts is not smart enough
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
// @ts-expect-error
writerMap: {
...writers,
...newWriters,

View file

@ -68,9 +68,9 @@ export function stringifyArgumentType(
if (!modifiers) return type
let ret = type
if (modifiers?.isBareUnion) ret = `%${ret}`
if (modifiers?.isVector) ret = `Vector<${ret}>`
else if (modifiers?.isBareVector) ret = `vector<${ret}>`
if (modifiers.isBareUnion) ret = `%${ret}`
if (modifiers.isVector) ret = `Vector<${ret}>`
else if (modifiers.isBareVector) ret = `vector<${ret}>`
if (modifiers.predicate) ret = `${modifiers.predicate}?${ret}`
return ret

View file

@ -91,8 +91,8 @@ describe('mergeTlSchemas', () => {
).eq(expected.join('\n'))
}
it('merges different constructors', () => {
test(
it('merges different constructors', async () => {
await test(
[
['testClass = Test;'],
['testClass2 = Test;'],
@ -106,8 +106,8 @@ describe('mergeTlSchemas', () => {
)
})
it('merges true flags in constructors', () => {
test(
it('merges true flags in constructors', async () => {
await test(
[
['test foo:flags.0?true = Test;'],
['test bar:flags.0?true = Test;'],
@ -118,8 +118,8 @@ describe('mergeTlSchemas', () => {
)
})
it('resolves conflict using user-provided option', () => {
test(
it('resolves conflict using user-provided option', async () => {
await test(
[
['test foo:int = Test;'],
['test bar:int = Test;'],
@ -128,7 +128,7 @@ describe('mergeTlSchemas', () => {
0,
'test foo:int = Test;',
)
test(
await test(
[
['test foo:int = Test;'],
['test bar:int = Test;'],
@ -137,11 +137,15 @@ describe('mergeTlSchemas', () => {
1,
'test foo:int = Test;',
)
test([['test foo:int = Test;'], [], ['test bar:int = Test;']], 1, '')
await test(
[['test foo:int = Test;'], [], ['test bar:int = Test;']],
1,
'',
)
})
it('merges comments', () => {
test(
it('merges comments', async () => {
await test(
[
['test foo:flags.0?true = Test;'],
['// test ctor', 'test bar:flags.0?true = Test;'],
@ -156,8 +160,8 @@ describe('mergeTlSchemas', () => {
)
})
it('merges arguments comments', () => {
test(
it('merges arguments comments', async () => {
await test(
[
['test foo:flags.0?true = Test;'],
['// @bar bar comment', 'test bar:flags.0?true = Test;'],

View file

@ -4,6 +4,7 @@
"outDir": "./dist"
},
"include": [
"./src/**/*.ts"
"./src/**/*.ts",
"./tests/**/*.ts"
]
}

View file

@ -21,7 +21,7 @@ import {
DOC_CACHE_FILE,
} from './constants'
import { applyDescriptionsYamlFile } from './process-descriptions-yaml'
import { packTlSchema, unpackTlSchema } from './schema'
import { packTlSchema, TlPackedSchema, unpackTlSchema } from './schema'
import { fetchRetry } from './utils'
export interface CachedDocumentationEntry {
@ -38,7 +38,10 @@ export interface CachedDocumentation {
unions: Record<string, string>
}
function normalizeLinks(url: string, el: cheerio.Cheerio<cheerio.Element>): void {
function normalizeLinks(
url: string,
el: cheerio.Cheerio<cheerio.Element>,
): void {
el.find('a').each((i, _it) => {
const it = cheerio.default(_it)
let href = it.attr('href')
@ -314,7 +317,7 @@ export async function getCachedDocumentation(): Promise<CachedDocumentation | nu
try {
const file = await readFile(DOC_CACHE_FILE, 'utf8')
return JSON.parse(file)
return JSON.parse(file) as CachedDocumentation
} catch (e: unknown) {
if (e && typeof e === 'object' && 'code' in e && e.code === 'ENOENT') {
return null
@ -355,7 +358,9 @@ async function main() {
if (act === 1) {
const [schema, layer] = unpackTlSchema(
JSON.parse(await readFile(API_SCHEMA_JSON_FILE, 'utf8')),
JSON.parse(
await readFile(API_SCHEMA_JSON_FILE, 'utf8'),
) as TlPackedSchema,
)
cached = await fetchDocumentation(schema, layer)
}
@ -381,7 +386,9 @@ async function main() {
}
const [schema, layer] = unpackTlSchema(
JSON.parse(await readFile(API_SCHEMA_JSON_FILE, 'utf8')),
JSON.parse(
await readFile(API_SCHEMA_JSON_FILE, 'utf8'),
) as TlPackedSchema,
)
applyDocumentation(schema, cached)

View file

@ -36,7 +36,7 @@ import {
fetchDocumentation,
getCachedDocumentation,
} from './documentation'
import { packTlSchema, unpackTlSchema } from './schema'
import { packTlSchema, TlPackedSchema, unpackTlSchema } from './schema'
import { fetchRetry } from './utils'
import { bumpVersion } from '~scripts/version'
@ -137,8 +137,10 @@ async function updatePackageVersion(
rl: readline.Interface,
currentLayer: number,
) {
const packageJson = JSON.parse(await readFile(PACKAGE_JSON_FILE, 'utf8'))
const version: string = packageJson.version
const packageJson = JSON.parse(
await readFile(PACKAGE_JSON_FILE, 'utf8'),
) as { version: string }
const version = packageJson.version
let [major, minor] = version.split('.').map((i) => parseInt(i))
if (major === currentLayer) {
@ -168,7 +170,7 @@ async function overrideInt53(schema: TlFullSchema): Promise<void> {
const config = JSON.parse(
await readFile(join(__dirname, '../data/int53-overrides.json'), 'utf8'),
)
) as Record<string, Record<string, string[]>>
schema.entries.forEach((entry) => {
const overrides: string[] | undefined = config[entry.kind][entry.name]
@ -274,8 +276,8 @@ async function main() {
console.log(
'Conflict detected at %s %s:',
nonEmptyOptions[0].entry?.kind,
nonEmptyOptions[0].entry?.name,
nonEmptyOptions[0].entry.kind,
nonEmptyOptions[0].entry.name,
)
console.log('0. Remove')
nonEmptyOptions.forEach((opt, idx) => {
@ -329,7 +331,9 @@ async function main() {
console.log('Writing diff to file...')
const oldSchema = unpackTlSchema(
JSON.parse(await readFile(API_SCHEMA_JSON_FILE, 'utf8')),
JSON.parse(
await readFile(API_SCHEMA_JSON_FILE, 'utf8'),
) as TlPackedSchema,
)
await writeFile(
API_SCHEMA_DIFF_JSON_FILE,

View file

@ -83,6 +83,13 @@ const virtualErrors: TlError[] = [
]
virtualErrors.forEach((it) => (it.virtual = true))
interface TelegramErrorsSpec {
errors: Record<string, Record<string, string[]>>
descriptions: Record<string, string>
user_only: string[]
bot_only: string[]
}
async function fetchFromTelegram(errors: TlErrors) {
const page = await fetch(ERRORS_PAGE_TG).then((it) => it.text())
const jsonUrl = page.match(
@ -90,9 +97,9 @@ async function fetchFromTelegram(errors: TlErrors) {
)?.[1]
if (!jsonUrl) throw new Error('Cannot find JSON URL')
const json = await fetch(new URL(jsonUrl, ERRORS_PAGE_TG)).then((it) =>
const json = (await fetch(new URL(jsonUrl, ERRORS_PAGE_TG)).then((it) =>
it.json(),
)
)) as TelegramErrorsSpec
// since nobody fucking guarantees that .descriptions
// will have description for each described here (or vice versa),
@ -120,7 +127,7 @@ async function fetchFromTelegram(errors: TlErrors) {
errors.throws[method] = []
}
if (errors.throws[method].indexOf(name) === -1) {
if (!errors.throws[method].includes(name)) {
errors.throws[method].push(name)
}
}
@ -185,14 +192,17 @@ async function fetchFromTelethon(errors: TlErrors) {
// names for better code insights
// we also prefer description from telegram, if it's available and doesn't use placeholders
if (description) {
const desc = description.replace(/{([a-z0-9_]+)}/gi, (_, name) => {
const desc = description.replace(
/{([a-z0-9_]+)}/gi,
(_, name: string) => {
if (!obj._paramNames) {
obj._paramNames = []
}
obj._paramNames.push(name)
return '%d'
})
},
)
if (!obj.description || obj._paramNames?.length) {
obj.description = desc
@ -202,13 +212,24 @@ async function fetchFromTelethon(errors: TlErrors) {
return new Promise<void>((resolve, reject) => {
parser
.on('data', ({ name, codes, description }) =>
addError(name, codes, description),
.on(
'data',
({
name,
codes,
description,
}: {
name: string
codes: string
description: string
}) => addError(name, codes, description),
)
.on('end', resolve)
.on('error', reject)
csv.text().then((it) => parser.write(it)).catch(reject)
csv.text()
.then((it) => parser.write(it))
.catch(reject)
})
}
@ -229,7 +250,7 @@ async function main() {
await fetchFromTelethon(errors)
virtualErrors.forEach((err) => {
if (errors.errors[err.name]) {
if (err.name in errors.errors) {
console.log(`Error ${err.name} already exists and is not virtual`)
return

View file

@ -29,13 +29,13 @@ async function main() {
// remove manually parsed types
entries = entries.filter(
(it) =>
[
![
'mt_msg_container',
'mt_message',
'mt_msg_copy',
'mt_gzip_packed',
'mt_rpc_result',
].indexOf(it.name) === -1,
].includes(it.name),
)
// mtproto is handled internally, for simplicity we make them all classes

View file

@ -6,6 +6,7 @@ import {
generateTypescriptDefinitionsForTlSchema,
generateWriterCodeForTlEntries,
parseFullTlSchema,
TlEntry,
TlErrors,
TlFullSchema,
} from '@mtcute/tl-utils'
@ -16,7 +17,7 @@ import {
ESM_PRELUDE,
MTP_SCHEMA_JSON_FILE,
} from './constants'
import { unpackTlSchema } from './schema'
import { TlPackedSchema, unpackTlSchema } from './schema'
const OUT_TYPINGS_FILE = join(__dirname, '../index.d.ts')
const OUT_TYPINGS_JS_FILE = join(__dirname, '../index.js')
@ -92,15 +93,17 @@ async function generateWriters(
}
async function main() {
const errors: TlErrors = JSON.parse(
const errors = JSON.parse(
await readFile(ERRORS_JSON_FILE, 'utf8'),
)
) as TlErrors
const [apiSchema, apiLayer] = unpackTlSchema(
JSON.parse(await readFile(API_SCHEMA_JSON_FILE, 'utf8')),
JSON.parse(
await readFile(API_SCHEMA_JSON_FILE, 'utf8'),
) as TlPackedSchema,
)
const mtpSchema = parseFullTlSchema(
JSON.parse(await readFile(MTP_SCHEMA_JSON_FILE, 'utf8')),
JSON.parse(await readFile(MTP_SCHEMA_JSON_FILE, 'utf8')) as TlEntry[],
)
await generateTypings(apiSchema, apiLayer, mtpSchema, errors)

View file

@ -66,7 +66,7 @@ export function applyDescriptionsYamlFile(
prefix: string,
) {
for (const name in obj) {
objIndex[prefix + name] = obj[name]
objIndex[prefix + name] = obj[name]!
}
}
@ -75,7 +75,7 @@ export function applyDescriptionsYamlFile(
// process byObjects
for (const name in byObjects) {
const rules = byObjects[name]
const rules = byObjects[name]!
const obj = objIndex[name]
if (!obj) continue
@ -88,7 +88,7 @@ export function applyDescriptionsYamlFile(
if (rules.arguments) {
for (const arg in rules.arguments) {
const repl = unwrapMaybe(
rules.arguments[arg],
rules.arguments[arg]!,
obj.arguments !== undefined && arg in obj.arguments,
)
@ -102,15 +102,14 @@ export function applyDescriptionsYamlFile(
// process byArguments
for (const i in objIndex) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const obj = objIndex[i] as any
const obj = objIndex[i]!
for (const arg in byArguments) {
if (obj.arguments && !(arg in obj.arguments)) continue
const repl = unwrapMaybe(
byArguments[arg],
Boolean(obj.arguments) && arg in obj.arguments,
byArguments[arg]!,
Boolean(obj.arguments && arg in obj.arguments),
)
if (repl) {
@ -126,7 +125,7 @@ export function applyDescriptionsYamlFile(
if (!rule._cached) {
let flags = rule.flags || ''
if (flags.indexOf('g') === -1) flags += 'g'
if (!flags.includes('g')) flags += 'g'
rule._cached = new RegExp(rule.regex, flags)
}
@ -135,7 +134,7 @@ export function applyDescriptionsYamlFile(
}
for (const i in objIndex) {
const obj = objIndex[i]
const obj = objIndex[i]!
byRegex.forEach((rule) => {
obj.comment = applyRegex(obj.comment, rule)

View file

@ -7,6 +7,7 @@
"./binary/reader.d.ts",
"./binary/writer.d.ts",
"./binary/rsa-keys.d.ts",
"./scripts"
"./scripts",
"./tests/types.ts"
]
}

View file

@ -157,6 +157,9 @@ importers:
'@types/ws':
specifier: 8.5.4
version: 8.5.4
exit-hook:
specifier: ^4.0.0
version: 4.0.0
ws:
specifier: 8.13.0
version: 8.13.0
@ -2442,6 +2445,11 @@ packages:
strip-final-newline: 3.0.0
dev: true
/exit-hook@4.0.0:
resolution: {integrity: sha512-Fqs7ChZm72y40wKjOFXBKg7nJZvQJmewP5/7LtePDdnah/+FH9Hp5sgMujSCMPXlxOAW2//1jrW9pnsY7o20vQ==}
engines: {node: '>=18'}
dev: true
/exit-on-epipe@1.0.1:
resolution: {integrity: sha512-h2z5mrROTxce56S+pnvAV890uu7ls7f1kEvVGJbw1OlFH3/mlJ5bkXu0KRyW94v37zzHPiUd55iLn3DA7TjWpw==}
engines: {node: '>=0.8'}