chore: enabled isolatedDeclarations

This commit is contained in:
alina 🌸 2024-08-18 09:31:23 +03:00
parent a0ed9c2426
commit b76463ccc0
Signed by: teidesu
SSH key fingerprint: SHA256:uNeCpw6aTSU4aIObXLvHfLkDa82HWH9EiOj9AXOIRpI
126 changed files with 676 additions and 490 deletions

View file

@ -28,12 +28,10 @@ describe('@mtcute/tl', () => {
it('readers map works with TlBinaryReader', () => { it('readers map works with TlBinaryReader', () => {
const buf = p.hexDecode('4ca5e8dd7b00000000000000c801000000000000') const buf = p.hexDecode('4ca5e8dd7b00000000000000c801000000000000')
// eslint-disable-next-line
const obj = TlBinaryReader.deserializeObject<any>(__tlReaderMap, buf) const obj = TlBinaryReader.deserializeObject<any>(__tlReaderMap, buf)
expect(obj._).equal('inputPeerUser') expect(obj._).equal('inputPeerUser')
expect(obj.userId).equal(123) expect(obj.userId).equal(123)
// eslint-disable-next-line
expect(obj.accessHash.toString()).equal('456') expect(obj.accessHash.toString()).equal('456')
}) })

View file

@ -44,6 +44,12 @@ export default antfu({
SwitchCase: 1, SwitchCase: 1,
VariableDeclarator: 1, VariableDeclarator: 1,
}], }],
'style/max-len': ['error', {
code: 120,
ignoreComments: true,
ignoreStrings: true,
ignoreTemplateLiterals: true,
}],
'curly': ['error', 'multi-line'], 'curly': ['error', 'multi-line'],
'style/brace-style': ['error', '1tbs', { allowSingleLine: true }], 'style/brace-style': ['error', '1tbs', { allowSingleLine: true }],
'node/prefer-global/process': ['error', 'always'], 'node/prefer-global/process': ['error', 'always'],
@ -60,7 +66,7 @@ export default antfu({
'ts/no-redeclare': 'off', 'ts/no-redeclare': 'off',
'eslint-comments/no-unlimited-disable': 'off', 'eslint-comments/no-unlimited-disable': 'off',
'no-cond-assign': 'off', 'no-cond-assign': 'off',
'ts/explicit-function-return-type': 'off', // todo: enable once we move to isolatedDeclarations 'ts/explicit-function-return-type': 'off',
'no-labels': 'off', 'no-labels': 'off',
'no-restricted-syntax': 'off', 'no-restricted-syntax': 'off',
'unicorn/no-new-array': 'off', 'unicorn/no-new-array': 'off',

View file

@ -1,5 +1,6 @@
import type { Interface as RlInterface } from 'node:readline' import type { Interface as RlInterface } from 'node:readline'
import { createInterface } from 'node:readline' import { createInterface } from 'node:readline'
import type { Readable } from 'node:stream'
import type { FileDownloadLocation, FileDownloadParameters, ITelegramStorageProvider, PartialOnly, User } from '@mtcute/core' import type { FileDownloadLocation, FileDownloadParameters, ITelegramStorageProvider, PartialOnly, User } from '@mtcute/core'
import type { import type {
@ -145,7 +146,7 @@ export class TelegramClient extends TelegramClientBase {
return downloadToFile(this, filename, location, params) return downloadToFile(this, filename, location, params)
} }
downloadAsNodeStream(location: FileDownloadLocation, params?: FileDownloadParameters | undefined) { downloadAsNodeStream(location: FileDownloadLocation, params?: FileDownloadParameters | undefined): Readable {
return downloadAsNodeStream(this, location, params) return downloadAsNodeStream(this, location, params)
} }
} }

View file

@ -19,7 +19,7 @@ export interface SqliteStorageDriverOptions {
export class SqliteStorageDriver extends BaseSqliteStorageDriver { export class SqliteStorageDriver extends BaseSqliteStorageDriver {
constructor( constructor(
readonly filename = ':memory:', readonly filename = ':memory:',
readonly params?: SqliteStorageDriverOptions, readonly params?: SqliteStorageDriverOptions | undefined,
) { ) {
super() super()
} }

View file

@ -8,7 +8,7 @@ export { SqliteStorageDriver } from './driver.js'
export class SqliteStorage extends BaseSqliteStorage { export class SqliteStorage extends BaseSqliteStorage {
constructor( constructor(
readonly filename = ':memory:', readonly filename = ':memory:',
readonly params?: SqliteStorageDriverOptions, readonly params?: SqliteStorageDriverOptions | undefined,
) { ) {
super(new SqliteStorageDriver(filename, params)) super(new SqliteStorageDriver(filename, params))
} }

View file

@ -112,7 +112,7 @@ export class BunCryptoProvider extends BaseCryptoProvider implements ICryptoProv
return gunzip(data) return gunzip(data)
} }
randomFill(buf: Uint8Array) { randomFill(buf: Uint8Array): void {
crypto.getRandomValues(buf) crypto.getRandomValues(buf)
} }
} }

View file

@ -11,7 +11,11 @@ function isBunFile(file: unknown): file is BunFile {
return file instanceof Blob && 'name' in file && file.name.length > 0 return file instanceof Blob && 'name' in file && file.name.length > 0
} }
export async function normalizeFile(file: UploadFileLike) { export async function normalizeFile(file: UploadFileLike): Promise<{
file: UploadFileLike
fileName?: string | undefined
fileSize?: number
} | null> {
if (typeof file === 'string') { if (typeof file === 'string') {
file = Bun.file(file) file = Bun.file(file)
} }

View file

@ -149,5 +149,5 @@ export abstract class BaseTcpTransport extends EventEmitter implements ITelegram
} }
export class TcpTransport extends BaseTcpTransport { export class TcpTransport extends BaseTcpTransport {
_packetCodec = new IntermediatePacketCodec() _packetCodec: IntermediatePacketCodec = new IntermediatePacketCodec()
} }

View file

@ -20,7 +20,7 @@ export function parseIpFromBytes(data: Uint8Array): string {
throw new MtArgumentError('Invalid IP address length') throw new MtArgumentError('Invalid IP address length')
} }
export function serializeIpv4ToBytes(ip: string, buf: Uint8Array) { export function serializeIpv4ToBytes(ip: string, buf: Uint8Array): void {
const parts = ip.split('.') const parts = ip.split('.')
if (parts.length !== 4) { if (parts.length !== 4) {
@ -33,7 +33,7 @@ export function serializeIpv4ToBytes(ip: string, buf: Uint8Array) {
buf[3] = Number(parts[3]) buf[3] = Number(parts[3])
} }
export function serializeIpv6ToBytes(ip: string, buf: Uint8Array) { export function serializeIpv6ToBytes(ip: string, buf: Uint8Array): void {
const parts = ip.split(':') const parts = ip.split(':')
if (parts.length !== 8) { if (parts.length !== 8) {

View file

@ -94,7 +94,7 @@ export class BaseTelegramClient implements ITelegramClient {
}) })
} }
readonly appConfig = new AppConfigManager(this) readonly appConfig: AppConfigManager = new AppConfigManager(this)
private _prepare = asyncResettable(async () => { private _prepare = asyncResettable(async () => {
await this.mt.prepare() await this.mt.prepare()
@ -115,7 +115,7 @@ export class BaseTelegramClient implements ITelegramClient {
* *
* Call {@link connect} to actually connect. * Call {@link connect} to actually connect.
*/ */
prepare() { prepare(): Promise<void> {
return this._prepare.run() return this._prepare.run()
} }
@ -327,7 +327,10 @@ export class BaseTelegramClient implements ITelegramClient {
this._connectionStateHandler = handler this._connectionStateHandler = handler
} }
async getApiCrenetials() { async getApiCrenetials(): Promise<{
id: number
hash: string
}> {
return { return {
id: this.params.apiId, id: this.params.apiId,
hash: this.params.apiHash, hash: this.params.apiHash,

View file

@ -519,7 +519,6 @@ export interface TelegramClient extends ITelegramClient {
*/ */
on(name: 'delete_business_message', handler: ((upd: DeleteBusinessMessageUpdate) => void)): this on(name: 'delete_business_message', handler: ((upd: DeleteBusinessMessageUpdate) => void)): this
// eslint-disable-next-line ts/no-explicit-any
on(name: string, handler: (...args: any[]) => void): this on(name: string, handler: (...args: any[]) => void): this
/** /**

View file

@ -9,10 +9,11 @@ import {
toInputChannel, toInputChannel,
toInputUser, toInputUser,
} from '../../utils/peer-utils.js' } from '../../utils/peer-utils.js'
import type { BatchedQuery } from '../../utils/query-batcher.js'
import { batchedQuery } from '../../utils/query-batcher.js' import { batchedQuery } from '../../utils/query-batcher.js'
/** @internal */ /** @internal */
export const _getUsersBatched = batchedQuery<tl.TypeInputUser, tl.TypeUser, number>({ export const _getUsersBatched: BatchedQuery<tl.TypeInputUser, tl.TypeUser> = batchedQuery({
fetch: (client, items) => fetch: (client, items) =>
client client
.call({ .call({
@ -63,7 +64,7 @@ export const _getUsersBatched = batchedQuery<tl.TypeInputUser, tl.TypeUser, numb
}) })
/** @internal */ /** @internal */
export const _getChatsBatched = batchedQuery<number, tl.RawChat, number>({ export const _getChatsBatched: BatchedQuery<number, tl.RawChat> = batchedQuery({
fetch: (client, items) => fetch: (client, items) =>
client client
.call({ .call({
@ -78,7 +79,10 @@ export const _getChatsBatched = batchedQuery<number, tl.RawChat, number>({
}) })
/** @internal */ /** @internal */
export const _getChannelsBatched = batchedQuery<tl.TypeInputChannel, tl.RawChannel | tl.RawChannelForbidden, number>({ export const _getChannelsBatched: BatchedQuery<
tl.TypeInputChannel,
tl.RawChannel | tl.RawChannelForbidden
> = batchedQuery({
fetch: (client, items) => fetch: (client, items) =>
client client
.call({ .call({

View file

@ -156,7 +156,13 @@ export async function _processCommonSendParameters(
client: ITelegramClient, client: ITelegramClient,
chatId: InputPeerLike, chatId: InputPeerLike,
params: CommonSendParams, params: CommonSendParams,
) { ): Promise<{
peer: tl.TypeInputPeer
replyTo: tl.TypeInputReplyTo | undefined
scheduleDate: number | undefined
quickReplyShortcut: tl.TypeInputQuickReplyShortcut | undefined
chainId: string
}> {
let peer = await resolvePeer(client, chatId) let peer = await resolvePeer(client, chatId)
let replyTo = normalizeMessageId(params.replyTo) let replyTo = normalizeMessageId(params.replyTo)

View file

@ -62,7 +62,7 @@ export class PeersService extends BaseService {
this._cache = new LruMap(options.cacheSize ?? 100) this._cache = new LruMap(options.cacheSize ?? 100)
} }
async updatePeersFrom(obj: tl.TlObject | tl.TlObject[]) { async updatePeersFrom(obj: tl.TlObject | tl.TlObject[]): Promise<boolean> {
let count = 0 let count = 0
for (const peer of getAllPeersFrom(obj)) { for (const peer of getAllPeersFrom(obj)) {

View file

@ -23,9 +23,9 @@ export interface TelegramStorageManagerExtraOptions {
export class TelegramStorageManager { export class TelegramStorageManager {
private provider private provider
readonly updates readonly updates: UpdatesStateService
readonly self: PublicPart<CurrentUserService> readonly self: PublicPart<CurrentUserService>
readonly refMsgs readonly refMsgs: RefMessagesService
readonly peers: PublicPart<PeersService> readonly peers: PublicPart<PeersService>
constructor( constructor(
@ -56,7 +56,7 @@ export class TelegramStorageManager {
) )
} }
async clear(withAuthKeys = false) { async clear(withAuthKeys = false): Promise<void> {
await this.provider.peers.deleteAll() await this.provider.peers.deleteAll()
await this.provider.refMessages.deleteAll() await this.provider.refMessages.deleteAll()
await this.mt.clear(withAuthKeys) await this.mt.clear(withAuthKeys)

View file

@ -9,7 +9,7 @@ import { Voice } from './voice.js'
export type ParsedDocument = Sticker | Voice | Audio | Video | Document export type ParsedDocument = Sticker | Voice | Audio | Video | Document
/** @internal */ /** @internal */
export function parseSticker(doc: tl.RawDocument) { export function parseSticker(doc: tl.RawDocument): Sticker | undefined {
const stickerAttr = doc.attributes.find( const stickerAttr = doc.attributes.find(
a => a._ === 'documentAttributeSticker' || a._ === 'documentAttributeCustomEmoji', a => a._ === 'documentAttributeSticker' || a._ === 'documentAttributeCustomEmoji',
) )

View file

@ -24,7 +24,7 @@ export class Invoice {
constructor( constructor(
readonly raw: tl.RawMessageMediaInvoice, readonly raw: tl.RawMessageMediaInvoice,
private readonly _extendedMedia?: MessageMedia, private readonly _extendedMedia?: MessageMedia | undefined,
) {} ) {}
/** /**

View file

@ -23,7 +23,7 @@ export class Photo extends FileLocation {
constructor( constructor(
readonly raw: tl.RawPhoto, readonly raw: tl.RawPhoto,
readonly media?: tl.RawMessageMediaPhoto, readonly media?: tl.RawMessageMediaPhoto | undefined,
) { ) {
const location = { const location = {
_: 'inputPhotoFileLocation', _: 'inputPhotoFileLocation',

View file

@ -9,7 +9,7 @@ import type { PeersIndex } from '../peers/peers-index.js'
export class PollAnswer { export class PollAnswer {
constructor( constructor(
readonly raw: tl.TypePollAnswer, readonly raw: tl.TypePollAnswer,
readonly result?: tl.TypePollAnswerVoters, readonly result?: tl.RawPollAnswerVoters | undefined,
) {} ) {}
/** /**
@ -71,7 +71,7 @@ export class Poll {
constructor( constructor(
readonly raw: tl.TypePoll, readonly raw: tl.TypePoll,
readonly _peers: PeersIndex, readonly _peers: PeersIndex,
readonly results?: tl.TypePollResults, readonly results?: tl.RawPollResults | undefined,
) {} ) {}
/** /**

View file

@ -72,7 +72,7 @@ export class Sticker extends RawDocument {
constructor( constructor(
doc: tl.RawDocument, doc: tl.RawDocument,
readonly attr: tl.RawDocumentAttributeSticker | tl.RawDocumentAttributeCustomEmoji, readonly attr: tl.RawDocumentAttributeSticker | tl.RawDocumentAttributeCustomEmoji,
readonly attr2?: tl.RawDocumentAttributeImageSize | tl.RawDocumentAttributeVideo, readonly attr2?: tl.RawDocumentAttributeImageSize | tl.RawDocumentAttributeVideo | undefined,
) { ) {
super(doc) super(doc)
} }

View file

@ -24,7 +24,7 @@ export class Video extends RawDocument {
constructor( constructor(
doc: tl.RawDocument, doc: tl.RawDocument,
readonly attr: tl.RawDocumentAttributeVideo | tl.RawDocumentAttributeImageSize, readonly attr: tl.RawDocumentAttributeVideo | tl.RawDocumentAttributeImageSize,
readonly media?: tl.RawMessageMediaDocument, readonly media?: tl.RawMessageMediaDocument | undefined,
) { ) {
super(doc) super(doc)
} }

View file

@ -13,7 +13,10 @@ export type InputMessageId = { chatId: InputPeerLike, message: number } | { mess
export type OmitInputMessageId<T> = Omit<T, 'chatId' | 'message'> export type OmitInputMessageId<T> = Omit<T, 'chatId' | 'message'>
/** @internal */ /** @internal */
export function normalizeInputMessageId(id: InputMessageId) { export function normalizeInputMessageId(id: InputMessageId): {
chatId: InputPeerLike
message: number
} {
if ('chatId' in id) return id if ('chatId' in id) return id
return { chatId: id.message.chat.inputPeer, message: id.message.id } return { chatId: id.message.chat.inputPeer, message: id.message.id }

View file

@ -67,7 +67,7 @@ export class MessageEntity {
* *
* Since JS strings are UTF-16, you can use this as-is * Since JS strings are UTF-16, you can use this as-is
*/ */
get offset() { get offset(): number {
return this.raw.offset return this.raw.offset
} }
@ -76,7 +76,7 @@ export class MessageEntity {
* *
* Since JS strings are UTF-16, you can use this as-is * Since JS strings are UTF-16, you can use this as-is
*/ */
get length() { get length(): number {
return this.raw.length return this.raw.length
} }

View file

@ -8,7 +8,7 @@ import { makeInspectable } from '../../utils/inspectable.js'
export class ChatColors { export class ChatColors {
constructor( constructor(
private readonly _peerId: number, private readonly _peerId: number,
readonly raw?: tl.RawPeerColor, readonly raw?: tl.RawPeerColor | undefined,
) {} ) {}
/** /**

View file

@ -16,7 +16,7 @@ export class ChatInviteLink {
constructor( constructor(
raw: tl.TypeExportedChatInvite, raw: tl.TypeExportedChatInvite,
readonly _peers?: PeersIndex, readonly _peers?: PeersIndex | undefined,
) { ) {
assertTypeIsNot('ChatInviteLink', raw, 'chatInvitePublicJoinRequests') assertTypeIsNot('ChatInviteLink', raw, 'chatInvitePublicJoinRequests')

View file

@ -21,7 +21,7 @@ export class ForumTopic {
constructor( constructor(
readonly raw: tl.RawForumTopic, readonly raw: tl.RawForumTopic,
readonly _peers: PeersIndex, readonly _peers: PeersIndex,
readonly _messages?: Map<number, tl.TypeMessage>, readonly _messages?: Map<number, tl.TypeMessage> | undefined,
) {} ) {}
static parseTlForumTopics(topics: tl.messages.TypeForumTopics): ForumTopic[] { static parseTlForumTopics(topics: tl.messages.TypeForumTopics): ForumTopic[] {

View file

@ -14,7 +14,7 @@ import { StoriesStealthMode } from './stealth-mode.js'
*/ */
export class AllStories { export class AllStories {
/** Peers index */ /** Peers index */
readonly _peers readonly _peers: PeersIndex
constructor( constructor(
/** Raw TL object */ /** Raw TL object */
readonly raw: tl.stories.RawAllStories, readonly raw: tl.stories.RawAllStories,

View file

@ -18,7 +18,7 @@ import { normalizeInputReaction } from '../../reactions/index.js'
export class StoryElement { export class StoryElement {
private constructor(private _position: tl.RawMediaAreaCoordinates) {} private constructor(private _position: tl.RawMediaAreaCoordinates) {}
static at(params: { x: number, y: number, width: number, height: number, rotation?: number }) { static at(params: { x: number, y: number, width: number, height: number, rotation?: number }): StoryElement {
return new StoryElement({ return new StoryElement({
_: 'mediaAreaCoordinates', _: 'mediaAreaCoordinates',
x: params.x, x: params.x,

View file

@ -3,6 +3,9 @@ import { tl } from '@mtcute/tl'
import { MtArgumentError } from '../../types/errors.js' import { MtArgumentError } from '../../types/errors.js'
import type { MaybePromise } from '../../types/utils.js' import type { MaybePromise } from '../../types/utils.js'
import { assertNever } from '../../types/utils.js' import { assertNever } from '../../types/utils.js'
import type {
Logger,
} from '../../utils/index.js'
import { import {
AsyncLock, AsyncLock,
ConditionVariable, ConditionVariable,
@ -89,26 +92,32 @@ const UPDATES_TOO_LONG = { _: 'updatesTooLong' } as const
// todo: fix docs // todo: fix docs
export class UpdatesManager { export class UpdatesManager {
updatesLoopActive = false updatesLoopActive = false
updatesLoopCv = new ConditionVariable() updatesLoopCv: ConditionVariable = new ConditionVariable()
postponedTimer = new EarlyTimer() postponedTimer: EarlyTimer = new EarlyTimer()
hasTimedoutPostponed = false hasTimedoutPostponed = false
pendingUpdateContainers = new SortedLinkedList<PendingUpdateContainer>((a, b) => a.seqStart - b.seqStart) pendingUpdateContainers: SortedLinkedList<PendingUpdateContainer>
pendingPtsUpdates = new SortedLinkedList<PendingUpdate>((a, b) => a.ptsBefore! - b.ptsBefore!) = new SortedLinkedList((a, b) => a.seqStart - b.seqStart)
pendingPtsUpdatesPostponed = new SortedLinkedList<PendingUpdate>((a, b) => a.ptsBefore! - b.ptsBefore!)
pendingQtsUpdates = new SortedLinkedList<PendingUpdate>((a, b) => a.qtsBefore! - b.qtsBefore!)
pendingQtsUpdatesPostponed = new SortedLinkedList<PendingUpdate>((a, b) => a.qtsBefore! - b.qtsBefore!)
pendingUnorderedUpdates = new Deque<PendingUpdate>()
noDispatchEnabled pendingPtsUpdates: SortedLinkedList<PendingUpdate> = new SortedLinkedList((a, b) => a.ptsBefore! - b.ptsBefore!)
pendingPtsUpdatesPostponed: SortedLinkedList<PendingUpdate>
= new SortedLinkedList((a, b) => a.ptsBefore! - b.ptsBefore!)
pendingQtsUpdates: SortedLinkedList<PendingUpdate> = new SortedLinkedList((a, b) => a.qtsBefore! - b.qtsBefore!)
pendingQtsUpdatesPostponed: SortedLinkedList<PendingUpdate>
= new SortedLinkedList((a, b) => a.qtsBefore! - b.qtsBefore!)
pendingUnorderedUpdates: Deque<PendingUpdate> = new Deque()
noDispatchEnabled: boolean
// channel id or 0 => msg id // channel id or 0 => msg id
noDispatchMsg = new Map<number, Set<number>>() noDispatchMsg: Map<number, Set<number>> = new Map()
// channel id or 0 => pts // channel id or 0 => pts
noDispatchPts = new Map<number, Set<number>>() noDispatchPts: Map<number, Set<number>> = new Map()
noDispatchQts = new Set<number>() noDispatchQts: Set<number> = new Set()
lock = new AsyncLock() lock: AsyncLock = new AsyncLock()
// rpsIncoming?: RpsMeter // rpsIncoming?: RpsMeter
// rpsProcessing?: RpsMeter // rpsProcessing?: RpsMeter
@ -129,14 +138,14 @@ export class UpdatesManager {
// whether to catch up channels from the locally stored pts // whether to catch up channels from the locally stored pts
catchingUp = false catchingUp = false
catchUpOnStart catchUpOnStart: boolean
cpts = new Map<number, number>() cpts: Map<number, number> = new Map()
cptsMod = new Map<number, number>() cptsMod: Map<number, number> = new Map()
channelDiffTimeouts = new Map<number, NodeJS.Timeout>() channelDiffTimeouts: Map<number, NodeJS.Timeout> = new Map()
channelsOpened = new Map<number, number>() channelsOpened: Map<number, number> = new Map()
log log: Logger
private _handler: RawUpdateHandler = () => {} private _handler: RawUpdateHandler = () => {}
private _onCatchingUp: (catchingUp: boolean) => void = () => {} private _onCatchingUp: (catchingUp: boolean) => void = () => {}
@ -189,7 +198,7 @@ export class UpdatesManager {
this._onCatchingUp = handler this._onCatchingUp = handler
} }
destroy() { destroy(): void {
this.stopLoop() this.stopLoop()
} }

View file

@ -60,7 +60,7 @@ export function encodeInlineMessageId(id: tl.TypeInputBotInlineMessageID): strin
return getPlatform().base64Encode(writer.result(), true) return getPlatform().base64Encode(writer.result(), true)
} }
export function normalizeInlineId(id: string | tl.TypeInputBotInlineMessageID) { export function normalizeInlineId(id: string | tl.TypeInputBotInlineMessageID): tl.TypeInputBotInlineMessageID {
if (typeof id === 'string') { if (typeof id === 'string') {
return parseInlineMessageId(id) return parseInlineMessageId(id)
} }

View file

@ -4,7 +4,7 @@ import { assertNever } from '../../types/utils.js'
import { MtInvalidPeerTypeError } from '../types/errors.js' import { MtInvalidPeerTypeError } from '../types/errors.js'
import type { InputPeerLike } from '../types/peers/index.js' import type { InputPeerLike } from '../types/peers/index.js'
export const INVITE_LINK_REGEX export const INVITE_LINK_REGEX: RegExp
= /^(?:https?:\/\/)?(?:www\.)?t(?:elegram)?\.(?:org|me|dog)\/(?:joinchat\/|\+)([\w-]+)$/i = /^(?:https?:\/\/)?(?:www\.)?t(?:elegram)?\.(?:org|me|dog)\/(?:joinchat\/|\+)([\w-]+)$/i
// helpers to convert result of `resolvePeer` function // helpers to convert result of `resolvePeer` function
@ -145,7 +145,7 @@ export function inputPeerToPeer(inp: tl.TypeInputPeer): tl.TypePeer {
} }
} }
export function extractUsernames(obj: tl.RawUser | tl.RawChannel) { export function extractUsernames(obj: tl.RawUser | tl.RawChannel): string[] {
if (obj.usernames?.length) return obj.usernames.map(x => x.username.toLowerCase()) if (obj.usernames?.length) return obj.usernames.map(x => x.username.toLowerCase())
if (obj.username) return [obj.username.toLowerCase()] if (obj.username) return [obj.username.toLowerCase()]

View file

@ -4,14 +4,15 @@ import type { ITelegramClient } from '../client.types.js'
type Resolve<T> = (value: T | PromiseLike<T>) => void type Resolve<T> = (value: T | PromiseLike<T>) => void
type Reject = (err?: unknown) => void type Reject = (err?: unknown) => void
type WaitersMap<K, U, T> = Map<K, [T, Resolve<U | null>, Reject][]> type WaitersMap<U, T> = Map<string | number, [T, Resolve<U | null>, Reject][]>
interface InternalState<K, U, T> { interface InternalState<U, T> {
waiters: WaitersMap<K, U, T> waiters: WaitersMap<U, T>
fetchingKeys: Set<K> fetchingKeys: Set<string | number>
retryQueue: Deque<T> retryQueue: Deque<T>
numRunning: number numRunning: number
} }
export type BatchedQuery<T, U> = (client: ITelegramClient, item: T) => Promise<U | null>
// todo: should it be MtClient? // todo: should it be MtClient?
/** /**
@ -23,7 +24,7 @@ interface InternalState<K, U, T> {
* - "key" - unique identifier of the item, which should be deriveable from both input and output. * - "key" - unique identifier of the item, which should be deriveable from both input and output.
* used for matching input and output items and deduplicating them. * used for matching input and output items and deduplicating them.
*/ */
export function batchedQuery<T, U, K extends string | number>(params: { export function batchedQuery<T, U>(params: {
/** /**
* Fetcher function, taking an array of input items and returning an array of output items. * Fetcher function, taking an array of input items and returning an array of output items.
* *
@ -33,9 +34,9 @@ export function batchedQuery<T, U, K extends string | number>(params: {
fetch: (client: ITelegramClient, items: T[]) => Promise<U[]> fetch: (client: ITelegramClient, items: T[]) => Promise<U[]>
/** Key derivation function for input items */ /** Key derivation function for input items */
inputKey: (item: T, client: ITelegramClient) => K inputKey: (item: T, client: ITelegramClient) => string | number
/** Key derivation function for output items */ /** Key derivation function for output items */
outputKey: (item: U, client: ITelegramClient) => K outputKey: (item: U, client: ITelegramClient) => string | number
/** /**
* Maximum number of items to be passed to the `fetcher` function at once. * Maximum number of items to be passed to the `fetcher` function at once.
@ -64,13 +65,13 @@ export function batchedQuery<T, U, K extends string | number>(params: {
* or an array of items for which the query should be retried (waiters for other items will throw `err`). * or an array of items for which the query should be retried (waiters for other items will throw `err`).
*/ */
retrySingleOnError?: (items: T[], err: unknown) => boolean | T[] retrySingleOnError?: (items: T[], err: unknown) => boolean | T[]
}): (client: ITelegramClient, item: T) => Promise<U | null> { }): BatchedQuery<T, U> {
const { inputKey, outputKey, fetch, maxBatchSize = Infinity, maxConcurrent = 1, retrySingleOnError } = params const { inputKey, outputKey, fetch, maxBatchSize = Infinity, maxConcurrent = 1, retrySingleOnError } = params
const symbol = Symbol('batchedQueryState') const symbol = Symbol('batchedQueryState')
function getState(client_: ITelegramClient) { function getState(client_: ITelegramClient) {
const client = client_ as { [symbol]?: InternalState<K, U, T> } const client = client_ as { [symbol]?: InternalState<U, T> }
if (!client[symbol]) { if (!client[symbol]) {
client[symbol] = { client[symbol] = {
@ -84,7 +85,7 @@ export function batchedQuery<T, U, K extends string | number>(params: {
return client[symbol] return client[symbol]
} }
function addWaiter(client: ITelegramClient, waiters: WaitersMap<K, U, T>, item: T) { function addWaiter(client: ITelegramClient, waiters: WaitersMap<U, T>, item: T) {
const key = inputKey(item, client) const key = inputKey(item, client)
let arr = waiters.get(key) let arr = waiters.get(key)
@ -99,7 +100,7 @@ export function batchedQuery<T, U, K extends string | number>(params: {
}) })
} }
function popWaiters(waiters: WaitersMap<K, U, T>, key: K) { function popWaiters(waiters: WaitersMap<U, T>, key: string | number) {
const arr = waiters.get(key) const arr = waiters.get(key)
if (!arr) return [] if (!arr) return []
@ -108,19 +109,19 @@ export function batchedQuery<T, U, K extends string | number>(params: {
return arr return arr
} }
function startLoops(client: ITelegramClient, state: InternalState<K, U, T>) { function startLoops(client: ITelegramClient, state: InternalState<U, T>) {
for (let i = state.numRunning; i <= maxConcurrent; i++) { for (let i = state.numRunning; i <= maxConcurrent; i++) {
processPending(client, state) processPending(client, state)
} }
} }
function processPending(client: ITelegramClient, state: InternalState<K, U, T>) { function processPending(client: ITelegramClient, state: InternalState<U, T>) {
const { waiters, fetchingKeys, retryQueue } = state const { waiters, fetchingKeys, retryQueue } = state
if (state.numRunning >= maxConcurrent) return if (state.numRunning >= maxConcurrent) return
const request: T[] = [] const request: T[] = []
const requestKeys: K[] = [] const requestKeys: (string | number)[] = []
let isRetryRequest = false let isRetryRequest = false
if (retryQueue.length > 0) { if (retryQueue.length > 0) {
@ -152,7 +153,7 @@ export function batchedQuery<T, U, K extends string | number>(params: {
// eslint-disable-next-line ts/no-floating-promises // eslint-disable-next-line ts/no-floating-promises
fetch(client, request) fetch(client, request)
.then((res) => { .then((res) => {
const receivedKeys = new Set<K>() const receivedKeys = new Set()
for (const it of res) { for (const it of res) {
const key = outputKey(it, client) const key = outputKey(it, client)

View file

@ -24,7 +24,10 @@ export async function streamToBuffer(stream: ReadableStream<Uint8Array>): Promis
return concatBuffers(chunks) return concatBuffers(chunks)
} }
export function createChunkedReader(stream: ReadableStream<Uint8Array>, chunkSize: number) { export function createChunkedReader(stream: ReadableStream<Uint8Array>, chunkSize: number): {
ended: () => boolean
read: () => Promise<Uint8Array | null>
} {
const reader = stream.getReader() const reader = stream.getReader()
const lock = new AsyncLock() const lock = new AsyncLock()

View file

@ -5,7 +5,7 @@ import type { WorkerInvoker } from './invoker.js'
export class AppConfigManagerProxy implements PublicPart<AppConfigManager> { export class AppConfigManagerProxy implements PublicPart<AppConfigManager> {
readonly get: AppConfigManager['get'] readonly get: AppConfigManager['get']
readonly getField readonly getField: AppConfigManager['getField']
constructor(readonly invoker: WorkerInvoker) { constructor(readonly invoker: WorkerInvoker) {
const bind = invoker.makeBinder<AppConfigManager>('app-config') const bind = invoker.makeBinder<AppConfigManager>('app-config')

View file

@ -57,7 +57,7 @@ export class WorkerInvoker {
return this._invoke(target, method, args, false, abortSignal) as Promise<unknown> return this._invoke(target, method, args, false, abortSignal) as Promise<unknown>
} }
handleResult(msg: Extract<WorkerOutboundMessage, { type: 'result' }>) { handleResult(msg: Extract<WorkerOutboundMessage, { type: 'result' }>): void {
const promise = this._pending.get(msg.id) const promise = this._pending.get(msg.id)
if (!promise) return if (!promise) return

View file

@ -18,36 +18,36 @@ export interface TelegramWorkerPortOptions {
} }
export abstract class TelegramWorkerPort<Custom extends WorkerCustomMethods> implements ITelegramClient { export abstract class TelegramWorkerPort<Custom extends WorkerCustomMethods> implements ITelegramClient {
readonly log readonly log: LogManager
private _connection private _connection
private _invoker private _invoker
readonly storage readonly storage: TelegramStorageProxy
readonly appConfig readonly appConfig: AppConfigManagerProxy
// bound methods // bound methods
readonly prepare readonly prepare: ITelegramClient['prepare']
private _connect private _connect
readonly close readonly close: ITelegramClient['close']
readonly notifyLoggedIn readonly notifyLoggedIn: ITelegramClient['notifyLoggedIn']
readonly notifyLoggedOut readonly notifyLoggedOut: ITelegramClient['notifyLoggedOut']
readonly notifyChannelOpened readonly notifyChannelOpened: ITelegramClient['notifyChannelOpened']
readonly notifyChannelClosed readonly notifyChannelClosed: ITelegramClient['notifyChannelClosed']
readonly importSession readonly importSession: ITelegramClient['importSession']
readonly exportSession readonly exportSession: ITelegramClient['exportSession']
readonly handleClientUpdate readonly handleClientUpdate: ITelegramClient['handleClientUpdate']
readonly getApiCrenetials readonly getApiCrenetials: ITelegramClient['getApiCrenetials']
readonly getPoolSize readonly getPoolSize: ITelegramClient['getPoolSize']
readonly getPrimaryDcId readonly getPrimaryDcId: ITelegramClient['getPrimaryDcId']
readonly changePrimaryDc readonly changePrimaryDc: ITelegramClient['changePrimaryDc']
readonly computeSrpParams readonly computeSrpParams: ITelegramClient['computeSrpParams']
readonly computeNewPasswordHash readonly computeNewPasswordHash: ITelegramClient['computeNewPasswordHash']
readonly startUpdatesLoop readonly startUpdatesLoop: ITelegramClient['startUpdatesLoop']
readonly stopUpdatesLoop readonly stopUpdatesLoop: ITelegramClient['stopUpdatesLoop']
private _abortController = new AbortController() private _abortController = new AbortController()
readonly stopSignal = this._abortController.signal readonly stopSignal: AbortSignal = this._abortController.signal
constructor(readonly options: TelegramWorkerPortOptions) { constructor(readonly options: TelegramWorkerPortOptions) {
this.log = new LogManager('worker') this.log = new LogManager('worker')

View file

@ -60,12 +60,12 @@ class CurrentUserServiceProxy implements PublicPart<CurrentUserService> {
} }
class PeersServiceProxy implements PublicPart<PeersService> { class PeersServiceProxy implements PublicPart<PeersService> {
readonly updatePeersFrom readonly updatePeersFrom: PeersService['updatePeersFrom']
readonly store readonly store: PeersService['store']
readonly getById readonly getById: PeersService['getById']
readonly getByPhone readonly getByPhone: PeersService['getByPhone']
readonly getByUsername readonly getByUsername: PeersService['getByUsername']
readonly getCompleteById readonly getCompleteById: PeersService['getCompleteById']
constructor(private _invoker: WorkerInvoker) { constructor(private _invoker: WorkerInvoker) {
const bind = this._invoker.makeBinder<PeersService>('storage-peers') const bind = this._invoker.makeBinder<PeersService>('storage-peers')
@ -80,10 +80,10 @@ class PeersServiceProxy implements PublicPart<PeersService> {
} }
export class TelegramStorageProxy implements PublicPart<TelegramStorageManager> { export class TelegramStorageProxy implements PublicPart<TelegramStorageManager> {
readonly self readonly self: CurrentUserServiceProxy
readonly peers readonly peers: PeersServiceProxy
readonly clear readonly clear: TelegramStorageManager['clear']
constructor(private _invoker: WorkerInvoker) { constructor(private _invoker: WorkerInvoker) {
const bind = this._invoker.makeBinder<TelegramStorageManager>('storage') const bind = this._invoker.makeBinder<TelegramStorageManager>('storage')

View file

@ -24,7 +24,7 @@ export abstract class TelegramWorker<T extends WorkerCustomMethods> {
abstract registerWorker(handler: WorkerMessageHandler): RespondFn abstract registerWorker(handler: WorkerMessageHandler): RespondFn
readonly pendingAborts = new Map<number, AbortController>() readonly pendingAborts: Map<number, AbortController> = new Map()
constructor(readonly params: TelegramWorkerOptions<T>) { constructor(readonly params: TelegramWorkerOptions<T>) {
this.broadcast = this.registerWorker((message, respond) => { this.broadcast = this.registerWorker((message, respond) => {

View file

@ -20,9 +20,8 @@ import type { SessionConnection } from './session-connection.js'
// see https://core.telegram.org/mtproto/security_guidelines // see https://core.telegram.org/mtproto/security_guidelines
// const DH_SAFETY_RANGE = bigInt[2].pow(2048 - 64) // const DH_SAFETY_RANGE = bigInt[2].pow(2048 - 64)
const DH_SAFETY_RANGE = 2n ** (2048n - 64n) const DH_SAFETY_RANGE = 2n ** (2048n - 64n)
const KNOWN_DH_PRIME // eslint-disable-next-line style/max-len
const KNOWN_DH_PRIME = 0xC71CAEB9C6B1C9048E6C522F70F13F73980D40238E3E21C14934D037563D930F48198A0AA7C14058229493D22530F4DBFA336F6E0AC925139543AED44CCE7C3720FD51F69458705AC68CD4FE6B6B13ABDC9746512969328454F18FAF8C595F642477FE96BB2A941D5BCD1D4AC8CC49880708FA9B378E3C4F3A9060BEE67CF9A4A4A695811051907E162753B56B0F6B410DBA74D8A84B2A14B3144E0EF1284754FD17ED950D5965B4B9DD46582DB1178D169C6BC465B0D6FF9CA3928FEF5B9AE4E418FC15E83EBEA0F87FA9FF5EED70050DED2849F47BF959D956850CE929851F0D8115F635B105EE2E4E15D04B2454BF6F4FADF034B10403119CD8E3B92FCC5Bn
= 0xC71CAEB9C6B1C9048E6C522F70F13F73980D40238E3E21C14934D037563D930F48198A0AA7C14058229493D22530F4DBFA336F6E0AC925139543AED44CCE7C3720FD51F69458705AC68CD4FE6B6B13ABDC9746512969328454F18FAF8C595F642477FE96BB2A941D5BCD1D4AC8CC49880708FA9B378E3C4F3A9060BEE67CF9A4A4A695811051907E162753B56B0F6B410DBA74D8A84B2A14B3144E0EF1284754FD17ED950D5965B4B9DD46582DB1178D169C6BC465B0D6FF9CA3928FEF5B9AE4E418FC15E83EBEA0F87FA9FF5EED70050DED2849F47BF959D956850CE929851F0D8115F635B105EE2E4E15D04B2454BF6F4FADF034B10403119CD8E3B92FCC5Bn
const TWO_POW_2047 = 2n ** 2047n const TWO_POW_2047 = 2n ** 2047n
const TWO_POW_2048 = 2n ** 2048n const TWO_POW_2048 = 2n ** 2048n

View file

@ -204,7 +204,7 @@ export class MtClient extends EventEmitter {
/** TL writers map used by the client */ /** TL writers map used by the client */
readonly _writerMap: TlWriterMap readonly _writerMap: TlWriterMap
readonly _config = new ConfigManager(async () => { readonly _config: ConfigManager = new ConfigManager(async () => {
const res = await this.call({ _: 'help.getConfig' }) const res = await this.call({ _: 'help.getConfig' })
if (isTlRpcError(res)) throw new Error(`Failed to get config: ${res.errorMessage}`) if (isTlRpcError(res)) throw new Error(`Failed to get config: ${res.errorMessage}`)
@ -313,7 +313,7 @@ export class MtClient extends EventEmitter {
* *
* Call {@link connect} to actually connect. * Call {@link connect} to actually connect.
*/ */
prepare() { prepare(): Promise<void> {
return this._prepare.run() return this._prepare.run()
} }

View file

@ -92,40 +92,40 @@ export type PendingMessage =
* all the relevant state * all the relevant state
*/ */
export class MtprotoSession { export class MtprotoSession {
_sessionId = randomLong() _sessionId: Long = randomLong()
_authKey: AuthKey _authKey: AuthKey
_authKeyTemp: AuthKey _authKeyTemp: AuthKey
_authKeyTempSecondary: AuthKey _authKeyTempSecondary: AuthKey
_timeOffset = 0 _timeOffset = 0
_lastMessageId = Long.ZERO _lastMessageId: Long = Long.ZERO
_seqNo = 0 _seqNo = 0
/// state /// /// state ///
// recent msg ids // recent msg ids
recentOutgoingMsgIds = new LruSet<Long>(1000, true) recentOutgoingMsgIds: LruSet<Long> = new LruSet(1000, true)
recentIncomingMsgIds = new LruSet<Long>(1000, true) recentIncomingMsgIds: LruSet<Long> = new LruSet(1000, true)
// queues // queues
queuedRpc = new Deque<PendingRpc>() queuedRpc: Deque<PendingRpc> = new Deque()
queuedAcks: Long[] = [] queuedAcks: Long[] = []
queuedStateReq: Long[] = [] queuedStateReq: Long[] = []
queuedResendReq: Long[] = [] queuedResendReq: Long[] = []
queuedCancelReq: Long[] = [] queuedCancelReq: Long[] = []
getStateSchedule = new SortedArray<PendingRpc>([], (a, b) => a.getState! - b.getState!) getStateSchedule: SortedArray<PendingRpc> = new SortedArray<PendingRpc>([], (a, b) => a.getState! - b.getState!)
chains = new Map<string | number, Long>() chains: Map<string | number, Long> = new Map()
chainsPendingFails = new Map<string | number, SortedArray<PendingRpc>>() chainsPendingFails: Map<string | number, SortedArray<PendingRpc>> = new Map()
// requests info // requests info
pendingMessages = new LongMap<PendingMessage>() pendingMessages: LongMap<PendingMessage> = new LongMap()
destroySessionIdToMsgId = new LongMap<Long>() destroySessionIdToMsgId: LongMap<Long> = new LongMap()
lastPingRtt = Number.NaN lastPingRtt: number = Number.NaN
lastPingTime = 0 lastPingTime = 0
lastPingMsgId = Long.ZERO lastPingMsgId: Long = Long.ZERO
lastSessionCreatedUid = Long.ZERO lastSessionCreatedUid: Long = Long.ZERO
initConnectionCalled = false initConnectionCalled = false
authorizationPending = false authorizationPending = false
@ -176,7 +176,7 @@ export class MtprotoSession {
this._authKeyTempSecondary.reset() this._authKeyTempSecondary.reset()
} }
updateTimeOffset(offset: number) { updateTimeOffset(offset: number): void {
this.log.debug('time offset updated: %d', offset) this.log.debug('time offset updated: %d', offset)
this._timeOffset = offset this._timeOffset = offset
// lastMessageId was generated with (potentially) wrong time // lastMessageId was generated with (potentially) wrong time
@ -330,7 +330,7 @@ export class MtprotoSession {
return messageId return messageId
} }
onTransportFlood(callback: () => void) { onTransportFlood(callback: () => void): number | undefined {
if (this.current429Timeout) return // already waiting if (this.current429Timeout) return // already waiting
// all active queries must be resent after a timeout // all active queries must be resent after a timeout

View file

@ -32,7 +32,7 @@ export class MultiSessionConnection extends EventEmitter {
protected _connections: SessionConnection[] = [] protected _connections: SessionConnection[] = []
setCount(count: number, connect = this.params.isMainConnection): void { setCount(count: number, connect: boolean = this.params.isMainConnection): void {
this._count = count this._count = count
this._updateConnections(connect) this._updateConnections(connect)

View file

@ -456,7 +456,7 @@ export class DcConnectionManager {
this.main.setCount(count) this.main.setCount(count)
} }
async destroy() { async destroy(): Promise<void> {
await this.main.destroy() await this.main.destroy()
await this.upload.destroy() await this.upload.destroy()
await this.download.destroy() await this.download.destroy()
@ -469,15 +469,15 @@ export class DcConnectionManager {
* Class that manages all connections to Telegram servers. * Class that manages all connections to Telegram servers.
*/ */
export class NetworkManager { export class NetworkManager {
readonly _log readonly _log: Logger
readonly _storage readonly _storage: StorageManager
readonly _initConnectionParams: tl.RawInitConnectionRequest readonly _initConnectionParams: tl.RawInitConnectionRequest
readonly _transportFactory: TransportFactory readonly _transportFactory: TransportFactory
readonly _reconnectionStrategy: ReconnectionStrategy<PersistentConnectionParams> readonly _reconnectionStrategy: ReconnectionStrategy<PersistentConnectionParams>
readonly _connectionCount: ConnectionCountDelegate readonly _connectionCount: ConnectionCountDelegate
protected readonly _dcConnections = new Map<number, DcConnectionManager>() protected readonly _dcConnections: Map<number, DcConnectionManager> = new Map()
protected _primaryDc?: DcConnectionManager protected _primaryDc?: DcConnectionManager
private _updateHandler: (upd: tl.TypeUpdates, fromClient: boolean) => void private _updateHandler: (upd: tl.TypeUpdates, fromClient: boolean) => void
@ -866,7 +866,7 @@ export class NetworkManager {
} }
} }
getPoolSize(kind: ConnectionKind, dcId?: number) { getPoolSize(kind: ConnectionKind, dcId?: number): number {
const dc = dcId ? this._dcConnections.get(dcId) : this._primaryDc const dc = dcId ? this._dcConnections.get(dcId) : this._primaryDc
if (!dc) { if (!dc) {
@ -882,7 +882,7 @@ export class NetworkManager {
return dc[kind].getPoolSize() return dc[kind].getPoolSize()
} }
getPrimaryDcId() { getPrimaryDcId(): number {
if (!this._primaryDc) throw new MtcuteError('Not connected to any DC') if (!this._primaryDc) throw new MtcuteError('Not connected to any DC')
return this._primaryDc.dcId return this._primaryDc.dcId

View file

@ -4,7 +4,7 @@ import type { mtp } from '@mtcute/tl'
export class ServerSaltManager { export class ServerSaltManager {
private _futureSalts: mtp.RawMt_future_salt[] = [] private _futureSalts: mtp.RawMt_future_salt[] = []
currentSalt = Long.ZERO currentSalt: Long = Long.ZERO
isFetching = false isFetching = false

View file

@ -259,7 +259,7 @@ export class SessionConnection extends PersistentConnection {
this.emit('error', error) this.emit('error', error)
} }
protected onConnectionUsable() { protected onConnectionUsable(): void {
super.onConnectionUsable() super.onConnectionUsable()
if (this.params.withUpdates) { if (this.params.withUpdates) {
@ -1499,7 +1499,7 @@ export class SessionConnection extends PersistentConnection {
} }
} }
protected _onInactivityTimeout() { protected _onInactivityTimeout(): void {
// we should send all pending acks and other service messages // we should send all pending acks and other service messages
// before dropping the connection // before dropping the connection
// additionally, if we are still waiting for some rpc results, // additionally, if we are still waiting for some rpc results,

View file

@ -62,7 +62,7 @@ export class PaddedIntermediatePacketCodec extends IntermediatePacketCodec {
} }
private _crypto!: ICryptoProvider private _crypto!: ICryptoProvider
setup?(crypto: ICryptoProvider) { setup?(crypto: ICryptoProvider): void {
this._crypto = crypto this._crypto = crypto
} }

View file

@ -9,7 +9,7 @@ import { concatBuffers } from '../../utils/index.js'
* multiple transport packets. * multiple transport packets.
*/ */
export abstract class StreamedCodec extends EventEmitter { export abstract class StreamedCodec extends EventEmitter {
protected _stream = new Uint8Array(0) protected _stream: Uint8Array = new Uint8Array(0)
/** /**
* Should return whether a full packet is available * Should return whether a full packet is available

View file

@ -11,5 +11,5 @@ export class MemoryStorageDriver implements IStorageDriver {
return this.states.get(repo) as T return this.states.get(repo) as T
} }
load() {} load(): void {}
} }

View file

@ -17,9 +17,9 @@ export { MemoryStorageDriver } from './driver.js'
* or if you know exactly what you're doing. * or if you know exactly what you're doing.
*/ */
export class MemoryStorage implements IMtStorageProvider, ITelegramStorageProvider { export class MemoryStorage implements IMtStorageProvider, ITelegramStorageProvider {
readonly driver = new MemoryStorageDriver() readonly driver: MemoryStorageDriver = new MemoryStorageDriver()
readonly kv = new MemoryKeyValueRepository(this.driver) readonly kv: MemoryKeyValueRepository = new MemoryKeyValueRepository(this.driver)
readonly authKeys = new MemoryAuthKeysRepository(this.driver) readonly authKeys: MemoryAuthKeysRepository = new MemoryAuthKeysRepository(this.driver)
readonly peers = new MemoryPeersRepository(this.driver) readonly peers: MemoryPeersRepository = new MemoryPeersRepository(this.driver)
readonly refMessages = new MemoryRefMessagesRepository(this.driver) readonly refMessages: MemoryRefMessagesRepository = new MemoryRefMessagesRepository(this.driver)
} }

View file

@ -8,9 +8,9 @@ interface AuthKeysState {
} }
export class MemoryAuthKeysRepository implements IAuthKeysRepository { export class MemoryAuthKeysRepository implements IAuthKeysRepository {
readonly state readonly state: AuthKeysState
constructor(readonly _driver: MemoryStorageDriver) { constructor(readonly _driver: MemoryStorageDriver) {
this.state = this._driver.getState<AuthKeysState>('authKeys', () => ({ this.state = this._driver.getState('authKeys', () => ({
authKeys: new Map(), authKeys: new Map(),
authKeysTemp: new Map(), authKeysTemp: new Map(),
authKeysTempExpiry: new Map(), authKeysTempExpiry: new Map(),

View file

@ -2,7 +2,7 @@ import type { IKeyValueRepository } from '../../repository/key-value.js'
import type { MemoryStorageDriver } from '../driver.js' import type { MemoryStorageDriver } from '../driver.js'
export class MemoryKeyValueRepository implements IKeyValueRepository { export class MemoryKeyValueRepository implements IKeyValueRepository {
readonly state readonly state: Map<string, Uint8Array>
constructor(readonly _driver: MemoryStorageDriver) { constructor(readonly _driver: MemoryStorageDriver) {
this.state = this._driver.getState<Map<string, Uint8Array>>('kv', () => new Map()) this.state = this._driver.getState<Map<string, Uint8Array>>('kv', () => new Map())
} }

View file

@ -8,9 +8,9 @@ interface PeersState {
} }
export class MemoryPeersRepository implements IPeersRepository { export class MemoryPeersRepository implements IPeersRepository {
readonly state readonly state: PeersState
constructor(readonly _driver: MemoryStorageDriver) { constructor(readonly _driver: MemoryStorageDriver) {
this.state = this._driver.getState<PeersState>('peers', () => ({ this.state = this._driver.getState('peers', () => ({
entities: new Map(), entities: new Map(),
usernameIndex: new Map(), usernameIndex: new Map(),
phoneIndex: new Map(), phoneIndex: new Map(),

View file

@ -6,9 +6,9 @@ interface RefMessagesState {
} }
export class MemoryRefMessagesRepository implements IReferenceMessagesRepository { export class MemoryRefMessagesRepository implements IReferenceMessagesRepository {
readonly state readonly state: RefMessagesState
constructor(readonly _driver: MemoryStorageDriver) { constructor(readonly _driver: MemoryStorageDriver) {
this.state = this._driver.getState<RefMessagesState>('refMessages', () => ({ this.state = this._driver.getState('refMessages', () => ({
refs: new Map(), refs: new Map(),
})) }))
} }

View file

@ -11,10 +11,10 @@ export { BaseSqliteStorageDriver }
export * from './types.js' export * from './types.js'
export class BaseSqliteStorage implements IMtStorageProvider, ITelegramStorageProvider { export class BaseSqliteStorage implements IMtStorageProvider, ITelegramStorageProvider {
readonly authKeys readonly authKeys: SqliteAuthKeysRepository
readonly kv readonly kv: SqliteKeyValueRepository
readonly refMessages readonly refMessages: SqliteRefMessagesRepository
readonly peers readonly peers: SqlitePeersRepository
constructor(readonly driver: BaseSqliteStorageDriver) { constructor(readonly driver: BaseSqliteStorageDriver) {
this.authKeys = new SqliteAuthKeysRepository(this.driver) this.authKeys = new SqliteAuthKeysRepository(this.driver)

View file

@ -9,6 +9,7 @@ import { AuthKeysService } from './service/auth-keys.js'
import type { ServiceOptions } from './service/base.js' import type { ServiceOptions } from './service/base.js'
import { DefaultDcsService } from './service/default-dcs.js' import { DefaultDcsService } from './service/default-dcs.js'
import { FutureSaltsService } from './service/future-salts.js' import { FutureSaltsService } from './service/future-salts.js'
import type { IStorageDriver } from './driver.js'
interface StorageManagerOptions { interface StorageManagerOptions {
provider: IMtStorageProvider provider: IMtStorageProvider
@ -41,12 +42,12 @@ export interface StorageManagerExtraOptions {
} }
export class StorageManager { export class StorageManager {
readonly provider readonly provider: IMtStorageProvider
readonly driver readonly driver: IStorageDriver
readonly log readonly log: Logger
readonly dcs readonly dcs: DefaultDcsService
readonly salts readonly salts: FutureSaltsService
readonly keys readonly keys: AuthKeysService
constructor(readonly options: StorageManagerOptions & StorageManagerExtraOptions) { constructor(readonly options: StorageManagerOptions & StorageManagerExtraOptions) {
this.provider = this.options.provider this.provider = this.options.provider
@ -87,7 +88,7 @@ export class StorageManager {
await this.driver.save?.() await this.driver.save?.()
} }
async clear(withAuthKeys = false) { async clear(withAuthKeys = false): Promise<void> {
if (withAuthKeys) { if (withAuthKeys) {
await this.provider.authKeys.deleteAll() await this.provider.authKeys.deleteAll()
} }

View file

@ -4,7 +4,7 @@ import type { ICryptoProvider } from './crypto/abstract.js'
/** /**
* Get the minimum number of bits required to represent a number * Get the minimum number of bits required to represent a number
*/ */
export function bigIntBitLength(n: bigint) { export function bigIntBitLength(n: bigint): number {
// not the fastest way, but at least not .toString(2) and not too complex // not the fastest way, but at least not .toString(2) and not too complex
// taken from: https://stackoverflow.com/a/76616288/22656950 // taken from: https://stackoverflow.com/a/76616288/22656950

View file

@ -21,7 +21,7 @@ export function buffersEqual(a: Uint8Array, b: Uint8Array): boolean {
* @param start Start offset * @param start Start offset
* @param end End offset * @param end End offset
*/ */
export function cloneBuffer(buf: Uint8Array, start = 0, end = buf.length): Uint8Array { export function cloneBuffer(buf: Uint8Array, start = 0, end: number = buf.length): Uint8Array {
const ret = new Uint8Array(end - start) const ret = new Uint8Array(end - start)
ret.set(buf.subarray(start, end)) ret.set(buf.subarray(start, end))
@ -67,7 +67,7 @@ export function dataViewFromBuffer(buf: Uint8Array): DataView {
/** /**
* Reverse a buffer (or a part of it) into a new buffer * Reverse a buffer (or a part of it) into a new buffer
*/ */
export function bufferToReversed(buf: Uint8Array, start = 0, end = buf.length): Uint8Array { export function bufferToReversed(buf: Uint8Array, start = 0, end: number = buf.length): Uint8Array {
const len = end - start const len = end - start
const ret = new Uint8Array(len) const ret = new Uint8Array(len)

View file

@ -45,11 +45,11 @@ export interface ICryptoProvider {
export abstract class BaseCryptoProvider { export abstract class BaseCryptoProvider {
abstract randomFill(buf: Uint8Array): void abstract randomFill(buf: Uint8Array): void
factorizePQ(pq: Uint8Array) { factorizePQ(pq: Uint8Array): [Uint8Array, Uint8Array] {
return factorizePQSync(this as unknown as ICryptoProvider, pq) return factorizePQSync(this as unknown as ICryptoProvider, pq)
} }
randomBytes(size: number) { randomBytes(size: number): Uint8Array {
const buf = new Uint8Array(size) const buf = new Uint8Array(size)
this.randomFill(buf) this.randomFill(buf)

View file

@ -24,8 +24,8 @@ export class Deque<T> {
protected _capacity: number protected _capacity: number
constructor( constructor(
readonly maxLength = Infinity, readonly maxLength: number = Infinity,
minCapacity = maxLength === Infinity ? MIN_INITIAL_CAPACITY : maxLength, minCapacity: number = maxLength === Infinity ? MIN_INITIAL_CAPACITY : maxLength,
) { ) {
let capacity = minCapacity let capacity = minCapacity
@ -51,7 +51,7 @@ export class Deque<T> {
this._capacity = capacity this._capacity = capacity
} }
protected _resize() { protected _resize(): void {
const p = this._head const p = this._head
const n = this._capacity const n = this._capacity
const r = n - p // number of elements to the right of the head const r = n - p // number of elements to the right of the head

View file

@ -39,7 +39,12 @@ export function throttle(func: () => void, delay: number): ThrottledFunction {
return res return res
} }
export function asyncResettable<T extends(...args: any[]) => Promise<any>>(func: T) { export function asyncResettable<T extends(...args: any[]) => Promise<any>>(func: T): {
run: T
finished: () => boolean
wait: () => Promise<any> | null
reset: () => void
} {
let runningPromise: Promise<any> | null = null let runningPromise: Promise<any> | null = null
let finished = false let finished = false

View file

@ -3,6 +3,7 @@ import type { tl } from '@mtcute/tl'
import { isPresent } from '../type-assertions.js' import { isPresent } from '../type-assertions.js'
import { assertNever } from '../../types/utils.js' import { assertNever } from '../../types/utils.js'
import type { Deeplink } from './common.js'
import { deeplinkBuilder } from './common.js' import { deeplinkBuilder } from './common.js'
/** /**
@ -10,12 +11,12 @@ import { deeplinkBuilder } from './common.js'
* *
* Used to link to bots with a start parameter * Used to link to bots with a start parameter
*/ */
export const botStart = deeplinkBuilder<{ export const botStart: Deeplink<{
/** Bot username */ /** Bot username */
username: string username: string
/** Start parameter */ /** Start parameter */
parameter: string parameter: string
}>({ }> = /* #__PURE__ */ deeplinkBuilder({
internalBuild: ({ username, parameter }) => ['resolve', { domain: username, start: parameter }], internalBuild: ({ username, parameter }) => ['resolve', { domain: username, start: parameter }],
internalParse: (path, query) => { internalParse: (path, query) => {
if (path !== 'resolve') return null if (path !== 'resolve') return null
@ -137,14 +138,14 @@ function parseBotAdmin(rights: string | null): BotAdminRight[] | undefined {
* Note that the user is still free to choose which rights to grant, and * Note that the user is still free to choose which rights to grant, and
* whether to grant them at all. * whether to grant them at all.
*/ */
export const botAddToGroup = deeplinkBuilder<{ export const botAddToGroup: Deeplink<{
/** Bot username */ /** Bot username */
bot: string bot: string
/** If specified, the client will call `/start parameter` on the bot once the bot has been added */ /** If specified, the client will call `/start parameter` on the bot once the bot has been added */
parameter?: string parameter?: string
/** Admin rights to request */ /** Admin rights to request */
admin?: BotAdminRight[] admin?: BotAdminRight[]
}>({ }> = /* #__PURE__ */ deeplinkBuilder({
internalBuild: ({ bot, parameter, admin }) => [ internalBuild: ({ bot, parameter, admin }) => [
'resolve', 'resolve',
{ domain: bot, startgroup: parameter ?? true, admin: normalizeBotAdmin(admin) }, { domain: bot, startgroup: parameter ?? true, admin: normalizeBotAdmin(admin) },
@ -193,12 +194,12 @@ export const botAddToGroup = deeplinkBuilder<{
* Note that the user is still free to choose which rights to grant, and * Note that the user is still free to choose which rights to grant, and
* whether to grant them at all. * whether to grant them at all.
*/ */
export const botAddToChannel = deeplinkBuilder<{ export const botAddToChannel: Deeplink<{
/** Bot username */ /** Bot username */
bot: string bot: string
/** Admin rights to request */ /** Admin rights to request */
admin?: BotAdminRight[] admin?: BotAdminRight[]
}>({ }> = /* #__PURE__ */ deeplinkBuilder({
internalBuild: ({ bot, admin }) => [ internalBuild: ({ bot, admin }) => [
'resolve', 'resolve',
{ domain: bot, startchannel: true, admin: normalizeBotAdmin(admin) }, { domain: bot, startchannel: true, admin: normalizeBotAdmin(admin) },
@ -240,12 +241,12 @@ export const botAddToChannel = deeplinkBuilder<{
* *
* Used to share games. * Used to share games.
*/ */
export const botGame = deeplinkBuilder<{ export const botGame: Deeplink<{
/** Bot username */ /** Bot username */
bot: string bot: string
/** Game short name */ /** Game short name */
game: string game: string
}>({ }> = /* #__PURE__ */ deeplinkBuilder({
internalBuild: ({ bot, game }) => ['resolve', { domain: bot, game }], internalBuild: ({ bot, game }) => ['resolve', { domain: bot, game }],
internalParse: (path, query) => { internalParse: (path, query) => {
if (path !== 'resolve') return null if (path !== 'resolve') return null
@ -280,14 +281,14 @@ export const botGame = deeplinkBuilder<{
* and a single bot can offer multiple named web apps, distinguished by * and a single bot can offer multiple named web apps, distinguished by
* their `short_name`. * their `short_name`.
*/ */
export const botWebApp = deeplinkBuilder<{ export const botWebApp: Deeplink<{
/** Bot username */ /** Bot username */
bot: string bot: string
/** App short name */ /** App short name */
app: string app: string
/** Parameter to be passed by the client to messages.requestAppWebView as `start_param` */ /** Parameter to be passed by the client to messages.requestAppWebView as `start_param` */
parameter?: string parameter?: string
}>({ }> = /* #__PURE__ */ deeplinkBuilder({
internalBuild: ({ bot, app, parameter }) => ['resolve', { domain: bot, appname: app, startapp: parameter }], internalBuild: ({ bot, app, parameter }) => ['resolve', { domain: bot, appname: app, startapp: parameter }],
internalParse: (path, query) => { internalParse: (path, query) => {
if (path !== 'resolve') return null if (path !== 'resolve') return null

View file

@ -1,3 +1,4 @@
import type { Deeplink } from './common.js'
import { deeplinkBuilder } from './common.js' import { deeplinkBuilder } from './common.js'
/** /**
@ -6,7 +7,7 @@ import { deeplinkBuilder } from './common.js'
* Used to join video/voice chats in groups, and livestreams in channels. * Used to join video/voice chats in groups, and livestreams in channels.
* Such links are generated using phone.exportGroupCallInvite. * Such links are generated using phone.exportGroupCallInvite.
*/ */
export const videoChat = deeplinkBuilder<{ export const videoChat: Deeplink<{
username: string username: string
/** /**
* Invite hash exported if the `can_self_unmute` flag is set when calling `phone.exportGroupCallInvite`: * Invite hash exported if the `can_self_unmute` flag is set when calling `phone.exportGroupCallInvite`:
@ -15,7 +16,7 @@ export const videoChat = deeplinkBuilder<{
*/ */
inviteHash?: string inviteHash?: string
isLivestream?: boolean isLivestream?: boolean
}>({ }> = /* #__PURE__ */ deeplinkBuilder({
internalBuild: ({ username, inviteHash, isLivestream }) => [ internalBuild: ({ username, inviteHash, isLivestream }) => [
'resolve', 'resolve',
{ {

View file

@ -1,3 +1,4 @@
import type { Deeplink } from './common.js'
import { deeplinkBuilder } from './common.js' import { deeplinkBuilder } from './common.js'
/** /**
@ -5,7 +6,7 @@ import { deeplinkBuilder } from './common.js'
* *
* Used to invite users to private groups and channels * Used to invite users to private groups and channels
*/ */
export const chatInvite = deeplinkBuilder<{ hash: string }>({ export const chatInvite: Deeplink<{ hash: string }> = /* #__PURE__ */ deeplinkBuilder({
internalBuild: ({ hash }) => ['join', { invite: hash }], internalBuild: ({ hash }) => ['join', { invite: hash }],
internalParse: (path, query) => { internalParse: (path, query) => {
if (path !== 'join') return null if (path !== 'join') return null
@ -32,7 +33,7 @@ export const chatInvite = deeplinkBuilder<{ hash: string }>({
/** /**
* Chat folder links * Chat folder links
*/ */
export const chatFolder = deeplinkBuilder<{ slug: string }>({ export const chatFolder: Deeplink<{ slug: string }> = /* #__PURE__ */ deeplinkBuilder({
internalBuild: ({ slug }) => ['addlist', { slug }], internalBuild: ({ slug }) => ['addlist', { slug }],
internalParse: (path, query) => { internalParse: (path, query) => {
if (path !== 'addlist') return null if (path !== 'addlist') return null
@ -76,127 +77,126 @@ function parseMediaTimestamp(timestamp: string) {
* *
* Note: `channelId` is a non-marked channel ID * Note: `channelId` is a non-marked channel ID
*/ */
export const message = deeplinkBuilder< export const message: Deeplink<({ username: string } | { channelId: number }) & {
({ username: string } | { channelId: number }) & { /** Message ID */
/** Message ID */ id: number
id: number /** Thread ID */
/** Thread ID */ threadId?: number
threadId?: number
/** /**
* For comments, `id` will contain the message ID of the channel message that started * For comments, `id` will contain the message ID of the channel message that started
* the comment section and this field will contain the message ID of the comment in * the comment section and this field will contain the message ID of the comment in
* the discussion group. * the discussion group.
*/ */
commentId?: number commentId?: number
/** /**
* Timestamp at which to start playing the media file present * Timestamp at which to start playing the media file present
* in the body or in the webpage preview of the message * in the body or in the webpage preview of the message
*/ */
mediaTimestamp?: number mediaTimestamp?: number
/** /**
* Whether this is a link to a specific message in the album or to the entire album * Whether this is a link to a specific message in the album or to the entire album
*/ */
single?: boolean single?: boolean
} }
>({ > = /* #__PURE__ */ deeplinkBuilder({
internalBuild: (params) => { internalBuild: (params) => {
const common = { const common = {
post: params.id, post: params.id,
thread: params.threadId, thread: params.threadId,
comment: params.commentId, comment: params.commentId,
t: params.mediaTimestamp, t: params.mediaTimestamp,
single: params.single ? '' : undefined, single: params.single ? '' : undefined,
} }
if ('username' in params) { if ('username' in params) {
return ['resolve', { domain: params.username, ...common }] return ['resolve', { domain: params.username, ...common }]
} }
return ['privatepost', { channel: params.channelId, ...common }] return ['privatepost', { channel: params.channelId, ...common }]
}, },
internalParse: (path, query) => { internalParse: (path, query) => {
const common = { const common = {
id: Number(query.get('post')), id: Number(query.get('post')),
threadId: query.has('thread') ? Number(query.get('thread')) : undefined, threadId: query.has('thread') ? Number(query.get('thread')) : undefined,
commentId: query.has('comment') ? Number(query.get('comment')) : undefined, commentId: query.has('comment') ? Number(query.get('comment')) : undefined,
mediaTimestamp: query.has('t') ? parseMediaTimestamp(query.get('t')!) : undefined, mediaTimestamp: query.has('t') ? parseMediaTimestamp(query.get('t')!) : undefined,
single: query.has('single'), single: query.has('single'),
} }
if (path === 'resolve') { if (path === 'resolve') {
const username = query.get('domain') const username = query.get('domain')
if (!username) return null if (!username) return null
return { username, ...common } return { username, ...common }
} }
if (path === 'privatepost') { if (path === 'privatepost') {
const channelId = Number(query.get('channel')) const channelId = Number(query.get('channel'))
if (!channelId) return null if (!channelId) return null
return { channelId, ...common } return { channelId, ...common }
} }
return null return null
}, },
externalBuild: (params) => { externalBuild: (params) => {
const common = { const common = {
comment: params.commentId, comment: params.commentId,
t: params.mediaTimestamp, t: params.mediaTimestamp,
single: params.single ? '' : undefined, single: params.single ? '' : undefined,
} }
if ('username' in params) { if ('username' in params) {
if (params.threadId) { if (params.threadId) {
return [`${params.username}/${params.threadId}/${params.id}`, common] return [`${params.username}/${params.threadId}/${params.id}`, common]
} }
return [`${params.username}/${params.id}`, common] return [`${params.username}/${params.id}`, common]
} }
if (params.threadId) { if (params.threadId) {
return [`c/${params.channelId}/${params.threadId}/${params.id}`, common] return [`c/${params.channelId}/${params.threadId}/${params.id}`, common]
} }
return [`c/${params.channelId}/${params.id}`, common] return [`c/${params.channelId}/${params.id}`, common]
}, },
externalParse: (path, query) => { externalParse: (path, query) => {
const chunks = path.split('/') const chunks = path.split('/')
if (chunks.length < 2) return null if (chunks.length < 2) return null
const id = Number(chunks[chunks.length - 1]) const id = Number(chunks[chunks.length - 1])
if (Number.isNaN(id)) return null if (Number.isNaN(id)) return null
const common = { const common = {
id, id,
commentId: query.has('comment') ? Number(query.get('comment')) : undefined, commentId: query.has('comment') ? Number(query.get('comment')) : undefined,
mediaTimestamp: query.has('t') ? parseMediaTimestamp(query.get('t')!) : undefined, mediaTimestamp: query.has('t') ? parseMediaTimestamp(query.get('t')!) : undefined,
single: query.has('single'), single: query.has('single'),
} }
if (chunks[0] === 'c') { if (chunks[0] === 'c') {
const channelId = Number(chunks[1]) const channelId = Number(chunks[1])
if (Number.isNaN(channelId)) return null if (Number.isNaN(channelId)) return null
return { return {
channelId, channelId,
threadId: chunks[3] ? Number(chunks[2]) : undefined, threadId: chunks[3] ? Number(chunks[2]) : undefined,
...common, ...common,
} }
} }
const username = chunks[0] const username = chunks[0]
if (username[0] === '+') return null if (username[0] === '+') return null
return { return {
username, username,
threadId: chunks[2] ? Number(chunks[1]) : undefined, threadId: chunks[2] ? Number(chunks[1]) : undefined,
...common, ...common,
} }
}, },
}) })

View file

@ -1,3 +1,4 @@
import type { Deeplink } from './common.js'
import { deeplinkBuilder } from './common.js' import { deeplinkBuilder } from './common.js'
/** /**
@ -5,7 +6,7 @@ import { deeplinkBuilder } from './common.js'
* *
* Used to share a prepared message and URL into a chosen chat's text field. * Used to share a prepared message and URL into a chosen chat's text field.
*/ */
export const share = deeplinkBuilder<{ url: string, text?: string }>({ export const share: Deeplink<{ url: string, text?: string }> = /* #__PURE__ */ deeplinkBuilder({
internalBuild: ({ url, text }) => ['msg_url', { url, text }], internalBuild: ({ url, text }) => ['msg_url', { url, text }],
internalParse: (path, query) => { internalParse: (path, query) => {
if (path !== 'msg_url') return null if (path !== 'msg_url') return null
@ -35,7 +36,7 @@ export const share = deeplinkBuilder<{ url: string, text?: string }>({
* *
* Used by users to boost channels, granting them the ability to post stories. * Used by users to boost channels, granting them the ability to post stories.
*/ */
export const boost = deeplinkBuilder<{ username: string } | { channelId: number }>({ export const boost: Deeplink<{ username: string } | { channelId: number }> = /* #__PURE__ */ deeplinkBuilder({
internalBuild: (params) => { internalBuild: (params) => {
if ('username' in params) { if ('username' in params) {
return ['boost', { domain: params.username }] return ['boost', { domain: params.username }]
@ -86,7 +87,7 @@ export const boost = deeplinkBuilder<{ username: string } | { channelId: number
/** /**
* Link to a shared folder (chat list) * Link to a shared folder (chat list)
*/ */
export const folder = deeplinkBuilder<{ slug: string }>({ export const folder: Deeplink<{ slug: string }> = /* #__PURE__ */ deeplinkBuilder({
// tg://addlist?slug=XXX // tg://addlist?slug=XXX
internalBuild: ({ slug }) => ['addlist', { slug }], internalBuild: ({ slug }) => ['addlist', { slug }],
internalParse: (path, query) => { internalParse: (path, query) => {

View file

@ -1,13 +1,14 @@
import type { Deeplink } from './common.js'
import { deeplinkBuilder } from './common.js' import { deeplinkBuilder } from './common.js'
/** /**
* MTProxy links * MTProxy links
*/ */
export const mtproxy = deeplinkBuilder<{ export const mtproxy: Deeplink<{
server: string server: string
port: number port: number
secret: string secret: string
}>({ }> = /* #__PURE__ */ deeplinkBuilder({
internalBuild: params => ['proxy', params], internalBuild: params => ['proxy', params],
externalBuild: params => ['proxy', params], externalBuild: params => ['proxy', params],
internalParse: (path, query) => { internalParse: (path, query) => {
@ -37,12 +38,12 @@ export const mtproxy = deeplinkBuilder<{
/** /**
* Socks5 proxy links * Socks5 proxy links
*/ */
export const socks5 = deeplinkBuilder<{ export const socks5: Deeplink<{
server: string server: string
port: number port: number
user?: string user?: string
pass?: string pass?: string
}>({ }> = /* #__PURE__ */ deeplinkBuilder({
internalBuild: params => ['socks', params], internalBuild: params => ['socks', params],
externalBuild: params => ['socks', params], externalBuild: params => ['socks', params],
internalParse: (path, query) => { internalParse: (path, query) => {

View file

@ -1,3 +1,4 @@
import type { Deeplink } from './common.js'
import { deeplinkBuilder } from './common.js' import { deeplinkBuilder } from './common.js'
/** /**
@ -5,10 +6,10 @@ import { deeplinkBuilder } from './common.js'
* *
* Used to import stickersets or custom emoji stickersets * Used to import stickersets or custom emoji stickersets
*/ */
export const stickerset = deeplinkBuilder<{ export const stickerset: Deeplink<{
slug: string slug: string
emoji?: boolean emoji?: boolean
}>({ }> = /* #__PURE__ */ deeplinkBuilder({
internalBuild: ({ slug, emoji }) => [emoji ? 'addemoji' : 'addstickers', { set: slug }], internalBuild: ({ slug, emoji }) => [emoji ? 'addemoji' : 'addstickers', { set: slug }],
internalParse: (path, query) => { internalParse: (path, query) => {
if (path !== 'addstickers' && path !== 'addemoji') return null if (path !== 'addstickers' && path !== 'addemoji') return null

View file

@ -1,3 +1,4 @@
import type { Deeplink } from './common.js'
import { deeplinkBuilder } from './common.js' import { deeplinkBuilder } from './common.js'
/** /**
@ -5,7 +6,7 @@ import { deeplinkBuilder } from './common.js'
* *
* Used to link to public users, groups and channels * Used to link to public users, groups and channels
*/ */
export const publicUsername = deeplinkBuilder<{ username: string }>({ export const publicUsername: Deeplink<{ username: string }> = /* #__PURE__ */ deeplinkBuilder({
internalBuild: ({ username }) => ['resolve', { domain: username }], internalBuild: ({ username }) => ['resolve', { domain: username }],
internalParse: (path, query) => { internalParse: (path, query) => {
if (path !== 'resolve') return null if (path !== 'resolve') return null
@ -36,7 +37,7 @@ export const publicUsername = deeplinkBuilder<{ username: string }>({
* and they have an expiration date, specified by the expires field of the exportedContactToken * and they have an expiration date, specified by the expires field of the exportedContactToken
* constructor returned by contacts.exportContactToken. * constructor returned by contacts.exportContactToken.
*/ */
export const temporaryProfile = deeplinkBuilder<{ token: string }>({ export const temporaryProfile: Deeplink<{ token: string }> = /* #__PURE__ */ deeplinkBuilder({
internalBuild: ({ token }) => ['contact', { token }], internalBuild: ({ token }) => ['contact', { token }],
internalParse: (path, query) => { internalParse: (path, query) => {
if (path !== 'contact') return null if (path !== 'contact') return null
@ -60,7 +61,7 @@ export const temporaryProfile = deeplinkBuilder<{ token: string }>({
* *
* Used to link to public and private users by their phone number. * Used to link to public and private users by their phone number.
*/ */
export const phoneNumber = deeplinkBuilder<{ phone: string }>({ export const phoneNumber: Deeplink<{ phone: string }> = /* #__PURE__ */ deeplinkBuilder({
internalBuild: ({ phone }) => ['resolve', { phone }], internalBuild: ({ phone }) => ['resolve', { phone }],
internalParse: (path, query) => { internalParse: (path, query) => {
if (path !== 'resolve') return null if (path !== 'resolve') return null

View file

@ -1,5 +1,6 @@
import { tl } from '@mtcute/tl' import { tl } from '@mtcute/tl'
import type { ICorePlatform } from '../platform.js'
import { getPlatform } from '../platform.js' import { getPlatform } from '../platform.js'
import { isTlRpcError } from './type-assertions.js' import { isTlRpcError } from './type-assertions.js'
@ -127,11 +128,11 @@ export class Logger {
this.mgr.handler(this.color, level, this.tag, this.getPrefix() + fmt, args) this.mgr.handler(this.color, level, this.tag, this.getPrefix() + fmt, args)
} }
readonly error = this.log.bind(this, LogManager.ERROR) readonly error: (fmt: string, ...args: unknown[]) => void = this.log.bind(this, LogManager.ERROR)
readonly warn = this.log.bind(this, LogManager.WARN) readonly warn: (fmt: string, ...args: unknown[]) => void = this.log.bind(this, LogManager.WARN)
readonly info = this.log.bind(this, LogManager.INFO) readonly info: (fmt: string, ...args: unknown[]) => void = this.log.bind(this, LogManager.INFO)
readonly debug = this.log.bind(this, LogManager.DEBUG) readonly debug: (fmt: string, ...args: unknown[]) => void = this.log.bind(this, LogManager.DEBUG)
readonly verbose = this.log.bind(this, LogManager.VERBOSE) readonly verbose: (fmt: string, ...args: unknown[]) => void = this.log.bind(this, LogManager.VERBOSE)
/** /**
* Create a {@link Logger} with the given tag * Create a {@link Logger} with the given tag
@ -158,9 +159,9 @@ export class LogManager extends Logger {
static DEBUG = 4 static DEBUG = 4
static VERBOSE = 5 static VERBOSE = 5
readonly platform readonly platform: ICorePlatform
level: number level: number
handler handler: (color: number, level: number, tag: string, fmt: string, args: unknown[]) => void
constructor(tag = 'base') { constructor(tag = 'base') {
// workaround because we cant pass this to super // workaround because we cant pass this to super

View file

@ -159,23 +159,23 @@ export class LongSet {
return this._set.size return this._set.size
} }
add(val: Long) { add(val: Long): void {
this._set.add(longToFastString(val)) this._set.add(longToFastString(val))
} }
delete(val: Long) { delete(val: Long): void {
this._set.delete(longToFastString(val)) this._set.delete(longToFastString(val))
} }
has(val: Long) { has(val: Long): boolean {
return this._set.has(longToFastString(val)) return this._set.has(longToFastString(val))
} }
clear() { clear(): void {
this._set.clear() this._set.clear()
} }
toArray() { toArray(): Long[] {
const arr: Long[] = [] const arr: Long[] = []
for (const v of this._set) { for (const v of this._set) {

View file

@ -33,12 +33,12 @@ export class LruSet<T extends string | number | Long> {
this._set = forLong ? new LongSet() : new Set() this._set = forLong ? new LongSet() : new Set()
} }
clear() { clear(): void {
this._first = this._last = undefined this._first = this._last = undefined
this._set.clear() this._set.clear()
} }
add(val: T) { add(val: T): void {
if (this._set.has(val as any)) return if (this._set.has(val as any)) return
if (!this._first) this._first = { v: val } if (!this._first) this._first = { v: val }
@ -59,7 +59,7 @@ export class LruSet<T extends string | number | Long> {
} }
} }
has(val: T) { has(val: T): boolean {
return this._set.has(val as any) return this._set.has(val as any)
} }
} }

View file

@ -8,7 +8,7 @@ export interface UserConfigPersisted {
apiHash: string apiHash: string
} }
export function getConfigFilePath() { export function getConfigFilePath(): string {
switch (process.platform) { switch (process.platform) {
case 'linux': case 'linux':
return path.join(homedir(), '.local', 'share', 'mtcute-create.json') return path.join(homedir(), '.local', 'share', 'mtcute-create.json')
@ -35,7 +35,7 @@ export async function readConfig(): Promise<UserConfigPersisted | null> {
} }
} }
export async function writeConfig(config: UserConfigPersisted) { export async function writeConfig(config: UserConfigPersisted): Promise<void> {
const filePath = getConfigFilePath() const filePath = getConfigFilePath()
await fs.mkdir(path.dirname(filePath), { recursive: true }) await fs.mkdir(path.dirname(filePath), { recursive: true })

View file

@ -11,7 +11,7 @@ export interface DependenciesList {
devDepdenencies: string[] devDepdenencies: string[]
} }
export function buildDependenciesList(config: UserConfig) { export function buildDependenciesList(config: UserConfig): DependenciesList {
const dependencies = [] const dependencies = []
const devDepdenencies = [] const devDepdenencies = []
@ -57,7 +57,7 @@ export function buildDependenciesList(config: UserConfig) {
} }
} }
export async function installDependencies(cwd: string, config: UserConfig) { export async function installDependencies(cwd: string, config: UserConfig): Promise<void> {
const { dependencies, devDepdenencies } = buildDependenciesList(config) const { dependencies, devDepdenencies } = buildDependenciesList(config)
if (config.packageManager === PackageManager.Deno) { if (config.packageManager === PackageManager.Deno) {

View file

@ -85,7 +85,7 @@ export function getInstallCommand(params: { mgr: PackageManager, packages: strin
return exec return exec
} }
export function getExecCommand(mgr: PackageManager, ...cmd: string[]) { export function getExecCommand(mgr: PackageManager, ...cmd: string[]): string[] {
switch (mgr) { switch (mgr) {
case PackageManager.Npm: case PackageManager.Npm:
return ['npx', ...cmd] return ['npx', ...cmd]
@ -100,7 +100,7 @@ export function getExecCommand(mgr: PackageManager, ...cmd: string[]) {
} }
} }
export function packageManagerToRuntime(mgr: PackageManager) { export function packageManagerToRuntime(mgr: PackageManager): 'node' | 'bun' | 'deno' {
switch (mgr) { switch (mgr) {
case PackageManager.Npm: case PackageManager.Npm:
case PackageManager.Yarn: case PackageManager.Yarn:

View file

@ -1,7 +1,7 @@
import * as colors from 'colorette' import * as colors from 'colorette'
import { spawn } from 'cross-spawn' import { spawn } from 'cross-spawn'
export function exec(cwd: string, ...cmd: string[]) { export function exec(cwd: string, ...cmd: string[]): Promise<void> {
return new Promise<void>((resolve, reject) => { return new Promise<void>((resolve, reject) => {
console.log(`${colors.blue('$')} ${cmd.map(it => (it.includes(' ') ? JSON.stringify(it) : it)).join(' ')}`) console.log(`${colors.blue('$')} ${cmd.map(it => (it.includes(' ') ? JSON.stringify(it) : it)).join(' ')}`)

View file

@ -83,7 +83,7 @@ class WrappedDatabase implements ISqliteDatabase {
export class SqliteStorageDriver extends BaseSqliteStorageDriver { export class SqliteStorageDriver extends BaseSqliteStorageDriver {
constructor( constructor(
readonly filename = ':memory:', readonly filename = ':memory:',
readonly params?: SqliteStorageDriverOptions, readonly params?: SqliteStorageDriverOptions | undefined,
) { ) {
super() super()
} }

View file

@ -8,7 +8,7 @@ export { SqliteStorageDriver } from './driver.js'
export class SqliteStorage extends BaseSqliteStorage { export class SqliteStorage extends BaseSqliteStorage {
constructor( constructor(
readonly filename = ':memory:', readonly filename = ':memory:',
readonly params?: SqliteStorageDriverOptions, readonly params?: SqliteStorageDriverOptions | undefined,
) { ) {
super(new SqliteStorageDriver(filename, params)) super(new SqliteStorageDriver(filename, params))
} }

View file

@ -89,7 +89,7 @@ export class DenoCryptoProvider extends BaseCryptoProvider implements ICryptoPro
return toUint8Array(gunzipSync(data)) return toUint8Array(gunzipSync(data))
} }
randomFill(buf: Uint8Array) { randomFill(buf: Uint8Array): void {
crypto.getRandomValues(buf) crypto.getRandomValues(buf)
} }
} }

View file

@ -6,7 +6,11 @@ import { Readable as NodeReadable } from 'node:stream'
import type { UploadFileLike } from '@mtcute/core' import type { UploadFileLike } from '@mtcute/core'
import { extractFileName } from '@mtcute/core/utils.js' import { extractFileName } from '@mtcute/core/utils.js'
export async function normalizeFile(file: UploadFileLike) { export async function normalizeFile(file: UploadFileLike): Promise<{
file: UploadFileLike
fileName?: string | undefined
fileSize?: number
} | null> {
if (typeof file === 'string') { if (typeof file === 'string') {
const fd = await Deno.open(file, { read: true }) const fd = await Deno.open(file, { read: true })

View file

@ -134,5 +134,5 @@ export abstract class BaseTcpTransport extends EventEmitter implements ITelegram
} }
export class TcpTransport extends BaseTcpTransport { export class TcpTransport extends BaseTcpTransport {
_packetCodec = new IntermediatePacketCodec() _packetCodec: IntermediatePacketCodec = new IntermediatePacketCodec()
} }

View file

@ -1,4 +1,4 @@
import type { OmitInputMessageId, ParametersSkip1 } from '@mtcute/core' import type { Message, OmitInputMessageId, ParametersSkip1, Sticker } from '@mtcute/core'
import { BusinessMessage } from '@mtcute/core' import { BusinessMessage } from '@mtcute/core'
import type { TelegramClient } from '@mtcute/core/client.js' import type { TelegramClient } from '@mtcute/core/client.js'
import type { import type {
@ -45,12 +45,12 @@ export class BusinessMessageContext extends BusinessMessage implements UpdateCon
} }
/** Get all custom emojis contained in this message (message group), if any */ /** Get all custom emojis contained in this message (message group), if any */
getCustomEmojis() { getCustomEmojis(): Promise<Sticker[]> {
return this.client.getCustomEmojisFromMessages(this.messages) return this.client.getCustomEmojisFromMessages(this.messages)
} }
/** Send a text message to the same chat (and topic, if applicable) as a given message */ /** Send a text message to the same chat (and topic, if applicable) as a given message */
answerText(...params: ParametersSkip1<TelegramClient['answerText']>) { answerText(...params: ParametersSkip1<TelegramClient['answerText']>): Promise<Message> {
const [send, params_ = {}] = params const [send, params_ = {}] = params
params_.businessConnectionId = this.update.connectionId params_.businessConnectionId = this.update.connectionId
@ -58,7 +58,7 @@ export class BusinessMessageContext extends BusinessMessage implements UpdateCon
} }
/** Send a media to the same chat (and topic, if applicable) as a given message */ /** Send a media to the same chat (and topic, if applicable) as a given message */
answerMedia(...params: ParametersSkip1<TelegramClient['answerMedia']>) { answerMedia(...params: ParametersSkip1<TelegramClient['answerMedia']>): Promise<Message> {
const [send, params_ = {}] = params const [send, params_ = {}] = params
params_.businessConnectionId = this.update.connectionId params_.businessConnectionId = this.update.connectionId
@ -66,7 +66,7 @@ export class BusinessMessageContext extends BusinessMessage implements UpdateCon
} }
/** Send a media group to the same chat (and topic, if applicable) as a given message */ /** Send a media group to the same chat (and topic, if applicable) as a given message */
answerMediaGroup(...params: ParametersSkip1<TelegramClient['answerMediaGroup']>) { answerMediaGroup(...params: ParametersSkip1<TelegramClient['answerMediaGroup']>): Promise<Message[]> {
const [send, params_ = {}] = params const [send, params_ = {}] = params
params_.businessConnectionId = this.update.connectionId params_.businessConnectionId = this.update.connectionId
@ -74,7 +74,7 @@ export class BusinessMessageContext extends BusinessMessage implements UpdateCon
} }
/** Send a text message in reply to this message */ /** Send a text message in reply to this message */
replyText(...params: ParametersSkip1<TelegramClient['replyText']>) { replyText(...params: ParametersSkip1<TelegramClient['replyText']>): Promise<Message> {
const [send, params_ = {}] = params const [send, params_ = {}] = params
params_.businessConnectionId = this.update.connectionId params_.businessConnectionId = this.update.connectionId
@ -82,7 +82,7 @@ export class BusinessMessageContext extends BusinessMessage implements UpdateCon
} }
/** Send a media in reply to this message */ /** Send a media in reply to this message */
replyMedia(...params: ParametersSkip1<TelegramClient['replyMedia']>) { replyMedia(...params: ParametersSkip1<TelegramClient['replyMedia']>): Promise<Message> {
const [send, params_ = {}] = params const [send, params_ = {}] = params
params_.businessConnectionId = this.update.connectionId params_.businessConnectionId = this.update.connectionId
@ -90,7 +90,7 @@ export class BusinessMessageContext extends BusinessMessage implements UpdateCon
} }
/** Send a media group in reply to this message */ /** Send a media group in reply to this message */
replyMediaGroup(...params: ParametersSkip1<TelegramClient['replyMediaGroup']>) { replyMediaGroup(...params: ParametersSkip1<TelegramClient['replyMediaGroup']>): Promise<Message[]> {
const [send, params_ = {}] = params const [send, params_ = {}] = params
params_.businessConnectionId = this.update.connectionId params_.businessConnectionId = this.update.connectionId
@ -98,28 +98,28 @@ export class BusinessMessageContext extends BusinessMessage implements UpdateCon
} }
/** Send a text message in reply to this message */ /** Send a text message in reply to this message */
quoteWithText(params: Parameters<TelegramClient['quoteWithText']>[1]) { quoteWithText(params: Parameters<TelegramClient['quoteWithText']>[1]): Promise<Message> {
params.businessConnectionId = this.update.connectionId params.businessConnectionId = this.update.connectionId
return this.client.quoteWithText(this, params) return this.client.quoteWithText(this, params)
} }
/** Send a media in reply to this message */ /** Send a media in reply to this message */
quoteWithMedia(params: Parameters<TelegramClient['quoteWithMedia']>[1]) { quoteWithMedia(params: Parameters<TelegramClient['quoteWithMedia']>[1]): Promise<Message> {
params.businessConnectionId = this.update.connectionId params.businessConnectionId = this.update.connectionId
return this.client.quoteWithMedia(this, params) return this.client.quoteWithMedia(this, params)
} }
/** Send a media group in reply to this message */ /** Send a media group in reply to this message */
quoteWithMediaGroup(params: Parameters<TelegramClient['quoteWithMediaGroup']>[1]) { quoteWithMediaGroup(params: Parameters<TelegramClient['quoteWithMediaGroup']>[1]): Promise<Message[]> {
params.businessConnectionId = this.update.connectionId params.businessConnectionId = this.update.connectionId
return this.client.quoteWithMediaGroup(this, params) return this.client.quoteWithMediaGroup(this, params)
} }
/** Delete this message (message group) */ /** Delete this message (message group) */
delete(params?: DeleteMessagesParams) { delete(params?: DeleteMessagesParams): Promise<void> {
return this.client.deleteMessagesById( return this.client.deleteMessagesById(
this.chat.inputPeer, this.chat.inputPeer,
this.messages.map(it => it.id), this.messages.map(it => it.id),
@ -128,7 +128,7 @@ export class BusinessMessageContext extends BusinessMessage implements UpdateCon
} }
/** Pin this message */ /** Pin this message */
pin(params?: OmitInputMessageId<Parameters<TelegramClient['pinMessage']>[0]>) { pin(params?: OmitInputMessageId<Parameters<TelegramClient['pinMessage']>[0]>): Promise<Message | null> {
return this.client.pinMessage({ return this.client.pinMessage({
chatId: this.chat.inputPeer, chatId: this.chat.inputPeer,
message: this.id, message: this.id,
@ -137,7 +137,7 @@ export class BusinessMessageContext extends BusinessMessage implements UpdateCon
} }
/** Unpin this message */ /** Unpin this message */
unpin() { unpin(): Promise<void> {
return this.client.unpinMessage({ return this.client.unpinMessage({
chatId: this.chat.inputPeer, chatId: this.chat.inputPeer,
message: this.id, message: this.id,
@ -145,7 +145,7 @@ export class BusinessMessageContext extends BusinessMessage implements UpdateCon
} }
/** Edit this message */ /** Edit this message */
edit(params: OmitInputMessageId<Parameters<TelegramClient['editMessage']>[0]>) { edit(params: OmitInputMessageId<Parameters<TelegramClient['editMessage']>[0]>): Promise<Message> {
return this.client.editMessage({ return this.client.editMessage({
chatId: this.chat.inputPeer, chatId: this.chat.inputPeer,
message: this.id, message: this.id,
@ -154,7 +154,7 @@ export class BusinessMessageContext extends BusinessMessage implements UpdateCon
} }
/** Forward this message (message group) */ /** Forward this message (message group) */
forwardTo(params: ForwardMessageOptions) { forwardTo(params: ForwardMessageOptions): Promise<Message[]> {
return this.client.forwardMessagesById({ return this.client.forwardMessagesById({
fromChatId: this.chat.inputPeer, fromChatId: this.chat.inputPeer,
messages: this.messages.map(it => it.id), messages: this.messages.map(it => it.id),
@ -163,7 +163,7 @@ export class BusinessMessageContext extends BusinessMessage implements UpdateCon
} }
/** Send a copy of this message (message group) */ /** Send a copy of this message (message group) */
copy(params: SendCopyParams & SendCopyGroupParams) { copy(params: SendCopyParams & SendCopyGroupParams): Promise<Message | Message[]> {
if (this.isMessageGroup) { if (this.isMessageGroup) {
return this.client.sendCopyGroup({ return this.client.sendCopyGroup({
messages: this.messages, messages: this.messages,
@ -178,7 +178,7 @@ export class BusinessMessageContext extends BusinessMessage implements UpdateCon
} }
/** React to this message */ /** React to this message */
react(params: OmitInputMessageId<Parameters<TelegramClient['sendReaction']>[0]>) { react(params: OmitInputMessageId<Parameters<TelegramClient['sendReaction']>[0]>): Promise<Message | null> {
return this.client.sendReaction({ return this.client.sendReaction({
chatId: this.chat.inputPeer, chatId: this.chat.inputPeer,
message: this.id, message: this.id,

View file

@ -20,7 +20,7 @@ export class CallbackQueryContext extends CallbackQuery implements UpdateContext
} }
/** Answer to this callback query */ /** Answer to this callback query */
answer(params: Parameters<TelegramClient['answerCallbackQuery']>[1]) { answer(params: Parameters<TelegramClient['answerCallbackQuery']>[1]): Promise<void> {
return this.client.answerCallbackQuery(this.id, params) return this.client.answerCallbackQuery(this.id, params)
} }
@ -37,7 +37,7 @@ export class CallbackQueryContext extends CallbackQuery implements UpdateContext
/** /**
* Edit the message that contained the callback button that was clicked. * Edit the message that contained the callback button that was clicked.
*/ */
async editMessage(params: Omit<Parameters<TelegramClient['editInlineMessage']>[0], 'messageId'>) { async editMessage(params: Omit<Parameters<TelegramClient['editInlineMessage']>[0], 'messageId'>): Promise<Message> {
return this.client.editMessage({ return this.client.editMessage({
chatId: this.raw.peer, chatId: this.raw.peer,
message: this.raw.msgId, message: this.raw.msgId,
@ -48,7 +48,9 @@ export class CallbackQueryContext extends CallbackQuery implements UpdateContext
/** /**
* Shortcut for getting the message and editing it. * Shortcut for getting the message and editing it.
*/ */
async editMessageWith(handler: (msg: Message) => MaybePromise<Parameters<CallbackQueryContext['editMessage']>[0]>) { async editMessageWith(
handler: (msg: Message) => MaybePromise<Parameters<CallbackQueryContext['editMessage']>[0]>,
): Promise<Message | undefined> {
const msg = await this.getMessage() const msg = await this.getMessage()
if (!msg) return if (!msg) return
@ -75,14 +77,14 @@ export class InlineCallbackQueryContext extends InlineCallbackQuery implements U
} }
/** Answer to this callback query */ /** Answer to this callback query */
answer(params: Parameters<TelegramClient['answerCallbackQuery']>[1]) { answer(params: Parameters<TelegramClient['answerCallbackQuery']>[1]): Promise<void> {
return this.client.answerCallbackQuery(this.id, params) return this.client.answerCallbackQuery(this.id, params)
} }
/** /**
* Edit the message that contained the callback button that was clicked. * Edit the message that contained the callback button that was clicked.
*/ */
async editMessage(params: Omit<Parameters<TelegramClient['editInlineMessage']>[0], 'messageId'>) { async editMessage(params: Omit<Parameters<TelegramClient['editInlineMessage']>[0], 'messageId'>): Promise<void> {
return this.client.editInlineMessage({ return this.client.editInlineMessage({
messageId: this.raw.msgId, messageId: this.raw.msgId,
...params, ...params,
@ -108,14 +110,14 @@ export class BusinessCallbackQueryContext
} }
/** Answer to this callback query */ /** Answer to this callback query */
answer(params: Parameters<TelegramClient['answerCallbackQuery']>[1]) { answer(params: Parameters<TelegramClient['answerCallbackQuery']>[1]): Promise<void> {
return this.client.answerCallbackQuery(this.id, params) return this.client.answerCallbackQuery(this.id, params)
} }
/** /**
* Edit the message that contained the callback button that was clicked. * Edit the message that contained the callback button that was clicked.
*/ */
async editMessage(params: Omit<Parameters<TelegramClient['editInlineMessage']>[0], 'messageId'>) { async editMessage(params: Omit<Parameters<TelegramClient['editInlineMessage']>[0], 'messageId'>): Promise<Message> {
return this.client.editMessage({ return this.client.editMessage({
message: this.message, message: this.message,
businessConnectionId: this.connectionId, businessConnectionId: this.connectionId,

View file

@ -20,7 +20,7 @@ export class InlineQueryContext extends InlineQuery implements UpdateContext<Inl
} }
/** Answer to this inline query */ /** Answer to this inline query */
answer(...params: ParametersSkip1<TelegramClient['answerInlineQuery']>) { answer(...params: ParametersSkip1<TelegramClient['answerInlineQuery']>): Promise<void> {
return this.client.answerInlineQuery(this.id, ...params) return this.client.answerInlineQuery(this.id, ...params)
} }
} }

View file

@ -1,7 +1,12 @@
import type { OmitInputMessageId, ParametersSkip1, Peer } from '@mtcute/core' import type { OmitInputMessageId, ParametersSkip1, Peer, Sticker } from '@mtcute/core'
import { Message, MtPeerNotFoundError } from '@mtcute/core' import { Message, MtPeerNotFoundError } from '@mtcute/core'
import type { TelegramClient } from '@mtcute/core/client.js' import type { TelegramClient } from '@mtcute/core/client.js'
import type { DeleteMessagesParams, ForwardMessageOptions, SendCopyGroupParams, SendCopyParams } from '@mtcute/core/methods.js' import type {
DeleteMessagesParams,
ForwardMessageOptions,
SendCopyGroupParams,
SendCopyParams,
} from '@mtcute/core/methods.js'
import type { UpdateContext } from './base.js' import type { UpdateContext } from './base.js'
@ -63,82 +68,82 @@ export class MessageContext extends Message implements UpdateContext<Message> {
} }
/** Get a message that this message is a reply to */ /** Get a message that this message is a reply to */
getReplyTo() { getReplyTo(): Promise<Message | null> {
return this.client.getReplyTo(this) return this.client.getReplyTo(this)
} }
/** If this is a channel post, get its automatic forward in the discussion group */ /** If this is a channel post, get its automatic forward in the discussion group */
getDiscussionMessage() { getDiscussionMessage(): Promise<Message | null> {
return this.client.getDiscussionMessage({ chatId: this.chat.inputPeer, message: this.id }) return this.client.getDiscussionMessage({ chatId: this.chat.inputPeer, message: this.id })
} }
/** Get all custom emojis contained in this message (message group), if any */ /** Get all custom emojis contained in this message (message group), if any */
getCustomEmojis() { getCustomEmojis(): Promise<Sticker[]> {
return this.client.getCustomEmojisFromMessages(this.messages) return this.client.getCustomEmojisFromMessages(this.messages)
} }
/** Send a text message to the same chat (and topic, if applicable) as a given message */ /** Send a text message to the same chat (and topic, if applicable) as a given message */
answerText(...params: ParametersSkip1<TelegramClient['answerText']>) { answerText(...params: ParametersSkip1<TelegramClient['answerText']>): Promise<Message> {
return this.client.answerText(this, ...params) return this.client.answerText(this, ...params)
} }
/** Send a media to the same chat (and topic, if applicable) as a given message */ /** Send a media to the same chat (and topic, if applicable) as a given message */
answerMedia(...params: ParametersSkip1<TelegramClient['answerMedia']>) { answerMedia(...params: ParametersSkip1<TelegramClient['answerMedia']>): Promise<Message> {
return this.client.answerMedia(this, ...params) return this.client.answerMedia(this, ...params)
} }
/** Send a media group to the same chat (and topic, if applicable) as a given message */ /** Send a media group to the same chat (and topic, if applicable) as a given message */
answerMediaGroup(...params: ParametersSkip1<TelegramClient['answerMediaGroup']>) { answerMediaGroup(...params: ParametersSkip1<TelegramClient['answerMediaGroup']>): Promise<Message[]> {
return this.client.answerMediaGroup(this, ...params) return this.client.answerMediaGroup(this, ...params)
} }
/** Send a text message in reply to this message */ /** Send a text message in reply to this message */
replyText(...params: ParametersSkip1<TelegramClient['replyText']>) { replyText(...params: ParametersSkip1<TelegramClient['replyText']>): Promise<Message> {
return this.client.replyText(this, ...params) return this.client.replyText(this, ...params)
} }
/** Send a media in reply to this message */ /** Send a media in reply to this message */
replyMedia(...params: ParametersSkip1<TelegramClient['replyMedia']>) { replyMedia(...params: ParametersSkip1<TelegramClient['replyMedia']>): Promise<Message> {
return this.client.replyMedia(this, ...params) return this.client.replyMedia(this, ...params)
} }
/** Send a media group in reply to this message */ /** Send a media group in reply to this message */
replyMediaGroup(...params: ParametersSkip1<TelegramClient['replyMediaGroup']>) { replyMediaGroup(...params: ParametersSkip1<TelegramClient['replyMediaGroup']>): Promise<Message[]> {
return this.client.replyMediaGroup(this, ...params) return this.client.replyMediaGroup(this, ...params)
} }
/** Send a text message in reply to this message */ /** Send a text message in reply to this message */
quoteWithText(params: Parameters<TelegramClient['quoteWithText']>[1]) { quoteWithText(params: Parameters<TelegramClient['quoteWithText']>[1]): Promise<Message> {
return this.client.quoteWithText(this, params) return this.client.quoteWithText(this, params)
} }
/** Send a media in reply to this message */ /** Send a media in reply to this message */
quoteWithMedia(params: Parameters<TelegramClient['quoteWithMedia']>[1]) { quoteWithMedia(params: Parameters<TelegramClient['quoteWithMedia']>[1]): Promise<Message> {
return this.client.quoteWithMedia(this, params) return this.client.quoteWithMedia(this, params)
} }
/** Send a media group in reply to this message */ /** Send a media group in reply to this message */
quoteWithMediaGroup(params: Parameters<TelegramClient['quoteWithMediaGroup']>[1]) { quoteWithMediaGroup(params: Parameters<TelegramClient['quoteWithMediaGroup']>[1]): Promise<Message[]> {
return this.client.quoteWithMediaGroup(this, params) return this.client.quoteWithMediaGroup(this, params)
} }
/** Send a text as a comment to this message */ /** Send a text as a comment to this message */
commentText(...params: ParametersSkip1<TelegramClient['commentText']>) { commentText(...params: ParametersSkip1<TelegramClient['commentText']>): Promise<Message> {
return this.client.commentText(this, ...params) return this.client.commentText(this, ...params)
} }
/** Send a media as a comment to this message */ /** Send a media as a comment to this message */
commentMedia(...params: ParametersSkip1<TelegramClient['commentMedia']>) { commentMedia(...params: ParametersSkip1<TelegramClient['commentMedia']>): Promise<Message> {
return this.client.commentMedia(this, ...params) return this.client.commentMedia(this, ...params)
} }
/** Send a media group as a comment to this message */ /** Send a media group as a comment to this message */
commentMediaGroup(...params: ParametersSkip1<TelegramClient['commentMediaGroup']>) { commentMediaGroup(...params: ParametersSkip1<TelegramClient['commentMediaGroup']>): Promise<Message[]> {
return this.client.commentMediaGroup(this, ...params) return this.client.commentMediaGroup(this, ...params)
} }
/** Delete this message (message group) */ /** Delete this message (message group) */
delete(params?: DeleteMessagesParams) { delete(params?: DeleteMessagesParams): Promise<void> {
return this.client.deleteMessagesById( return this.client.deleteMessagesById(
this.chat.inputPeer, this.chat.inputPeer,
this.messages.map(it => it.id), this.messages.map(it => it.id),
@ -147,7 +152,7 @@ export class MessageContext extends Message implements UpdateContext<Message> {
} }
/** Pin this message */ /** Pin this message */
pin(params?: OmitInputMessageId<Parameters<TelegramClient['pinMessage']>[0]>) { pin(params?: OmitInputMessageId<Parameters<TelegramClient['pinMessage']>[0]>): Promise<Message | null> {
return this.client.pinMessage({ return this.client.pinMessage({
chatId: this.chat.inputPeer, chatId: this.chat.inputPeer,
message: this.id, message: this.id,
@ -156,7 +161,7 @@ export class MessageContext extends Message implements UpdateContext<Message> {
} }
/** Unpin this message */ /** Unpin this message */
unpin() { unpin(): Promise<void> {
return this.client.unpinMessage({ return this.client.unpinMessage({
chatId: this.chat.inputPeer, chatId: this.chat.inputPeer,
message: this.id, message: this.id,
@ -164,7 +169,7 @@ export class MessageContext extends Message implements UpdateContext<Message> {
} }
/** Edit this message */ /** Edit this message */
edit(params: OmitInputMessageId<Parameters<TelegramClient['editMessage']>[0]>) { edit(params: OmitInputMessageId<Parameters<TelegramClient['editMessage']>[0]>): Promise<Message> {
return this.client.editMessage({ return this.client.editMessage({
chatId: this.chat.inputPeer, chatId: this.chat.inputPeer,
message: this.id, message: this.id,
@ -173,7 +178,7 @@ export class MessageContext extends Message implements UpdateContext<Message> {
} }
/** Forward this message (message group) */ /** Forward this message (message group) */
forwardTo(params: ForwardMessageOptions) { forwardTo(params: ForwardMessageOptions): Promise<Message[]> {
return this.client.forwardMessagesById({ return this.client.forwardMessagesById({
fromChatId: this.chat.inputPeer, fromChatId: this.chat.inputPeer,
messages: this.messages.map(it => it.id), messages: this.messages.map(it => it.id),
@ -182,7 +187,7 @@ export class MessageContext extends Message implements UpdateContext<Message> {
} }
/** Send a copy of this message (message group) */ /** Send a copy of this message (message group) */
copy(params: SendCopyParams & SendCopyGroupParams) { copy(params: SendCopyParams & SendCopyGroupParams): Promise<Message> | Promise<Message[]> {
if (this.isMessageGroup) { if (this.isMessageGroup) {
return this.client.sendCopyGroup({ return this.client.sendCopyGroup({
messages: this.messages, messages: this.messages,
@ -197,7 +202,9 @@ export class MessageContext extends Message implements UpdateContext<Message> {
} }
/** React to this message */ /** React to this message */
react(params: OmitInputMessageId<Parameters<TelegramClient['sendReaction']>[0]>) { react(
params: OmitInputMessageId<Parameters<TelegramClient['sendReaction']>[0]>,
): Promise<Message | null> {
return this.client.sendReaction({ return this.client.sendReaction({
chatId: this.chat.inputPeer, chatId: this.chat.inputPeer,
message: this.id, message: this.id,

View file

@ -1,4 +1,21 @@
import type { ParsedUpdate } from '@mtcute/core' import type {
BotReactionCountUpdate,
BotReactionUpdate,
BotStoppedUpdate,
BusinessConnection,
ChatJoinRequestUpdate,
ChatMemberUpdate,
DeleteBusinessMessageUpdate,
DeleteMessageUpdate,
DeleteStoryUpdate,
HistoryReadUpdate,
ParsedUpdate,
PollUpdate,
PollVoteUpdate,
StoryUpdate,
UserStatusUpdate,
UserTypingUpdate,
} from '@mtcute/core'
import type { TelegramClient } from '@mtcute/core/client.js' import type { TelegramClient } from '@mtcute/core/client.js'
import type { UpdateContextDistributed } from './base.js' import type { UpdateContextDistributed } from './base.js'
@ -10,8 +27,36 @@ import { InlineQueryContext } from './inline-query.js'
import { MessageContext } from './message.js' import { MessageContext } from './message.js'
import { PreCheckoutQueryContext } from './pre-checkout-query.js' import { PreCheckoutQueryContext } from './pre-checkout-query.js'
export type UpdateContextType =
| MessageContext
| InlineQueryContext
| ChosenInlineResultContext
| CallbackQueryContext
| InlineCallbackQueryContext
| BusinessCallbackQueryContext
| ChatJoinRequestUpdateContext
| PreCheckoutQueryContext
| BusinessMessageContext
| UpdateContextDistributed<
| DeleteMessageUpdate
| ChatMemberUpdate
| PollUpdate
| PollVoteUpdate
| UserStatusUpdate
| UserTypingUpdate
| HistoryReadUpdate
| BotStoppedUpdate
| ChatJoinRequestUpdate
| StoryUpdate
| DeleteStoryUpdate
| BotReactionUpdate
| BotReactionCountUpdate
| BusinessConnection
| DeleteBusinessMessageUpdate
>
/** @internal */ /** @internal */
export function _parsedUpdateToContext(client: TelegramClient, update: ParsedUpdate) { export function _parsedUpdateToContext(client: TelegramClient, update: ParsedUpdate): UpdateContextType {
switch (update.name) { switch (update.name) {
case 'new_message': case 'new_message':
case 'edit_message': case 'edit_message':
@ -43,5 +88,3 @@ export function _parsedUpdateToContext(client: TelegramClient, update: ParsedUpd
return _update return _update
} }
export type UpdateContextType = ReturnType<typeof _parsedUpdateToContext>

View file

@ -1,11 +1,11 @@
import type { MaybeArray, MaybePromise, Message } from '@mtcute/core' import type { Chat, MaybeArray, MaybePromise, Message } from '@mtcute/core'
import type { BusinessMessageContext } from '../context/business-message.js' import type { BusinessMessageContext } from '../context/business-message.js'
import type { MessageContext } from '../context/message.js' import type { MessageContext } from '../context/message.js'
import { chat } from './chat.js' import { chat } from './chat.js'
import { and, or } from './logic.js' import { and, or } from './logic.js'
import type { UpdateFilter } from './types.js' import type { Modify, UpdateFilter } from './types.js'
/** /**
* Filter messages that call the given command(s).. * Filter messages that call the given command(s)..
@ -97,13 +97,30 @@ export function command(commands: MaybeArray<string | RegExp>, {
* Shorthand filter that matches /start commands sent to bot's * Shorthand filter that matches /start commands sent to bot's
* private messages. * private messages.
*/ */
export const start = and(chat('private'), command('start')) export const start: UpdateFilter<
MessageContext | BusinessMessageContext,
{
chat: Modify<Chat, {
chatType: 'private'
}>
command: string[]
}
> = and(chat('private'), command('start'))
/** /**
* Shorthand filter that matches /start commands * Shorthand filter that matches /start commands
* sent in groups (i.e. using `?startgroup` parameter). * sent in groups (i.e. using `?startgroup` parameter).
*/ */
export const startGroup = and(or(chat('supergroup'), chat('group')), command('start')) export const startGroup: UpdateFilter<
MessageContext | BusinessMessageContext,
{
chat: Modify<Chat, {
chatType: 'group' | 'supergroup'
}>
command: string[]
},
never
> = and(or(chat('supergroup'), chat('group')), command('start'))
function deeplinkBase(base: UpdateFilter<MessageContext | BusinessMessageContext, { command: string[] }>) { function deeplinkBase(base: UpdateFilter<MessageContext | BusinessMessageContext, { command: string[] }>) {
return ( return (
@ -156,7 +173,10 @@ function deeplinkBase(base: UpdateFilter<MessageContext | BusinessMessageContext
* If the parameter is a regex, groups are added to `msg.command`, * If the parameter is a regex, groups are added to `msg.command`,
* meaning that the first group is available in `msg.command[2]`. * meaning that the first group is available in `msg.command[2]`.
*/ */
export const deeplink = deeplinkBase(start) export const deeplink: (params: MaybeArray<string | RegExp>) => UpdateFilter<
MessageContext | BusinessMessageContext,
{ command: string[] }
> = deeplinkBase(start)
/** /**
* Filter for group deep links (i.e. `/start <deeplink_parameter>`). * Filter for group deep links (i.e. `/start <deeplink_parameter>`).
@ -164,4 +184,7 @@ export const deeplink = deeplinkBase(start)
* If the parameter is a regex, groups are added to `msg.command`, * If the parameter is a regex, groups are added to `msg.command`,
* meaning that the first group is available in `msg.command[2]`. * meaning that the first group is available in `msg.command[2]`.
*/ */
export const deeplinkGroup = deeplinkBase(startGroup) export const deeplinkGroup: (params: MaybeArray<string | RegExp>) => UpdateFilter<
MessageContext | BusinessMessageContext,
{ command: string[] }
> = deeplinkBase(startGroup)

View file

@ -1,16 +1,29 @@
import type { import type {
Audio,
Contact,
Dice,
Document,
Game,
Invoice,
LiveLocation,
Location,
MaybeArray, MaybeArray,
Message, Message,
MessageAction, MessageAction,
MessageMediaType, MessageMediaType,
Peer, Peer,
Photo,
Poll,
RepliedMessageInfo, RepliedMessageInfo,
RepliedMessageOrigin, RepliedMessageOrigin,
Sticker, Sticker,
StickerSourceType, StickerSourceType,
StickerType, StickerType,
User, User,
Venue,
Video, Video,
Voice,
WebPage,
_RepliedMessageAssertionsByOrigin, _RepliedMessageAssertionsByOrigin,
} from '@mtcute/core' } from '@mtcute/core'
import { import {
@ -68,41 +81,44 @@ export const media: UpdateFilter<Message, { media: Exclude<Message['media'], nul
/** /**
* Filter messages containing media of given type * Filter messages containing media of given type
*/ */
export function mediaOf<T extends MessageMediaType>(type: T): UpdateFilter<Message, { media: Extract<Message['media'], { type: T }> }> { export function mediaOf<T extends MessageMediaType>(type: T): UpdateFilter<
Message,
{ media: Extract<Message['media'], { type: T }> }
> {
return msg => return msg =>
msg.media?.type === type msg.media?.type === type
} }
/** Filter messages containing a photo */ /** Filter messages containing a photo */
export const photo = mediaOf('photo') export const photo: UpdateFilter<Message, { media: Photo }> = mediaOf('photo')
/** Filter messages containing a dice */ /** Filter messages containing a dice */
export const dice = mediaOf('dice') export const dice: UpdateFilter<Message, { media: Dice }> = mediaOf('dice')
/** Filter messages containing a contact */ /** Filter messages containing a contact */
export const contact = mediaOf('contact') export const contact: UpdateFilter<Message, { media: Contact }> = mediaOf('contact')
/** Filter messages containing an audio file */ /** Filter messages containing an audio file */
export const audio = mediaOf('audio') export const audio: UpdateFilter<Message, { media: Audio }> = mediaOf('audio')
/** Filter messages containing a voice message (audio-only) */ /** Filter messages containing a voice message (audio-only) */
export const voice = mediaOf('voice') export const voice: UpdateFilter<Message, { media: Voice }> = mediaOf('voice')
/** Filter messages containing a sticker */ /** Filter messages containing a sticker */
export const sticker = mediaOf('sticker') export const sticker: UpdateFilter<Message, { media: Sticker }> = mediaOf('sticker')
/** Filter messages containing a document (a file) */ /** Filter messages containing a document (a file) */
export const document = mediaOf('document') export const document: UpdateFilter<Message, { media: Document }> = mediaOf('document')
/** Filter messages containing any video (videos, round messages and animations) */ /** Filter messages containing any video (videos, round messages and animations) */
export const anyVideo = mediaOf('video') export const anyVideo: UpdateFilter<Message, { media: Video }> = mediaOf('video')
/** Filter messages containing a static location */ /** Filter messages containing a static location */
export const location = mediaOf('location') export const location: UpdateFilter<Message, { media: Location }> = mediaOf('location')
/** Filter messages containing a live location */ /** Filter messages containing a live location */
export const liveLocation = mediaOf('live_location') export const liveLocation: UpdateFilter<Message, { media: LiveLocation }> = mediaOf('live_location')
/** Filter messages containing a game */ /** Filter messages containing a game */
export const game = mediaOf('game') export const game: UpdateFilter<Message, { media: Game }> = mediaOf('game')
/** Filter messages containing a web page */ /** Filter messages containing a web page */
export const webpage = mediaOf('webpage') export const webpage: UpdateFilter<Message, { media: WebPage }> = mediaOf('webpage')
/** Filter messages containing a venue */ /** Filter messages containing a venue */
export const venue = mediaOf('venue') export const venue: UpdateFilter<Message, { media: Venue }> = mediaOf('venue')
/** Filter messages containing a poll */ /** Filter messages containing a poll */
export const poll = mediaOf('poll') export const poll: UpdateFilter<Message, { media: Poll }> = mediaOf('poll')
/** Filter messages containing an invoice */ /** Filter messages containing an invoice */
export const invoice = mediaOf('invoice') export const invoice: UpdateFilter<Message, { media: Invoice }> = mediaOf('invoice')
/** /**
* Filter messages containing any location (live or static). * Filter messages containing any location (live or static).
@ -224,7 +240,10 @@ export function action<T extends Exclude<MessageAction, null>['type']>(type: May
return msg => msg.action?.type === type return msg => msg.action?.type === type
} }
export function sender<T extends Message['sender']['type']>(type: T): UpdateFilter<Message, { sender: Extract<Message['sender'], { type: T }> }> { export function sender<T extends Message['sender']['type']>(type: T): UpdateFilter<
Message,
{ sender: Extract<Message['sender'], { type: T }> }
> {
return msg => return msg =>
msg.sender.type === type msg.sender.type === type
} }
@ -235,7 +254,13 @@ export function sender<T extends Message['sender']['type']>(type: T): UpdateFilt
* *
* Optionally, you can pass a filter that will be applied to the replied message. * Optionally, you can pass a filter that will be applied to the replied message.
*/ */
export function replyTo<Mod, State extends object>(filter?: UpdateFilter<Message, Mod, State>): UpdateFilter<MessageContext | BusinessMessageContext, { getReplyTo: () => Promise<Message & Mod> }, State> { export function replyTo<Mod, State extends object>(
filter?: UpdateFilter<Message, Mod, State>,
): UpdateFilter<
MessageContext | BusinessMessageContext,
{ getReplyTo: () => Promise<Message & Mod> },
State
> {
return async (msg, state) => { return async (msg, state) => {
if (!msg.replyToMessage?.id) return false if (!msg.replyToMessage?.id) return false
@ -256,7 +281,9 @@ export function replyTo<Mod, State extends object>(filter?: UpdateFilter<Message
* Middleware-like filter that will fetch the sender of the message * Middleware-like filter that will fetch the sender of the message
* and make it available to further filters, as well as the handler itself. * and make it available to further filters, as well as the handler itself.
*/ */
export function withCompleteSender<Mod, State extends object>(filter?: UpdateFilter<MessageContext, Mod, State>): UpdateFilter<MessageContext, Mod, State> { export function withCompleteSender<Mod, State extends object>(
filter?: UpdateFilter<MessageContext, Mod, State>,
): UpdateFilter<MessageContext, Mod, State> {
return async (msg, state) => { return async (msg, state) => {
try { try {
await msg.getCompleteSender() await msg.getCompleteSender()

View file

@ -15,8 +15,8 @@ interface RateLimitDto {
} }
class MemoryStateRepository implements IStateRepository { class MemoryStateRepository implements IStateRepository {
readonly state readonly state: Map<string, StateDto>
readonly rl readonly rl: Map<string, RateLimitDto>
constructor(readonly _driver: MemoryStorageDriver) { constructor(readonly _driver: MemoryStorageDriver) {
this.state = this._driver.getState<Map<string, StateDto>>('dispatcher_fsm', () => new Map()) this.state = this._driver.getState<Map<string, StateDto>>('dispatcher_fsm', () => new Map())
this.rl = this._driver.getState<Map<string, RateLimitDto>>('rl', () => new Map()) this.rl = this._driver.getState<Map<string, RateLimitDto>>('rl', () => new Map())
@ -99,7 +99,7 @@ class MemoryStateRepository implements IStateRepository {
} }
export class MemoryStateStorage implements IStateStorageProvider { export class MemoryStateStorage implements IStateStorageProvider {
readonly state readonly state: MemoryStateRepository
constructor(readonly driver: MemoryStorageDriver = new MemoryStorageDriver()) { constructor(readonly driver: MemoryStorageDriver = new MemoryStorageDriver()) {
this.state = new MemoryStateRepository(this.driver) this.state = new MemoryStateRepository(this.driver)

View file

@ -116,12 +116,12 @@ class SqliteStateRepository implements IStateRepository {
} }
export class SqliteStateStorage implements IStateStorageProvider { export class SqliteStateStorage implements IStateStorageProvider {
readonly state readonly state: SqliteStateRepository
constructor(readonly driver: BaseSqliteStorageDriver) { constructor(readonly driver: BaseSqliteStorageDriver) {
this.state = new SqliteStateRepository(driver) this.state = new SqliteStateRepository(driver)
} }
static from(provider: BaseSqliteStorage) { static from(provider: BaseSqliteStorage): SqliteStateStorage {
return new SqliteStateStorage(provider.driver) return new SqliteStateStorage(provider.driver)
} }
} }

View file

@ -1,4 +1,5 @@
import { LruMap, asyncResettable } from '@mtcute/core/utils.js' import { LruMap, asyncResettable } from '@mtcute/core/utils.js'
import type { MaybePromise } from '@mtcute/core'
import type { IStateStorageProvider } from './provider.js' import type { IStateStorageProvider } from './provider.js'
@ -16,14 +17,14 @@ export class StateService {
this._loaded = true this._loaded = true
}) })
async load() { async load(): Promise<void> {
await this._load.run() await this._load.run()
this._vacuumTimer = setInterval(() => { this._vacuumTimer = setInterval(() => {
Promise.resolve(this.provider.state.vacuum(Date.now())).catch(() => {}) Promise.resolve(this.provider.state.vacuum(Date.now())).catch(() => {})
}, 300_000) }, 300_000)
} }
async destroy() { async destroy(): Promise<void> {
await this.provider.driver.save?.() await this.provider.driver.save?.()
await this.provider.driver.destroy?.() await this.provider.driver.destroy?.()
clearInterval(this._vacuumTimer) clearInterval(this._vacuumTimer)
@ -68,11 +69,11 @@ export class StateService {
return this.deleteState(makeCurrentSceneKey(key)) return this.deleteState(makeCurrentSceneKey(key))
} }
getRateLimit(key: string, limit: number, window: number) { getRateLimit(key: string, limit: number, window: number): MaybePromise<[number, number]> {
return this.provider.state.getRateLimit(key, Date.now(), limit, window) return this.provider.state.getRateLimit(key, Date.now(), limit, window)
} }
resetRateLimit(key: string) { resetRateLimit(key: string): MaybePromise<void> {
return this.provider.state.resetRateLimit(key) return this.provider.state.resetRateLimit(key)
} }
} }

View file

@ -3,6 +3,7 @@ import type { MaybePromise } from '@mtcute/core'
import type { MessageContext } from './context/message.js' import type { MessageContext } from './context/message.js'
import type { DispatcherParams } from './dispatcher.js' import type { DispatcherParams } from './dispatcher.js'
import { Dispatcher } from './dispatcher.js' import { Dispatcher } from './dispatcher.js'
import type { UpdateFilter } from './filters/index.js'
import { filters } from './filters/index.js' import { filters } from './filters/index.js'
import type { UpdateState } from './state/update-state.js' import type { UpdateState } from './state/update-state.js'
@ -61,7 +62,7 @@ export class WizardScene<State extends object> extends Dispatcher<State & Wizard
/** /**
* Go to the Nth step * Go to the Nth step
*/ */
async goToStep(state: UpdateState<WizardInternalState>, step: number) { async goToStep(state: UpdateState<WizardInternalState>, step: number): Promise<void> {
if (step >= this._steps) { if (step >= this._steps) {
await state.exit() await state.exit()
} else { } else {
@ -72,7 +73,7 @@ export class WizardScene<State extends object> extends Dispatcher<State & Wizard
/** /**
* Skip N steps * Skip N steps
*/ */
async skip(state: UpdateState<WizardInternalState>, count = 1) { async skip(state: UpdateState<WizardInternalState>, count = 1): Promise<void> {
const { $step } = (await state.get()) || {} const { $step } = (await state.get()) || {}
if ($step === undefined) throw new Error('Wizard state is not initialized') if ($step === undefined) throw new Error('Wizard state is not initialized')
@ -82,7 +83,8 @@ export class WizardScene<State extends object> extends Dispatcher<State & Wizard
/** /**
* Filter that will only pass if the current step is `step` * Filter that will only pass if the current step is `step`
*/ */
static onNthStep(step: number) { // eslint-disable-next-line ts/no-empty-object-type
static onNthStep(step: number): UpdateFilter<any, {}, WizardInternalState> {
const filter = filters.state<WizardInternalState>(it => it.$step === step) const filter = filters.state<WizardInternalState>(it => it.$step === step)
if (step === 0) return filters.or(filters.stateEmpty, filter) if (step === 0) return filters.or(filters.stateEmpty, filter)
@ -93,7 +95,8 @@ export class WizardScene<State extends object> extends Dispatcher<State & Wizard
/** /**
* Filter that will only pass if the current step is the one after last one added * Filter that will only pass if the current step is the one after last one added
*/ */
onCurrentStep() { // eslint-disable-next-line ts/no-empty-object-type
onCurrentStep(): UpdateFilter<any, {}, WizardInternalState> {
return WizardScene.onNthStep(this._steps) return WizardScene.onNthStep(this._steps)
} }

View file

@ -3,8 +3,8 @@ import type Long from 'long'
export const PERSISTENT_ID_VERSION_OLD = 2 export const PERSISTENT_ID_VERSION_OLD = 2
export const PERSISTENT_ID_VERSION = 4 export const PERSISTENT_ID_VERSION = 4
export const WEB_LOCATION_FLAG = 1 << 24 export const WEB_LOCATION_FLAG: number = 1 << 24
export const FILE_REFERENCE_FLAG = 1 << 25 export const FILE_REFERENCE_FLAG: number = 1 << 25
export const CURRENT_VERSION = 48 export const CURRENT_VERSION = 48

View file

@ -160,5 +160,5 @@ export abstract class BaseHttpProxyTcpTransport extends BaseTcpTransport {
* (unless you want to use a custom codec). * (unless you want to use a custom codec).
*/ */
export class HttpProxyTcpTransport extends BaseHttpProxyTcpTransport { export class HttpProxyTcpTransport extends BaseHttpProxyTcpTransport {
_packetCodec = new IntermediatePacketCodec() _packetCodec: IntermediatePacketCodec = new IntermediatePacketCodec()
} }

View file

@ -277,7 +277,7 @@ export async function generateFakeTlsHeader(domain: string, secret: Buffer, cryp
* @internal * @internal
*/ */
export class FakeTlsPacketCodec extends WrappedCodec implements IPacketCodec { export class FakeTlsPacketCodec extends WrappedCodec implements IPacketCodec {
protected _stream = Buffer.alloc(0) protected _stream: Buffer = Buffer.alloc(0)
private _header!: Buffer private _header!: Buffer
private _isFirstTls = true private _isFirstTls = true

View file

@ -1,5 +1,6 @@
import type { Interface as RlInterface } from 'node:readline' import type { Interface as RlInterface } from 'node:readline'
import { createInterface } from 'node:readline' import { createInterface } from 'node:readline'
import type { Readable } from 'node:stream'
import type { FileDownloadLocation, FileDownloadParameters, ITelegramStorageProvider, PartialOnly, User } from '@mtcute/core' import type { FileDownloadLocation, FileDownloadParameters, ITelegramStorageProvider, PartialOnly, User } from '@mtcute/core'
import type { import type {
@ -157,7 +158,7 @@ export class TelegramClient extends TelegramClientBase {
return downloadToFile(this, filename, location, params) return downloadToFile(this, filename, location, params)
} }
downloadAsNodeStream(location: FileDownloadLocation, params?: FileDownloadParameters | undefined) { downloadAsNodeStream(location: FileDownloadLocation, params?: FileDownloadParameters | undefined): Readable {
return downloadAsNodeStream(this, location, params) return downloadAsNodeStream(this, location, params)
} }
} }

View file

@ -21,7 +21,13 @@ const LEVEL_NAMES = isTty
const TAG_COLORS = [6, 2, 3, 4, 5, 1].map(i => `\x1B[3${i};1m`) const TAG_COLORS = [6, 2, 3, 4, 5, 1].map(i => `\x1B[3${i};1m`)
/** @internal */ /** @internal */
export const defaultLoggingHandler = isTty export const defaultLoggingHandler: (
color: number,
level: number,
tag: string,
fmt: string,
args: unknown[]
) => void = isTty
? (color: number, level: number, tag: string, fmt: string, args: unknown[]): void => { ? (color: number, level: number, tag: string, fmt: string, args: unknown[]): void => {
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
console.log(BASE_FORMAT + fmt, new Date().toISOString(), LEVEL_NAMES[level], TAG_COLORS[color], tag, ...args) console.log(BASE_FORMAT + fmt, new Date().toISOString(), LEVEL_NAMES[level], TAG_COLORS[color], tag, ...args)

View file

@ -25,7 +25,7 @@ export interface SqliteStorageDriverOptions {
export class SqliteStorageDriver extends BaseSqliteStorageDriver { export class SqliteStorageDriver extends BaseSqliteStorageDriver {
constructor( constructor(
readonly filename = ':memory:', readonly filename = ':memory:',
readonly params?: SqliteStorageDriverOptions, readonly params?: SqliteStorageDriverOptions | undefined,
) { ) {
super() super()
} }

View file

@ -8,7 +8,7 @@ export { SqliteStorageDriver } from './driver.js'
export class SqliteStorage extends BaseSqliteStorage { export class SqliteStorage extends BaseSqliteStorage {
constructor( constructor(
readonly filename = ':memory:', readonly filename = ':memory:',
readonly params?: SqliteStorageDriverOptions, readonly params?: SqliteStorageDriverOptions | undefined,
) { ) {
super(new SqliteStorageDriver(filename, params)) super(new SqliteStorageDriver(filename, params))
} }

View file

@ -63,7 +63,7 @@ export abstract class BaseNodeCryptoProvider extends BaseCryptoProvider {
return gunzipSync(data) return gunzipSync(data)
} }
randomFill(buf: Uint8Array) { randomFill(buf: Uint8Array): void {
randomFillSync(buf) randomFillSync(buf)
} }
} }

View file

@ -7,7 +7,11 @@ import type { UploadFileLike } from '@mtcute/core'
import { nodeStreamToWeb } from './stream-utils.js' import { nodeStreamToWeb } from './stream-utils.js'
export async function normalizeFile(file: UploadFileLike) { export async function normalizeFile(file: UploadFileLike): Promise<{
file: UploadFileLike
fileName?: string | undefined
fileSize?: number
} | null> {
if (typeof file === 'string') { if (typeof file === 'string') {
file = createReadStream(file) file = createReadStream(file)
} }

View file

@ -136,5 +136,5 @@ export abstract class BaseTcpTransport extends EventEmitter implements ITelegram
} }
export class TcpTransport extends BaseTcpTransport { export class TcpTransport extends BaseTcpTransport {
_packetCodec = new IntermediatePacketCodec() _packetCodec: IntermediatePacketCodec = new IntermediatePacketCodec()
} }

Some files were not shown because too many files have changed in this diff Show more