docs: improve documentation
This commit is contained in:
parent
e7219ed2de
commit
ea299cacca
40 changed files with 459 additions and 54 deletions
|
@ -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`
|
||||
*/
|
||||
|
|
|
@ -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`
|
||||
*/
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -113,6 +113,9 @@ function makeNiceStack(
|
|||
|
||||
let nextConnectionUid = 0
|
||||
|
||||
/**
|
||||
* A connection to a single DC.
|
||||
*/
|
||||
export class SessionConnection extends PersistentConnection {
|
||||
readonly params!: SessionConnectionParams
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ const BAD_HEADERS = [
|
|||
Buffer.from('eeeeeeee', 'hex'),
|
||||
]
|
||||
|
||||
interface MtProxyInfo {
|
||||
export interface MtProxyInfo {
|
||||
dcId: number
|
||||
secret: Buffer
|
||||
test: boolean
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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, '-')
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
/**
|
||||
* Class implementing a condition variable like behaviour.
|
||||
*/
|
||||
export class ConditionVariable {
|
||||
private _notify?: () => void
|
||||
private _timeout?: NodeJS.Timeout
|
||||
|
|
|
@ -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> {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -6,6 +6,9 @@ interface FloodControlLimit {
|
|||
pos: number
|
||||
}
|
||||
|
||||
/**
|
||||
* Flood limiter, based on TDlib
|
||||
*/
|
||||
export class FloodControl {
|
||||
private _wakeupAt = 1
|
||||
private _withoutUpdate = 0
|
||||
|
|
|
@ -4,6 +4,9 @@ interface LinkedListItem<T> {
|
|||
p?: LinkedListItem<T>
|
||||
}
|
||||
|
||||
/**
|
||||
* A sorted linked list.
|
||||
*/
|
||||
export class SortedLinkedList<T> {
|
||||
_first?: LinkedListItem<T>
|
||||
_last?: LinkedListItem<T>
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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))
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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[]
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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`)
|
||||
|
|
|
@ -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> {
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# @mtcute/sqlite
|
||||
|
||||
SQLite backed storage for mtcute.
|
||||
SQLite backed storage for mtcute, based on `better-sqlite3`
|
||||
|
||||
## Usage
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue