docs: improve documentation

This commit is contained in:
teidesu 2022-08-29 16:22:57 +03:00
parent e7219ed2de
commit ea299cacca
40 changed files with 459 additions and 54 deletions

View file

@ -1285,7 +1285,7 @@ export interface TelegramClient extends BaseTelegramClient {
* - `recent`: get recent members
* - `admins`: get only administrators (and creator)
* - `contacts`: get only contacts
* - `mention`: get users that can be mentioned ([learn more](https://mt.tei.su/tl/class/channelParticipantsMentions))
* - `mention`: get users that can be mentioned (see {@link tl.RawChannelParticipantsMentions})
*
* Only used for channels and supergroups. Defaults to `recent`
*/

View file

@ -58,7 +58,7 @@ export async function getChatMembers(
* - `recent`: get recent members
* - `admins`: get only administrators (and creator)
* - `contacts`: get only contacts
* - `mention`: get users that can be mentioned ([learn more](https://mt.tei.su/tl/class/channelParticipantsMentions))
* - `mention`: get users that can be mentioned (see {@link tl.RawChannelParticipantsMentions})
*
* Only used for channels and supergroups. Defaults to `recent`
*/

View file

@ -216,7 +216,7 @@ export namespace BotKeyboard {
* @param data Callback data (1-64 bytes). String will be converted to `Buffer`
* @param requiresPassword
* Whether the user should verify their identity by entering 2FA password.
* See more: [tl.RawKeyboardButtonCallback#requiresPassword](../../tl/interfaces/index.tl.rawkeyboardbuttoncallback.html#requirespassword)
* See more: {@link tl.RawKeyboardButtonCallback#requiresPassword}
*/
export function callback(
text: string,
@ -293,7 +293,7 @@ export namespace BotKeyboard {
* Used for inline keyboards, not reply!
*
* @param text Button label
* @param url Authorization URL (see [TL Reference](https://mt.tei.su/tl/class/inputKeyboardButtonUrlAuth))
* @param url Authorization URL (see {@link tl.RawInputKeyboardButtonUrlAuth})
* @param params
*/
export function urlAuth(

View file

@ -1,9 +1,14 @@
import { MtArgumentError } from '../types'
/**
* Given file size, determine the appropriate chunk size (in KB)
* for upload/download operations.
*/
export function determinePartSize(fileSize: number): number {
if (fileSize <= 104857600) return 128 // 100 MB
if (fileSize <= 786432000) return 256 // 750 MB
if (fileSize <= 2097152000) return 512 // 2000 MB
if (fileSize <= 4194304000) return 1024 // 4000 MB
throw new MtArgumentError('File is too large')
}
@ -50,6 +55,9 @@ const JPEG_HEADER = Buffer.from(
)
const JPEG_FOOTER = Buffer.from('ffd9', 'hex')
/**
* Convert stripped JPEG (from `photoStrippedSize`) to full JPEG
*/
export function strippedPhotoToJpg(stripped: Buffer): Buffer {
if (stripped.length < 3 || stripped[0] !== 1) {
return stripped
@ -64,6 +72,10 @@ export function strippedPhotoToJpg(stripped: Buffer): Buffer {
const SVG_LOOKUP =
'AACAAAAHAAALMAAAQASTAVAAAZaacaaaahaaalmaaaqastava.az0123456789-,'
/**
* Inflate compressed preview SVG path to full SVG path
* @param encoded
*/
export function inflateSvgPath(encoded: Buffer): string {
let path = 'M'
const len = encoded.length
@ -87,6 +99,10 @@ export function inflateSvgPath(encoded: Buffer): string {
return path
}
/**
* Convert SVG path to SVG file
* @param path
*/
export function svgPathToFile(path: string): Buffer {
return Buffer.from(
'<?xml version="1.0" encoding="utf-8"?>' +
@ -101,6 +117,11 @@ export function svgPathToFile(path: string): Buffer {
const FILENAME_REGEX = /^(?:file:)?(\/?.+[/\\])*(.+\..+)$/
/**
* Get file name from file path
*
* @param path File path
*/
export function extractFileName(path: string): string {
const m = path.match(FILENAME_REGEX)
if (m) return m[2]

View file

@ -7,6 +7,11 @@ import {
TlBinaryWriter,
} from '@mtcute/core'
/**
* Parse TDLib style inline message ID
*
* @param id Inline message ID
*/
export function parseInlineMessageId(
id: string
): tl.TypeInputBotInlineMessageID {
@ -31,6 +36,11 @@ export function parseInlineMessageId(
}
}
/**
* Generate TDLib style inline message ID
*
* @param id Inline message ID object
*/
export function encodeInlineMessageId(
id: tl.TypeInputBotInlineMessageID
): string {

View file

@ -2,6 +2,10 @@ import { tl } from '@mtcute/tl'
import { MaybeDynamic, Message, MtClientError } from '../types'
/**
* Normalize phone number by stripping formatting
* @param phone Phone number
*/
export function normalizePhoneNumber(phone: string): string {
phone = phone.trim().replace(/[+()\s-]/g, '')
if (!phone.match(/^\d+$/)) throw new MtClientError('Invalid phone number')

View file

@ -1,3 +1,9 @@
/**
* Decode 5-bit encoded voice message waveform into
* an array of waveform values (0-32).
*
* @param wf Encoded waveform
*/
export function decodeWaveform(wf: Buffer): number[] {
const bitsCount = wf.length * 8
const valuesCount = ~~(bitsCount / 5)
@ -33,6 +39,12 @@ export function decodeWaveform(wf: Buffer): number[] {
return result
}
/**
* Encode an array of waveform values into
* 5-bit encoded voice message waveform into
*
* @param wf Waveform values
*/
export function encodeWaveform(wf: number[]): Buffer {
const bitsCount = wf.length * 5
const bytesCount = ~~(bitsCount + 7) / 8

View file

@ -94,7 +94,7 @@ async function rsaEncrypt(
/**
* Execute authorization flow on `connection` using `crypto`.
*
* Returns tuple: [authKey, serverSalt, timeOffset]
* @returns tuple: [authKey, serverSalt, timeOffset]
*/
export async function doAuthorization(
connection: SessionConnection,

View file

@ -113,6 +113,9 @@ function makeNiceStack(
let nextConnectionUid = 0
/**
* A connection to a single DC.
*/
export class SessionConnection extends PersistentConnection {
readonly params!: SessionConnectionParams

View file

@ -11,7 +11,7 @@ const BAD_HEADERS = [
Buffer.from('eeeeeeee', 'hex'),
]
interface MtProxyInfo {
export interface MtProxyInfo {
dcId: number
secret: Buffer
test: boolean

View file

@ -2,6 +2,13 @@ import bigInt, { BigInteger } from 'big-integer'
import { randomBytes } from './buffer-utils'
/**
* Convert a big integer to a buffer
*
* @param value Value to convert
* @param length Length of the resulting buffer (by default it's computed automatically)
* @param le Whether to use little-endian encoding
*/
export function bigIntToBuffer(
value: BigInteger,
length = 0,
@ -23,6 +30,14 @@ export function bigIntToBuffer(
return buffer
}
/**
* Convert a buffer to a big integer
*
* @param buffer Buffer to convert
* @param offset Offset to start reading from
* @param length Length to read
* @param le Whether to use little-endian encoding
*/
export function bufferToBigInt(
buffer: Buffer,
offset = 0,
@ -36,10 +51,20 @@ export function bufferToBigInt(
return bigInt.fromArray(arr, 256)
}
/**
* Generate a random big integer of the given size (in bytes)
* @param size Size in bytes
*/
export function randomBigInt(size: number): BigInteger {
return bufferToBigInt(randomBytes(size))
}
/**
* Generate a random big integer in the range [min, max)
*
* @param max Maximum value (exclusive)
* @param min Minimum value (inclusive)
*/
export function randomBigIntInRange(
max: BigInteger,
min = bigInt.one

View file

@ -1,7 +1,13 @@
export { _randomBytes as randomBytes } from './platform/crypto'
const b64urlAvailable = Buffer.isEncoding('base64url')
// from https://github.com/feross/typedarray-to-buffer
// licensed under MIT
/**
* Convert a typed array to a Buffer.
* @param arr Typed array to convert
*/
export function typedArrayToBuffer(arr: NodeJS.TypedArray): Buffer {
return ArrayBuffer.isView(arr)
? // To avoid a copy, use the typed array's underlying ArrayBuffer to back
@ -11,6 +17,12 @@ export function typedArrayToBuffer(arr: NodeJS.TypedArray): Buffer {
Buffer.from(arr)
}
/**
* Check if two buffers are equal
*
* @param a First buffer
* @param b Second buffer
*/
export function buffersEqual(a: Buffer, b: Buffer): boolean {
if (a.length !== b.length) return false
@ -21,6 +33,12 @@ export function buffersEqual(a: Buffer, b: Buffer): boolean {
return true
}
/**
* Perform XOR operation on two buffers and return the new buffer
*
* @param data Buffer to XOR
* @param key Key to XOR with
*/
export function xorBuffer(data: Buffer, key: Buffer): Buffer {
const ret = Buffer.alloc(data.length)
for (let i = 0; i < data.length; i++) {
@ -29,25 +47,56 @@ export function xorBuffer(data: Buffer, key: Buffer): Buffer {
return ret
}
/**
* Perform XOR operation on two buffers in-place
*
* @param data Buffer to XOR
* @param key Key to XOR with
*/
export function xorBufferInPlace(data: Buffer, key: Buffer): void {
for (let i = 0; i < data.length; i++) {
data[i] ^= key[i]
}
}
/**
* Copy a buffer
*
* @param buf Buffer to copy
* @param start Start offset
* @param end End offset
*/
export function cloneBuffer(buf: Buffer, start = 0, end = buf.length): Buffer {
const ret = Buffer.alloc(end - start)
buf.copy(ret, 0, start, end)
return ret
}
/**
* Parse url-safe base64 string
*
* @param str String to parse
*/
export function parseUrlSafeBase64(str: string): Buffer {
if (b64urlAvailable) {
return Buffer.from(str, 'base64url')
}
str = str.replace(/-/g, '+').replace(/_/g, '/')
while (str.length % 4) str += '='
return Buffer.from(str, 'base64')
}
/**
* Convert a buffer to url-safe base64 string
*
* @param buf Buffer to convert
*/
export function encodeUrlSafeBase64(buf: Buffer): string {
if (b64urlAvailable) {
return buf.toString('base64url')
}
return buf
.toString('base64')
.replace(/\+/g, '-')

View file

@ -1,3 +1,6 @@
/**
* Class implementing a condition variable like behaviour.
*/
export class ConditionVariable {
private _notify?: () => void
private _timeout?: NodeJS.Timeout

View file

@ -1,14 +1,26 @@
/**
* A promise that can be resolved or rejected from outside.
*/
export type ControllablePromise<T = any> = Promise<T> & {
resolve(val: T): void
reject(err?: unknown): void
}
/**
* A promise that can be cancelled.
*/
export type CancellablePromise<T = any> = Promise<T> & {
cancel(): void
}
/**
* The promise was cancelled
*/
export class PromiseCancelledError extends Error {}
/**
* Creates a promise that can be resolved or rejected from outside.
*/
export function createControllablePromise<T = any>(): ControllablePromise<T> {
let _resolve: any
let _reject: any
@ -21,6 +33,11 @@ export function createControllablePromise<T = any>(): ControllablePromise<T> {
return promise as ControllablePromise<T>
}
/**
* Creates a promise that can be cancelled.
*
* @param onCancel Callback to call when cancellation is requested
*/
export function createCancellablePromise<T = any>(
onCancel: () => void
): ControllablePromise<T> & CancellablePromise<T> {

View file

@ -43,7 +43,7 @@ export interface ICryptoProvider {
factorizePQ(pq: Buffer): MaybeAsync<[Buffer, Buffer]>
}
export abstract class BaseCryptoProvider implements ICryptoProvider {
export abstract class BaseCryptoProvider {
createAesIge(key: Buffer, iv: Buffer): IEncryptionScheme {
return new AesModeOfOperationIge(key, iv, this.createAesEcb(key))
}
@ -54,29 +54,7 @@ export abstract class BaseCryptoProvider implements ICryptoProvider {
initialize(): void {}
abstract createAesCtr(
key: Buffer,
iv: Buffer,
encrypt: boolean
): IEncryptionScheme
abstract createAesEcb(key: Buffer): IEncryptionScheme
abstract pbkdf2(
password: Buffer,
salt: Buffer,
iterations: number,
keylen?: number, // = 64
algo?: string // sha1 or sha512 (default sha512)
): MaybeAsync<Buffer>
abstract sha1(data: Buffer): MaybeAsync<Buffer>
abstract sha256(data: Buffer): MaybeAsync<Buffer>
abstract hmacSha256(data: Buffer, key: Buffer): MaybeAsync<Buffer>
abstract createMd5(): IHashMethod
}
export type CryptoProviderFactory = () => ICryptoProvider

View file

@ -1,6 +1,9 @@
import { xorBufferInPlace } from '../buffer-utils'
import { IEncryptionScheme } from './abstract'
/**
* AES mode of operation IGE implementation in JS
*/
export class AesModeOfOperationIge implements IEncryptionScheme {
private _key: Buffer
private _iv: Buffer

View file

@ -6,6 +6,10 @@ import {
randomBigIntInRange,
} from '../bigint-utils'
/**
* Factorize `p*q` to `p` and `q` synchronously using Brent-Pollard rho algorithm
* @param pq
*/
export function factorizePQSync(pq: Buffer): [Buffer, Buffer] {
const pq_ = bufferToBigInt(pq)

View file

@ -1,12 +1,20 @@
import { MaybeAsync } from '../../types'
import { BaseCryptoProvider, IEncryptionScheme, IHashMethod } from './abstract'
import {
BaseCryptoProvider,
ICryptoProvider,
IEncryptionScheme,
IHashMethod,
} from './abstract'
let forge: any = null
try {
forge = require('node-forge')
} catch (e) {}
export class ForgeCryptoProvider extends BaseCryptoProvider {
export class ForgeCryptoProvider
extends BaseCryptoProvider
implements ICryptoProvider
{
constructor() {
super()
if (!forge)

View file

@ -5,6 +5,14 @@ import keysIndex, { TlPublicKey } from '@mtcute/tl/binary/rsa-keys'
import { parseAsn1, parsePemContents } from '../binary/asn1-parser'
import { ICryptoProvider } from './abstract'
/**
* Parse PEM-encoded RSA public key information into modulus and exponent
* and compute its fingerprint as defined by MTProto.
*
* @param crypto Crypto provider
* @param key PEM-encoded RSA public key
* @param old Whether this is an "old" key
*/
export async function parsePublicKey(
crypto: ICryptoProvider,
key: string,
@ -32,6 +40,13 @@ export async function parsePublicKey(
}
}
/**
* Add public key to the global index.
*
* @param crypto Crypto provider
* @param key PEM-encoded RSA public key
* @param old Whether this is an "old" key
*/
export async function addPublicKey(
crypto: ICryptoProvider,
key: string,
@ -41,6 +56,12 @@ export async function addPublicKey(
keysIndex[parsed.fingerprint] = parsed
}
/**
* Get public key by its fingerprint.
*
* @param fingerprints Fingerprints to match. The first one to match is returned.
* @param allowOld Whether to allow "old" keys
*/
export function findKeyByFingerprints(
fingerprints: (string | Long)[],
allowOld = false

View file

@ -1,6 +1,14 @@
import { IEncryptionScheme, ICryptoProvider } from './abstract'
// returns tuple: [key, iv]
/**
* Generate AES key and IV from nonces as defined by MTProto.
* Used in authorization flow.
*
* @param crypto Crypto provider
* @param serverNonce Server nonce
* @param newNonce New nonce
* @returns Tuple: `[key, iv]`
*/
export async function generateKeyAndIvFromNonce(
crypto: ICryptoProvider,
serverNonce: Buffer,
@ -16,6 +24,15 @@ export async function generateKeyAndIvFromNonce(
return [key, iv]
}
/**
* Create AES IGE instance for message (given auth key and message key)
* as defined by MTProto v2.
*
* @param crypto Crypto provider
* @param authKey Authorization key
* @param messageKey Message key
* @param client Whether this is a client to server message
*/
export async function createAesIgeForMessage(
crypto: ICryptoProvider,
authKey: Buffer,
@ -44,6 +61,15 @@ export async function createAesIgeForMessage(
return crypto.createAesIge(key, iv)
}
/**
* Create AES IGE instance for file (given auth key and message key)
* as defined by MTProto v1.
*
* @param crypto Crypto provider
* @param authKey Authorization key
* @param messageKey Message key
* @param client Whether this is a client to server message
*/
export async function createAesIgeForMessageOld(
crypto: ICryptoProvider,
authKey: Buffer,

View file

@ -7,9 +7,17 @@ import {
} from 'crypto'
import { MaybeAsync } from '../../types'
import { BaseCryptoProvider, IEncryptionScheme, IHashMethod } from './abstract'
import {
BaseCryptoProvider,
ICryptoProvider,
IEncryptionScheme,
IHashMethod,
} from './abstract'
export class NodeCryptoProvider extends BaseCryptoProvider {
export class NodeCryptoProvider
extends BaseCryptoProvider
implements ICryptoProvider
{
constructor() {
super()
}

View file

@ -5,13 +5,22 @@ import { randomBytes, xorBuffer } from '../buffer-utils'
import { bigIntToBuffer, bufferToBigInt } from '../bigint-utils'
import { ICryptoProvider } from './abstract'
/**
* Compute password hash as defined by MTProto.
*
* See https://core.telegram.org/api/srp#checking-the-password-with-srp
*
* @param crypto Crypto provider
* @param password Password
* @param salt1 Salt 1
* @param salt2 Salt 2
*/
export async function computePasswordHash(
crypto: ICryptoProvider,
password: Buffer,
salt1: Buffer,
salt2: Buffer
): Promise<Buffer> {
// https://core.telegram.org/api/srp#checking-the-password-with-srp
const SH = (data: Buffer, salt: Buffer) =>
crypto.sha256(Buffer.concat([salt, data, salt]))
const PH1 = async (pwd: Buffer, salt1: Buffer, salt2: Buffer) =>
@ -25,6 +34,13 @@ export async function computePasswordHash(
return PH2(password, salt1, salt2)
}
/**
* Compute new password SRP hash as defined by MTProto.
*
* @param crypto Crypto provider
* @param algo KDF algorithm
* @param password Password
*/
export async function computeNewPasswordHash(
crypto: ICryptoProvider,
algo: tl.RawPasswordKdfAlgoSHA256SHA256PBKDF2HMACSHA512iter100000SHA256ModPow,
@ -49,6 +65,13 @@ export async function computeNewPasswordHash(
return bigIntToBuffer(g.modPow(x, p), 256)
}
/**
* Compute SRP check parameters for 2fa password as defined by MTProto.
*
* @param crypto Crypto provider
* @param request SRP request
* @param password 2fa password
*/
export async function computeSrpParams(
crypto: ICryptoProvider,
request: tl.account.RawPassword,

View file

@ -1,5 +1,6 @@
import { tl } from '@mtcute/tl'
/** @internal */
export const defaultProductionDc: tl.RawDcOption = {
_: 'dcOption',
ipAddress: '149.154.167.50',
@ -7,6 +8,7 @@ export const defaultProductionDc: tl.RawDcOption = {
id: 2,
}
/** @internal */
export const defaultProductionIpv6Dc: tl.RawDcOption = {
_: 'dcOption',
ipAddress: '2001:67c:4e8:f002::a',
@ -15,6 +17,7 @@ export const defaultProductionIpv6Dc: tl.RawDcOption = {
id: 2,
}
/** @internal */
export const defaultTestDc: tl.RawDcOption = {
_: 'dcOption',
ipAddress: '149.154.167.40',
@ -22,6 +25,7 @@ export const defaultTestDc: tl.RawDcOption = {
id: 2,
}
/** @internal */
export const defaultTestIpv6Dc: tl.RawDcOption = {
_: 'dcOption',
ipAddress: '2001:67c:4e8:f002::e',

View file

@ -6,6 +6,9 @@ interface FloodControlLimit {
pos: number
}
/**
* Flood limiter, based on TDlib
*/
export class FloodControl {
private _wakeupAt = 1
private _withoutUpdate = 0

View file

@ -4,6 +4,9 @@ interface LinkedListItem<T> {
p?: LinkedListItem<T>
}
/**
* A sorted linked list.
*/
export class SortedLinkedList<T> {
_first?: LinkedListItem<T>
_last?: LinkedListItem<T>

View file

@ -1,6 +1,11 @@
import Long from 'long'
import { getRandomInt } from './misc-utils'
/**
* Get a random Long
*
* @param unsigned Whether the number should be unsigned
*/
export function randomLong(unsigned = false): Long {
const lo = getRandomInt(0xffffffff)
const hi = getRandomInt(0xffffffff)
@ -8,6 +13,12 @@ export function randomLong(unsigned = false): Long {
return new Long(lo, hi, unsigned)
}
/**
* Remove a Long from an array
*
* @param arr Array to remove from
* @param val Value to remove
*/
export function removeFromLongArray(arr: Long[], val: Long): boolean {
for (let i = 0; i < arr.length; i++) {
const v = arr[i]

View file

@ -9,11 +9,9 @@ interface OneWayLinkedList<T> {
* Simple class implementing LRU-like behaviour for a set,
* falling back to objects when `Set` is not available.
*
* Used to store recently received message IDs in {@link TelegramConnection}
* Used to store recently received message IDs in {@link SessionConnection}
*
* Uses one-way linked list internally to keep track of insertion order
*
* @internal
*/
export class LruSet<T> {
private _capacity: number

View file

@ -1,3 +1,8 @@
/**
* Sleep for the given number of ms
*
* @param ms Number of ms to sleep
*/
export const sleep = (ms: number): Promise<void> =>
new Promise((resolve) => setTimeout(resolve, ms))

View file

@ -17,6 +17,9 @@ const MAX_USER_ID = 1099511627775
// const MAX_CHANNEL_ID = 997852516352
const MIN_MARKED_CHANNEL_ID = -1997852516352 // ZERO_CHANNEL_ID - MAX_CHANNEL_ID
/**
* Add or remove channel marker from ID
*/
export function toggleChannelIdMark(id: number): number {
return ZERO_CHANNEL_ID - id
}
@ -46,9 +49,9 @@ export function getBarePeerId(peer: tl.TypePeer): number {
* - ID is negated and `-1e12` is subtracted for channels
*/
export function getMarkedPeerId(peerId: number, peerType: BasicPeerType): number
export function getMarkedPeerId(peer: tl.TypePeer | tl.TypeInputPeer): number
export function getMarkedPeerId(peer: tl.TypePeer | tl.TypeInputPeer | tl.TypeInputUser | tl.TypeInputChannel): number
export function getMarkedPeerId(
peer: tl.TypePeer | tl.TypeInputPeer | number,
peer: tl.TypePeer | tl.TypeInputPeer | tl.TypeInputUser | tl.TypeInputChannel | number,
peerType?: BasicPeerType
): number {
if (typeof peer === 'number') {
@ -66,12 +69,14 @@ export function getMarkedPeerId(
switch (peer._) {
case 'peerUser':
case 'inputPeerUser':
case 'inputUser':
return peer.userId
case 'peerChat':
case 'inputPeerChat':
return -peer.chatId
case 'peerChannel':
case 'inputPeerChannel':
case 'inputChannel':
return ZERO_CHANNEL_ID - peer.channelId
}
@ -112,6 +117,11 @@ export function getBasicPeerType(peer: tl.TypePeer | number): BasicPeerType {
throw new Error(`Invalid marked peer id: ${peer}`)
}
/**
* Extract bare peer ID from marked ID.
*
* @param peerId Marked peer ID
*/
export function markedPeerIdToBare(peerId: number): number {
const type = getBasicPeerType(peerId)

View file

@ -1,13 +1,15 @@
type Comparator<T> = (a: T, b: T) => number
// Comparator is always called like:
// comparator(itemFromSortedArray, yourItem)
//
//
/**
* Array that adds elements in sorted order.
*
* Comparator is always called like: comparator(itemFromSortedArray, yourItem)
*/
export class SortedArray<T> {
readonly raw: T[]
comparator: Comparator<T>
constructor(array: T[] = [], comparator: Comparator<T>) {
constructor(array: T[] = [], readonly comparator: (a: T, b: T) => number) {
this.raw = array.sort(comparator)
this.comparator = comparator
}

View file

@ -8,7 +8,7 @@ import { UpdateFilter } from './filters'
*
* This can be used to simplify management of different callbacks.
*
* [Learn more in the docs](//mt.tei.su/guide/topics/keyboards.html#callback-data-builders)
* [Learn more in the docs](/guide/topics/keyboards.html#callback-data-builders)
*/
export class CallbackDataBuilder<T extends string> {
private readonly _fields: T[]

View file

@ -21,6 +21,9 @@ export class HttpProxyConnectionError extends Error {
}
}
/**
* HTTP(s) proxy settings
*/
export interface HttpProxySettings {
/**
* Host or IP of the proxy (e.g. `proxy.example.com`, `1.2.3.4`)
@ -171,6 +174,12 @@ export abstract class BaseHttpProxyTcpTransport extends BaseTcpTransport {
}
}
/**
* HTTP(s) TCP transport using an intermediate packet codec.
*
* Should be the one passed as `transport` to {@link TelegramClient} constructor
* (unless you want to use a custom codec).
*/
export class HttpProxyTcpTransport extends BaseHttpProxyTcpTransport {
_packetCodec = new IntermediatePacketCodec()
}

View file

@ -1,6 +1,5 @@
import Long from 'long'
import type { IMessageEntityParser, MessageEntity, tl } from '@mtcute/client'
import { FormattedString } from '@mtcute/client'
import type { IMessageEntityParser, MessageEntity, tl, FormattedString } from '@mtcute/client'
const MENTION_REGEX =
/^tg:\/\/user\?id=(\d+)(?:&hash=(-?[0-9a-fA-F]+)(?:&|$)|&|$)/
@ -60,7 +59,6 @@ export class MarkdownMessageEntityParser implements IMessageEntityParser {
name = 'markdown'
/**
* Escape the text so it can be safely used inside Markdown code.
*
* @param str String to be escaped
*/
@ -399,6 +397,10 @@ export class MarkdownMessageEntityParser implements IMessageEntityParser {
startTag = '['
endTag = `](tg://user?id=${entity.userId!})`
break
case 'emoji':
startTag = '['
endTag = `](tg://emoji?id=${entity.emojiId!})`
break
default:
continue
}

View file

@ -12,6 +12,9 @@ import {
import { FakeTlsPacketCodec, generateFakeTlsHeader } from './fake-tls'
/**
* MTProto proxy settings
*/
export interface MtProxySettings {
/**
* Host or IP of the proxy (e.g. `proxy.example.com`, `1.2.3.4`)

View file

@ -18,6 +18,9 @@ try {
} catch (e) {}
export namespace NodeTelegramClient {
/**
* Options for {@link NodeTelegramClient}
*/
export interface Options
extends Omit<BaseTelegramClient.Options, 'storage'> {
/**
@ -44,10 +47,14 @@ export namespace NodeTelegramClient {
}
/**
* Tiny wrapper over `TelegramClient` for usage inside Node JS.
* Tiny wrapper over {@link TelegramClient} for usage inside Node JS.
*
* This automatically sets the parse modes, native
* crypto addon and defaults to SQLite session.
*
* Documentation for this class only contains the
* difference between {@link TelegramClient} and {@link NodeTelegramClient}.
* For the complete documentation, please refer to {@link TelegramClient}.
*/
export class NodeTelegramClient extends TelegramClient {
constructor(opts: NodeTelegramClient.Options) {
@ -75,7 +82,6 @@ export class NodeTelegramClient extends TelegramClient {
* Associated `readline` interface is closed
* after `run()` returns, or with the client.
*
*
* @param text Text of the question
*/
input(text: string): Promise<string> {

View file

@ -25,6 +25,9 @@ export class SocksProxyConnectionError extends Error {
}
}
/**
* Settings for a SOCKS4/5 proxy
*/
export interface SocksProxySettings {
/**
* Host or IP of the proxy (e.g. `proxy.example.com`, `1.2.3.4`)
@ -49,7 +52,7 @@ export interface SocksProxySettings {
/**
* Version of the SOCKS proxy (4 or 5)
*
* Defaults to `5`.
* @default `5`
*/
version?: 4 | 5
}
@ -475,6 +478,12 @@ export abstract class BaseSocksTcpTransport extends BaseTcpTransport {
}
}
/**
* Socks TCP transport using an intermediate packet codec.
*
* Should be the one passed as `transport` to {@link TelegramClient} constructor
* (unless you want to use a custom codec).
*/
export class SocksTcpTransport extends BaseSocksTcpTransport {
_packetCodec = new IntermediatePacketCodec()
}

View file

@ -1,6 +1,6 @@
# @mtcute/sqlite
SQLite backed storage for mtcute.
SQLite backed storage for mtcute, based on `better-sqlite3`
## Usage

View file

@ -1,9 +1,21 @@
import { deflateSync, gunzipSync } from 'zlib'
/**
* Decompress a buffer with gzip.
* @param buf Buffer to decompress
*/
export function gzipInflate(buf: Buffer): Buffer {
return gunzipSync(buf)
}
/**
* Compress a buffer with gzip.
*
* @param buf Buffer to compress
* @param maxRatio
* Maximum compression ratio. If the resulting buffer is smaller than
* `buf.length * ratio`, `null` is returned.
*/
export function gzipDeflate(buf: Buffer, maxRatio?: number): Buffer | null {
if (maxRatio) {
try {

View file

@ -18,10 +18,25 @@ const TWO_PWR_32_DBL = (1 << 16) * (1 << 16)
*/
export type TlReaderMap = Record<number, (r: any) => any>
/**
* Reader for TL objects.
*/
export class TlBinaryReader {
/**
* Underlying buffer.
*/
data: Buffer
/**
* Position in the buffer.
*/
pos = 0
/**
* @param objectsMap Readers map
* @param data Buffer to read from
* @param start Position to start reading from
*/
constructor(
readonly objectsMap: TlReaderMap | undefined,
data: Buffer,
@ -31,10 +46,23 @@ export class TlBinaryReader {
this.pos = start
}
/**
* Create a new reader without objects map for manual usage
*
* @param data Buffer to read from
* @param start Position to start reading from
*/
static manual(data: Buffer, start = 0): TlBinaryReader {
return new TlBinaryReader(undefined, data, start)
}
/**
* Deserialize a single object
*
* @param objectsMap Readers map
* @param data Buffer to read from
* @param start Position to start reading from
*/
static deserializeObject(
objectsMap: TlReaderMap,
data: Buffer,
@ -55,6 +83,9 @@ export class TlBinaryReader {
return res
}
/**
* Get the next {@link uint} without advancing the reader cursor
*/
peekUint(): number {
// e.g. for checking ctor number
return this.data.readUInt32LE(this.pos)
@ -98,6 +129,10 @@ export class TlBinaryReader {
)
}
/**
* Read raw bytes of the given length
* @param bytes Length of the buffer to read
*/
raw(bytes = -1): Buffer {
if (bytes === -1) bytes = this.data.length - this.pos
@ -188,10 +223,20 @@ export class TlBinaryReader {
return ret
}
/**
* Advance the reader cursor by the given amount of bytes
*
* @param delta Amount of bytes to advance (can be negative)
*/
seek(delta: number): void {
this.seekTo(this.pos + delta)
}
/**
* Seek to the given position
*
* @param pos Position to seek to
*/
seekTo(pos: number): void {
if (pos >= this.data.length || pos < 0)
throw new RangeError('New position is out of range')

View file

@ -7,11 +7,26 @@ const TWO_PWR_32_DBL = (1 << 16) * (1 << 16)
*/
export type TlWriterMap = Record<string, (w: any, val: any) => void>
/**
* Counter of the required number of bytes to encode a given object.
*
* Used as a pre-pass before using {@link TlBinaryWriter}
* to avoid unnecessary allocations.
*/
export class TlSerializationCounter {
count = 0
/**
* @param objectMap Writers map
*/
constructor(readonly objectMap: TlWriterMap) {}
/**
* Count bytes required to serialize the given object.
*
* @param objectMap Writers map
* @param obj Object to count bytes for
*/
static countNeededBytes(
objectMap: TlWriterMap,
obj: { _: string }
@ -21,6 +36,12 @@ export class TlSerializationCounter {
return cnt.count
}
/**
* Count overhead in bytes for the given number of bytes when
* encoded as `bytes` TL type.
*
* @param size Number of bytes
*/
static countBytesOverhead(size: number): number {
let res = 0
@ -103,10 +124,25 @@ export class TlSerializationCounter {
}
}
/**
* Writer for TL objects.
*/
export class TlBinaryWriter {
/**
* Underlying buffer.
*/
buffer: Buffer
/**
* Current position in the buffer.
*/
pos: number
/**
* @param objectMap Writers map
* @param buffer Buffer to write to
* @param start Position to start writing at
*/
constructor(
readonly objectMap: TlWriterMap | undefined,
buffer: Buffer,
@ -116,18 +152,43 @@ export class TlBinaryWriter {
this.pos = start
}
/**
* Create a new writer with the given size.
*
* @param objectMap Writers map
* @param size Size of the writer's buffer
*/
static alloc(objectMap: TlWriterMap, size: number): TlBinaryWriter {
return new TlBinaryWriter(objectMap, Buffer.allocUnsafe(size))
}
/**
* Create a new writer without objects map for manual usage
*
* @param buffer Buffer to write to
* @param start Position to start writing at
*/
static manual(buffer: Buffer, start = 0): TlBinaryWriter {
return new TlBinaryWriter(undefined, buffer, start)
}
/**
* Create a new writer without objects map for manual usage
* with a given size
*
* @param size Size of the writer's buffer
*/
static manualAlloc(size: number): TlBinaryWriter {
return new TlBinaryWriter(undefined, Buffer.allocUnsafe(size))
}
/**
* Serialize a single object
*
* @param objectMap Writers map
* @param obj Object to serialize
* @param knownSize In case the size is known, pass it here
*/
static serializeObject(
objectMap: TlWriterMap,
obj: { _: string },
@ -193,6 +254,10 @@ export class TlBinaryWriter {
this.pos += 4
}
/**
* Write raw bytes to the buffer
* @param val Buffer to write
*/
raw(val: Buffer): void {
val.copy(this.buffer, this.pos)
this.pos += val.length
@ -249,6 +314,9 @@ export class TlBinaryWriter {
val.forEach((it) => fn.call(this, it, bare))
}
/**
* Get the resulting buffer
*/
result(): Buffer {
return this.buffer.slice(0, this.pos)
}