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', () => {
const buf = p.hexDecode('4ca5e8dd7b00000000000000c801000000000000')
// eslint-disable-next-line
const obj = TlBinaryReader.deserializeObject<any>(__tlReaderMap, buf)
expect(obj._).equal('inputPeerUser')
expect(obj.userId).equal(123)
// eslint-disable-next-line
expect(obj.accessHash.toString()).equal('456')
})

View file

@ -44,6 +44,12 @@ export default antfu({
SwitchCase: 1,
VariableDeclarator: 1,
}],
'style/max-len': ['error', {
code: 120,
ignoreComments: true,
ignoreStrings: true,
ignoreTemplateLiterals: true,
}],
'curly': ['error', 'multi-line'],
'style/brace-style': ['error', '1tbs', { allowSingleLine: true }],
'node/prefer-global/process': ['error', 'always'],
@ -60,7 +66,7 @@ export default antfu({
'ts/no-redeclare': 'off',
'eslint-comments/no-unlimited-disable': '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-restricted-syntax': 'off',
'unicorn/no-new-array': 'off',

View file

@ -1,5 +1,6 @@
import type { Interface as RlInterface } 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 {
@ -145,7 +146,7 @@ export class TelegramClient extends TelegramClientBase {
return downloadToFile(this, filename, location, params)
}
downloadAsNodeStream(location: FileDownloadLocation, params?: FileDownloadParameters | undefined) {
downloadAsNodeStream(location: FileDownloadLocation, params?: FileDownloadParameters | undefined): Readable {
return downloadAsNodeStream(this, location, params)
}
}

View file

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

View file

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

View file

@ -112,7 +112,7 @@ export class BunCryptoProvider extends BaseCryptoProvider implements ICryptoProv
return gunzip(data)
}
randomFill(buf: Uint8Array) {
randomFill(buf: Uint8Array): void {
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
}
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') {
file = Bun.file(file)
}

View file

@ -149,5 +149,5 @@ export abstract class BaseTcpTransport extends EventEmitter implements ITelegram
}
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')
}
export function serializeIpv4ToBytes(ip: string, buf: Uint8Array) {
export function serializeIpv4ToBytes(ip: string, buf: Uint8Array): void {
const parts = ip.split('.')
if (parts.length !== 4) {
@ -33,7 +33,7 @@ export function serializeIpv4ToBytes(ip: string, buf: Uint8Array) {
buf[3] = Number(parts[3])
}
export function serializeIpv6ToBytes(ip: string, buf: Uint8Array) {
export function serializeIpv6ToBytes(ip: string, buf: Uint8Array): void {
const parts = ip.split(':')
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 () => {
await this.mt.prepare()
@ -115,7 +115,7 @@ export class BaseTelegramClient implements ITelegramClient {
*
* Call {@link connect} to actually connect.
*/
prepare() {
prepare(): Promise<void> {
return this._prepare.run()
}
@ -327,7 +327,10 @@ export class BaseTelegramClient implements ITelegramClient {
this._connectionStateHandler = handler
}
async getApiCrenetials() {
async getApiCrenetials(): Promise<{
id: number
hash: string
}> {
return {
id: this.params.apiId,
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
// eslint-disable-next-line ts/no-explicit-any
on(name: string, handler: (...args: any[]) => void): this
/**

View file

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

View file

@ -156,7 +156,13 @@ export async function _processCommonSendParameters(
client: ITelegramClient,
chatId: InputPeerLike,
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 replyTo = normalizeMessageId(params.replyTo)

View file

@ -62,7 +62,7 @@ export class PeersService extends BaseService {
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
for (const peer of getAllPeersFrom(obj)) {

View file

@ -23,9 +23,9 @@ export interface TelegramStorageManagerExtraOptions {
export class TelegramStorageManager {
private provider
readonly updates
readonly updates: UpdatesStateService
readonly self: PublicPart<CurrentUserService>
readonly refMsgs
readonly refMsgs: RefMessagesService
readonly peers: PublicPart<PeersService>
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.refMessages.deleteAll()
await this.mt.clear(withAuthKeys)

View file

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

View file

@ -24,7 +24,7 @@ export class Invoice {
constructor(
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(
readonly raw: tl.RawPhoto,
readonly media?: tl.RawMessageMediaPhoto,
readonly media?: tl.RawMessageMediaPhoto | undefined,
) {
const location = {
_: 'inputPhotoFileLocation',

View file

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

View file

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

View file

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

View file

@ -13,7 +13,10 @@ export type InputMessageId = { chatId: InputPeerLike, message: number } | { mess
export type OmitInputMessageId<T> = Omit<T, 'chatId' | 'message'>
/** @internal */
export function normalizeInputMessageId(id: InputMessageId) {
export function normalizeInputMessageId(id: InputMessageId): {
chatId: InputPeerLike
message: number
} {
if ('chatId' in id) return 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
*/
get offset() {
get offset(): number {
return this.raw.offset
}
@ -76,7 +76,7 @@ export class MessageEntity {
*
* Since JS strings are UTF-16, you can use this as-is
*/
get length() {
get length(): number {
return this.raw.length
}

View file

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

View file

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

View file

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

View file

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

View file

@ -18,7 +18,7 @@ import { normalizeInputReaction } from '../../reactions/index.js'
export class StoryElement {
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({
_: 'mediaAreaCoordinates',
x: params.x,

View file

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

View file

@ -60,7 +60,7 @@ export function encodeInlineMessageId(id: tl.TypeInputBotInlineMessageID): strin
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') {
return parseInlineMessageId(id)
}

View file

@ -4,7 +4,7 @@ import { assertNever } from '../../types/utils.js'
import { MtInvalidPeerTypeError } from '../types/errors.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
// 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.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 Reject = (err?: unknown) => void
type WaitersMap<K, U, T> = Map<K, [T, Resolve<U | null>, Reject][]>
interface InternalState<K, U, T> {
waiters: WaitersMap<K, U, T>
fetchingKeys: Set<K>
type WaitersMap<U, T> = Map<string | number, [T, Resolve<U | null>, Reject][]>
interface InternalState<U, T> {
waiters: WaitersMap<U, T>
fetchingKeys: Set<string | number>
retryQueue: Deque<T>
numRunning: number
}
export type BatchedQuery<T, U> = (client: ITelegramClient, item: T) => Promise<U | null>
// 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.
* 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.
*
@ -33,9 +34,9 @@ export function batchedQuery<T, U, K extends string | number>(params: {
fetch: (client: ITelegramClient, items: T[]) => Promise<U[]>
/** 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 */
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.
@ -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`).
*/
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 symbol = Symbol('batchedQueryState')
function getState(client_: ITelegramClient) {
const client = client_ as { [symbol]?: InternalState<K, U, T> }
const client = client_ as { [symbol]?: InternalState<U, T> }
if (!client[symbol]) {
client[symbol] = {
@ -84,7 +85,7 @@ export function batchedQuery<T, U, K extends string | number>(params: {
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)
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)
if (!arr) return []
@ -108,19 +109,19 @@ export function batchedQuery<T, U, K extends string | number>(params: {
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++) {
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
if (state.numRunning >= maxConcurrent) return
const request: T[] = []
const requestKeys: K[] = []
const requestKeys: (string | number)[] = []
let isRetryRequest = false
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
fetch(client, request)
.then((res) => {
const receivedKeys = new Set<K>()
const receivedKeys = new Set()
for (const it of res) {
const key = outputKey(it, client)

View file

@ -24,7 +24,10 @@ export async function streamToBuffer(stream: ReadableStream<Uint8Array>): Promis
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 lock = new AsyncLock()

View file

@ -5,7 +5,7 @@ import type { WorkerInvoker } from './invoker.js'
export class AppConfigManagerProxy implements PublicPart<AppConfigManager> {
readonly get: AppConfigManager['get']
readonly getField
readonly getField: AppConfigManager['getField']
constructor(readonly invoker: WorkerInvoker) {
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>
}
handleResult(msg: Extract<WorkerOutboundMessage, { type: 'result' }>) {
handleResult(msg: Extract<WorkerOutboundMessage, { type: 'result' }>): void {
const promise = this._pending.get(msg.id)
if (!promise) return

View file

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

View file

@ -60,12 +60,12 @@ class CurrentUserServiceProxy implements PublicPart<CurrentUserService> {
}
class PeersServiceProxy implements PublicPart<PeersService> {
readonly updatePeersFrom
readonly store
readonly getById
readonly getByPhone
readonly getByUsername
readonly getCompleteById
readonly updatePeersFrom: PeersService['updatePeersFrom']
readonly store: PeersService['store']
readonly getById: PeersService['getById']
readonly getByPhone: PeersService['getByPhone']
readonly getByUsername: PeersService['getByUsername']
readonly getCompleteById: PeersService['getCompleteById']
constructor(private _invoker: WorkerInvoker) {
const bind = this._invoker.makeBinder<PeersService>('storage-peers')
@ -80,10 +80,10 @@ class PeersServiceProxy implements PublicPart<PeersService> {
}
export class TelegramStorageProxy implements PublicPart<TelegramStorageManager> {
readonly self
readonly peers
readonly self: CurrentUserServiceProxy
readonly peers: PeersServiceProxy
readonly clear
readonly clear: TelegramStorageManager['clear']
constructor(private _invoker: WorkerInvoker) {
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
readonly pendingAborts = new Map<number, AbortController>()
readonly pendingAborts: Map<number, AbortController> = new Map()
constructor(readonly params: TelegramWorkerOptions<T>) {
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
// const DH_SAFETY_RANGE = bigInt[2].pow(2048 - 64)
const DH_SAFETY_RANGE = 2n ** (2048n - 64n)
const KNOWN_DH_PRIME
= 0xC71CAEB9C6B1C9048E6C522F70F13F73980D40238E3E21C14934D037563D930F48198A0AA7C14058229493D22530F4DBFA336F6E0AC925139543AED44CCE7C3720FD51F69458705AC68CD4FE6B6B13ABDC9746512969328454F18FAF8C595F642477FE96BB2A941D5BCD1D4AC8CC49880708FA9B378E3C4F3A9060BEE67CF9A4A4A695811051907E162753B56B0F6B410DBA74D8A84B2A14B3144E0EF1284754FD17ED950D5965B4B9DD46582DB1178D169C6BC465B0D6FF9CA3928FEF5B9AE4E418FC15E83EBEA0F87FA9FF5EED70050DED2849F47BF959D956850CE929851F0D8115F635B105EE2E4E15D04B2454BF6F4FADF034B10403119CD8E3B92FCC5Bn
// eslint-disable-next-line style/max-len
const KNOWN_DH_PRIME = 0xC71CAEB9C6B1C9048E6C522F70F13F73980D40238E3E21C14934D037563D930F48198A0AA7C14058229493D22530F4DBFA336F6E0AC925139543AED44CCE7C3720FD51F69458705AC68CD4FE6B6B13ABDC9746512969328454F18FAF8C595F642477FE96BB2A941D5BCD1D4AC8CC49880708FA9B378E3C4F3A9060BEE67CF9A4A4A695811051907E162753B56B0F6B410DBA74D8A84B2A14B3144E0EF1284754FD17ED950D5965B4B9DD46582DB1178D169C6BC465B0D6FF9CA3928FEF5B9AE4E418FC15E83EBEA0F87FA9FF5EED70050DED2849F47BF959D956850CE929851F0D8115F635B105EE2E4E15D04B2454BF6F4FADF034B10403119CD8E3B92FCC5Bn
const TWO_POW_2047 = 2n ** 2047n
const TWO_POW_2048 = 2n ** 2048n

View file

@ -204,7 +204,7 @@ export class MtClient extends EventEmitter {
/** TL writers map used by the client */
readonly _writerMap: TlWriterMap
readonly _config = new ConfigManager(async () => {
readonly _config: ConfigManager = new ConfigManager(async () => {
const res = await this.call({ _: 'help.getConfig' })
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.
*/
prepare() {
prepare(): Promise<void> {
return this._prepare.run()
}

View file

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

View file

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

View file

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

View file

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

View file

@ -259,7 +259,7 @@ export class SessionConnection extends PersistentConnection {
this.emit('error', error)
}
protected onConnectionUsable() {
protected onConnectionUsable(): void {
super.onConnectionUsable()
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
// before dropping the connection
// additionally, if we are still waiting for some rpc results,

View file

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

View file

@ -9,7 +9,7 @@ import { concatBuffers } from '../../utils/index.js'
* multiple transport packets.
*/
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

View file

@ -11,5 +11,5 @@ export class MemoryStorageDriver implements IStorageDriver {
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.
*/
export class MemoryStorage implements IMtStorageProvider, ITelegramStorageProvider {
readonly driver = new MemoryStorageDriver()
readonly kv = new MemoryKeyValueRepository(this.driver)
readonly authKeys = new MemoryAuthKeysRepository(this.driver)
readonly peers = new MemoryPeersRepository(this.driver)
readonly refMessages = new MemoryRefMessagesRepository(this.driver)
readonly driver: MemoryStorageDriver = new MemoryStorageDriver()
readonly kv: MemoryKeyValueRepository = new MemoryKeyValueRepository(this.driver)
readonly authKeys: MemoryAuthKeysRepository = new MemoryAuthKeysRepository(this.driver)
readonly peers: MemoryPeersRepository = new MemoryPeersRepository(this.driver)
readonly refMessages: MemoryRefMessagesRepository = new MemoryRefMessagesRepository(this.driver)
}

View file

@ -8,9 +8,9 @@ interface AuthKeysState {
}
export class MemoryAuthKeysRepository implements IAuthKeysRepository {
readonly state
readonly state: AuthKeysState
constructor(readonly _driver: MemoryStorageDriver) {
this.state = this._driver.getState<AuthKeysState>('authKeys', () => ({
this.state = this._driver.getState('authKeys', () => ({
authKeys: new Map(),
authKeysTemp: 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'
export class MemoryKeyValueRepository implements IKeyValueRepository {
readonly state
readonly state: Map<string, Uint8Array>
constructor(readonly _driver: MemoryStorageDriver) {
this.state = this._driver.getState<Map<string, Uint8Array>>('kv', () => new Map())
}

View file

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

View file

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

View file

@ -11,10 +11,10 @@ export { BaseSqliteStorageDriver }
export * from './types.js'
export class BaseSqliteStorage implements IMtStorageProvider, ITelegramStorageProvider {
readonly authKeys
readonly kv
readonly refMessages
readonly peers
readonly authKeys: SqliteAuthKeysRepository
readonly kv: SqliteKeyValueRepository
readonly refMessages: SqliteRefMessagesRepository
readonly peers: SqlitePeersRepository
constructor(readonly driver: BaseSqliteStorageDriver) {
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 { DefaultDcsService } from './service/default-dcs.js'
import { FutureSaltsService } from './service/future-salts.js'
import type { IStorageDriver } from './driver.js'
interface StorageManagerOptions {
provider: IMtStorageProvider
@ -41,12 +42,12 @@ export interface StorageManagerExtraOptions {
}
export class StorageManager {
readonly provider
readonly driver
readonly log
readonly dcs
readonly salts
readonly keys
readonly provider: IMtStorageProvider
readonly driver: IStorageDriver
readonly log: Logger
readonly dcs: DefaultDcsService
readonly salts: FutureSaltsService
readonly keys: AuthKeysService
constructor(readonly options: StorageManagerOptions & StorageManagerExtraOptions) {
this.provider = this.options.provider
@ -87,7 +88,7 @@ export class StorageManager {
await this.driver.save?.()
}
async clear(withAuthKeys = false) {
async clear(withAuthKeys = false): Promise<void> {
if (withAuthKeys) {
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
*/
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
// 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 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)
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
*/
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 ret = new Uint8Array(len)

View file

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

View file

@ -24,8 +24,8 @@ export class Deque<T> {
protected _capacity: number
constructor(
readonly maxLength = Infinity,
minCapacity = maxLength === Infinity ? MIN_INITIAL_CAPACITY : maxLength,
readonly maxLength: number = Infinity,
minCapacity: number = maxLength === Infinity ? MIN_INITIAL_CAPACITY : maxLength,
) {
let capacity = minCapacity
@ -51,7 +51,7 @@ export class Deque<T> {
this._capacity = capacity
}
protected _resize() {
protected _resize(): void {
const p = this._head
const n = this._capacity
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
}
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 finished = false

View file

@ -3,6 +3,7 @@ import type { tl } from '@mtcute/tl'
import { isPresent } from '../type-assertions.js'
import { assertNever } from '../../types/utils.js'
import type { Deeplink } 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
*/
export const botStart = deeplinkBuilder<{
export const botStart: Deeplink<{
/** Bot username */
username: string
/** Start parameter */
parameter: string
}>({
}> = /* #__PURE__ */ deeplinkBuilder({
internalBuild: ({ username, parameter }) => ['resolve', { domain: username, start: parameter }],
internalParse: (path, query) => {
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
* whether to grant them at all.
*/
export const botAddToGroup = deeplinkBuilder<{
export const botAddToGroup: Deeplink<{
/** Bot username */
bot: string
/** If specified, the client will call `/start parameter` on the bot once the bot has been added */
parameter?: string
/** Admin rights to request */
admin?: BotAdminRight[]
}>({
}> = /* #__PURE__ */ deeplinkBuilder({
internalBuild: ({ bot, parameter, admin }) => [
'resolve',
{ 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
* whether to grant them at all.
*/
export const botAddToChannel = deeplinkBuilder<{
export const botAddToChannel: Deeplink<{
/** Bot username */
bot: string
/** Admin rights to request */
admin?: BotAdminRight[]
}>({
}> = /* #__PURE__ */ deeplinkBuilder({
internalBuild: ({ bot, admin }) => [
'resolve',
{ domain: bot, startchannel: true, admin: normalizeBotAdmin(admin) },
@ -240,12 +241,12 @@ export const botAddToChannel = deeplinkBuilder<{
*
* Used to share games.
*/
export const botGame = deeplinkBuilder<{
export const botGame: Deeplink<{
/** Bot username */
bot: string
/** Game short name */
game: string
}>({
}> = /* #__PURE__ */ deeplinkBuilder({
internalBuild: ({ bot, game }) => ['resolve', { domain: bot, game }],
internalParse: (path, query) => {
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
* their `short_name`.
*/
export const botWebApp = deeplinkBuilder<{
export const botWebApp: Deeplink<{
/** Bot username */
bot: string
/** App short name */
app: string
/** Parameter to be passed by the client to messages.requestAppWebView as `start_param` */
parameter?: string
}>({
}> = /* #__PURE__ */ deeplinkBuilder({
internalBuild: ({ bot, app, parameter }) => ['resolve', { domain: bot, appname: app, startapp: parameter }],
internalParse: (path, query) => {
if (path !== 'resolve') return null

View file

@ -1,3 +1,4 @@
import type { Deeplink } 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.
* Such links are generated using phone.exportGroupCallInvite.
*/
export const videoChat = deeplinkBuilder<{
export const videoChat: Deeplink<{
username: string
/**
* 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
isLivestream?: boolean
}>({
}> = /* #__PURE__ */ deeplinkBuilder({
internalBuild: ({ username, inviteHash, isLivestream }) => [
'resolve',
{

View file

@ -1,3 +1,4 @@
import type { Deeplink } 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
*/
export const chatInvite = deeplinkBuilder<{ hash: string }>({
export const chatInvite: Deeplink<{ hash: string }> = /* #__PURE__ */ deeplinkBuilder({
internalBuild: ({ hash }) => ['join', { invite: hash }],
internalParse: (path, query) => {
if (path !== 'join') return null
@ -32,7 +33,7 @@ export const chatInvite = deeplinkBuilder<{ hash: string }>({
/**
* Chat folder links
*/
export const chatFolder = deeplinkBuilder<{ slug: string }>({
export const chatFolder: Deeplink<{ slug: string }> = /* #__PURE__ */ deeplinkBuilder({
internalBuild: ({ slug }) => ['addlist', { slug }],
internalParse: (path, query) => {
if (path !== 'addlist') return null
@ -76,8 +77,7 @@ function parseMediaTimestamp(timestamp: string) {
*
* Note: `channelId` is a non-marked channel ID
*/
export const message = deeplinkBuilder<
({ username: string } | { channelId: number }) & {
export const message: Deeplink<({ username: string } | { channelId: number }) & {
/** Message ID */
id: number
/** Thread ID */
@ -100,8 +100,8 @@ export const message = deeplinkBuilder<
* Whether this is a link to a specific message in the album or to the entire album
*/
single?: boolean
}
>({
}
> = /* #__PURE__ */ deeplinkBuilder({
internalBuild: (params) => {
const common = {
post: params.id,
@ -199,4 +199,4 @@ export const message = deeplinkBuilder<
...common,
}
},
})
})

View file

@ -1,3 +1,4 @@
import type { Deeplink } 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.
*/
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 }],
internalParse: (path, query) => {
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.
*/
export const boost = deeplinkBuilder<{ username: string } | { channelId: number }>({
export const boost: Deeplink<{ username: string } | { channelId: number }> = /* #__PURE__ */ deeplinkBuilder({
internalBuild: (params) => {
if ('username' in params) {
return ['boost', { domain: params.username }]
@ -86,7 +87,7 @@ export const boost = deeplinkBuilder<{ username: string } | { channelId: number
/**
* 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
internalBuild: ({ slug }) => ['addlist', { slug }],
internalParse: (path, query) => {

View file

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

View file

@ -1,3 +1,4 @@
import type { Deeplink } 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
*/
export const stickerset = deeplinkBuilder<{
export const stickerset: Deeplink<{
slug: string
emoji?: boolean
}>({
}> = /* #__PURE__ */ deeplinkBuilder({
internalBuild: ({ slug, emoji }) => [emoji ? 'addemoji' : 'addstickers', { set: slug }],
internalParse: (path, query) => {
if (path !== 'addstickers' && path !== 'addemoji') return null

View file

@ -1,3 +1,4 @@
import type { Deeplink } 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
*/
export const publicUsername = deeplinkBuilder<{ username: string }>({
export const publicUsername: Deeplink<{ username: string }> = /* #__PURE__ */ deeplinkBuilder({
internalBuild: ({ username }) => ['resolve', { domain: username }],
internalParse: (path, query) => {
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
* constructor returned by contacts.exportContactToken.
*/
export const temporaryProfile = deeplinkBuilder<{ token: string }>({
export const temporaryProfile: Deeplink<{ token: string }> = /* #__PURE__ */ deeplinkBuilder({
internalBuild: ({ token }) => ['contact', { token }],
internalParse: (path, query) => {
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.
*/
export const phoneNumber = deeplinkBuilder<{ phone: string }>({
export const phoneNumber: Deeplink<{ phone: string }> = /* #__PURE__ */ deeplinkBuilder({
internalBuild: ({ phone }) => ['resolve', { phone }],
internalParse: (path, query) => {
if (path !== 'resolve') return null

View file

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

View file

@ -159,23 +159,23 @@ export class LongSet {
return this._set.size
}
add(val: Long) {
add(val: Long): void {
this._set.add(longToFastString(val))
}
delete(val: Long) {
delete(val: Long): void {
this._set.delete(longToFastString(val))
}
has(val: Long) {
has(val: Long): boolean {
return this._set.has(longToFastString(val))
}
clear() {
clear(): void {
this._set.clear()
}
toArray() {
toArray(): Long[] {
const arr: Long[] = []
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()
}
clear() {
clear(): void {
this._first = this._last = undefined
this._set.clear()
}
add(val: T) {
add(val: T): void {
if (this._set.has(val as any)) return
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)
}
}

View file

@ -8,7 +8,7 @@ export interface UserConfigPersisted {
apiHash: string
}
export function getConfigFilePath() {
export function getConfigFilePath(): string {
switch (process.platform) {
case 'linux':
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()
await fs.mkdir(path.dirname(filePath), { recursive: true })

View file

@ -11,7 +11,7 @@ export interface DependenciesList {
devDepdenencies: string[]
}
export function buildDependenciesList(config: UserConfig) {
export function buildDependenciesList(config: UserConfig): DependenciesList {
const dependencies = []
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)
if (config.packageManager === PackageManager.Deno) {

View file

@ -85,7 +85,7 @@ export function getInstallCommand(params: { mgr: PackageManager, packages: strin
return exec
}
export function getExecCommand(mgr: PackageManager, ...cmd: string[]) {
export function getExecCommand(mgr: PackageManager, ...cmd: string[]): string[] {
switch (mgr) {
case PackageManager.Npm:
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) {
case PackageManager.Npm:
case PackageManager.Yarn:

View file

@ -1,7 +1,7 @@
import * as colors from 'colorette'
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) => {
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 {
constructor(
readonly filename = ':memory:',
readonly params?: SqliteStorageDriverOptions,
readonly params?: SqliteStorageDriverOptions | undefined,
) {
super()
}

View file

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

View file

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

View file

@ -6,7 +6,11 @@ import { Readable as NodeReadable } from 'node:stream'
import type { UploadFileLike } from '@mtcute/core'
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') {
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 {
_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 type { TelegramClient } from '@mtcute/core/client.js'
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 */
getCustomEmojis() {
getCustomEmojis(): Promise<Sticker[]> {
return this.client.getCustomEmojisFromMessages(this.messages)
}
/** 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
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 */
answerMedia(...params: ParametersSkip1<TelegramClient['answerMedia']>) {
answerMedia(...params: ParametersSkip1<TelegramClient['answerMedia']>): Promise<Message> {
const [send, params_ = {}] = params
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 */
answerMediaGroup(...params: ParametersSkip1<TelegramClient['answerMediaGroup']>) {
answerMediaGroup(...params: ParametersSkip1<TelegramClient['answerMediaGroup']>): Promise<Message[]> {
const [send, params_ = {}] = params
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 */
replyText(...params: ParametersSkip1<TelegramClient['replyText']>) {
replyText(...params: ParametersSkip1<TelegramClient['replyText']>): Promise<Message> {
const [send, params_ = {}] = params
params_.businessConnectionId = this.update.connectionId
@ -82,7 +82,7 @@ export class BusinessMessageContext extends BusinessMessage implements UpdateCon
}
/** Send a media in reply to this message */
replyMedia(...params: ParametersSkip1<TelegramClient['replyMedia']>) {
replyMedia(...params: ParametersSkip1<TelegramClient['replyMedia']>): Promise<Message> {
const [send, params_ = {}] = params
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 */
replyMediaGroup(...params: ParametersSkip1<TelegramClient['replyMediaGroup']>) {
replyMediaGroup(...params: ParametersSkip1<TelegramClient['replyMediaGroup']>): Promise<Message[]> {
const [send, params_ = {}] = params
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 */
quoteWithText(params: Parameters<TelegramClient['quoteWithText']>[1]) {
quoteWithText(params: Parameters<TelegramClient['quoteWithText']>[1]): Promise<Message> {
params.businessConnectionId = this.update.connectionId
return this.client.quoteWithText(this, params)
}
/** 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
return this.client.quoteWithMedia(this, params)
}
/** 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
return this.client.quoteWithMediaGroup(this, params)
}
/** Delete this message (message group) */
delete(params?: DeleteMessagesParams) {
delete(params?: DeleteMessagesParams): Promise<void> {
return this.client.deleteMessagesById(
this.chat.inputPeer,
this.messages.map(it => it.id),
@ -128,7 +128,7 @@ export class BusinessMessageContext extends BusinessMessage implements UpdateCon
}
/** Pin this message */
pin(params?: OmitInputMessageId<Parameters<TelegramClient['pinMessage']>[0]>) {
pin(params?: OmitInputMessageId<Parameters<TelegramClient['pinMessage']>[0]>): Promise<Message | null> {
return this.client.pinMessage({
chatId: this.chat.inputPeer,
message: this.id,
@ -137,7 +137,7 @@ export class BusinessMessageContext extends BusinessMessage implements UpdateCon
}
/** Unpin this message */
unpin() {
unpin(): Promise<void> {
return this.client.unpinMessage({
chatId: this.chat.inputPeer,
message: this.id,
@ -145,7 +145,7 @@ export class BusinessMessageContext extends BusinessMessage implements UpdateCon
}
/** Edit this message */
edit(params: OmitInputMessageId<Parameters<TelegramClient['editMessage']>[0]>) {
edit(params: OmitInputMessageId<Parameters<TelegramClient['editMessage']>[0]>): Promise<Message> {
return this.client.editMessage({
chatId: this.chat.inputPeer,
message: this.id,
@ -154,7 +154,7 @@ export class BusinessMessageContext extends BusinessMessage implements UpdateCon
}
/** Forward this message (message group) */
forwardTo(params: ForwardMessageOptions) {
forwardTo(params: ForwardMessageOptions): Promise<Message[]> {
return this.client.forwardMessagesById({
fromChatId: this.chat.inputPeer,
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) */
copy(params: SendCopyParams & SendCopyGroupParams) {
copy(params: SendCopyParams & SendCopyGroupParams): Promise<Message | Message[]> {
if (this.isMessageGroup) {
return this.client.sendCopyGroup({
messages: this.messages,
@ -178,7 +178,7 @@ export class BusinessMessageContext extends BusinessMessage implements UpdateCon
}
/** 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({
chatId: this.chat.inputPeer,
message: this.id,

View file

@ -20,7 +20,7 @@ export class CallbackQueryContext extends CallbackQuery implements UpdateContext
}
/** 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)
}
@ -37,7 +37,7 @@ export class CallbackQueryContext extends CallbackQuery implements UpdateContext
/**
* 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({
chatId: this.raw.peer,
message: this.raw.msgId,
@ -48,7 +48,9 @@ export class CallbackQueryContext extends CallbackQuery implements UpdateContext
/**
* 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()
if (!msg) return
@ -75,14 +77,14 @@ export class InlineCallbackQueryContext extends InlineCallbackQuery implements U
}
/** 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)
}
/**
* 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({
messageId: this.raw.msgId,
...params,
@ -108,14 +110,14 @@ export class BusinessCallbackQueryContext
}
/** 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)
}
/**
* 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({
message: this.message,
businessConnectionId: this.connectionId,

View file

@ -20,7 +20,7 @@ export class InlineQueryContext extends InlineQuery implements UpdateContext<Inl
}
/** Answer to this inline query */
answer(...params: ParametersSkip1<TelegramClient['answerInlineQuery']>) {
answer(...params: ParametersSkip1<TelegramClient['answerInlineQuery']>): Promise<void> {
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 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'
@ -63,82 +68,82 @@ export class MessageContext extends Message implements UpdateContext<Message> {
}
/** Get a message that this message is a reply to */
getReplyTo() {
getReplyTo(): Promise<Message | null> {
return this.client.getReplyTo(this)
}
/** 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 })
}
/** Get all custom emojis contained in this message (message group), if any */
getCustomEmojis() {
getCustomEmojis(): Promise<Sticker[]> {
return this.client.getCustomEmojisFromMessages(this.messages)
}
/** 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)
}
/** 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)
}
/** 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)
}
/** 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)
}
/** 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)
}
/** 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)
}
/** 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)
}
/** 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)
}
/** 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)
}
/** 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)
}
/** 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)
}
/** 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)
}
/** Delete this message (message group) */
delete(params?: DeleteMessagesParams) {
delete(params?: DeleteMessagesParams): Promise<void> {
return this.client.deleteMessagesById(
this.chat.inputPeer,
this.messages.map(it => it.id),
@ -147,7 +152,7 @@ export class MessageContext extends Message implements UpdateContext<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({
chatId: this.chat.inputPeer,
message: this.id,
@ -156,7 +161,7 @@ export class MessageContext extends Message implements UpdateContext<Message> {
}
/** Unpin this message */
unpin() {
unpin(): Promise<void> {
return this.client.unpinMessage({
chatId: this.chat.inputPeer,
message: this.id,
@ -164,7 +169,7 @@ export class MessageContext extends Message implements UpdateContext<Message> {
}
/** Edit this message */
edit(params: OmitInputMessageId<Parameters<TelegramClient['editMessage']>[0]>) {
edit(params: OmitInputMessageId<Parameters<TelegramClient['editMessage']>[0]>): Promise<Message> {
return this.client.editMessage({
chatId: this.chat.inputPeer,
message: this.id,
@ -173,7 +178,7 @@ export class MessageContext extends Message implements UpdateContext<Message> {
}
/** Forward this message (message group) */
forwardTo(params: ForwardMessageOptions) {
forwardTo(params: ForwardMessageOptions): Promise<Message[]> {
return this.client.forwardMessagesById({
fromChatId: this.chat.inputPeer,
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) */
copy(params: SendCopyParams & SendCopyGroupParams) {
copy(params: SendCopyParams & SendCopyGroupParams): Promise<Message> | Promise<Message[]> {
if (this.isMessageGroup) {
return this.client.sendCopyGroup({
messages: this.messages,
@ -197,7 +202,9 @@ export class MessageContext extends Message implements UpdateContext<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({
chatId: this.chat.inputPeer,
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 { UpdateContextDistributed } from './base.js'
@ -10,8 +27,36 @@ import { InlineQueryContext } from './inline-query.js'
import { MessageContext } from './message.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 */
export function _parsedUpdateToContext(client: TelegramClient, update: ParsedUpdate) {
export function _parsedUpdateToContext(client: TelegramClient, update: ParsedUpdate): UpdateContextType {
switch (update.name) {
case 'new_message':
case 'edit_message':
@ -43,5 +88,3 @@ export function _parsedUpdateToContext(client: TelegramClient, update: ParsedUpd
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 { MessageContext } from '../context/message.js'
import { chat } from './chat.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)..
@ -97,13 +97,30 @@ export function command(commands: MaybeArray<string | RegExp>, {
* Shorthand filter that matches /start commands sent to bot's
* 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
* 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[] }>) {
return (
@ -156,7 +173,10 @@ function deeplinkBase(base: UpdateFilter<MessageContext | BusinessMessageContext
* If the parameter is a regex, groups are added to `msg.command`,
* 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>`).
@ -164,4 +184,7 @@ export const deeplink = deeplinkBase(start)
* If the parameter is a regex, groups are added to `msg.command`,
* 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 {
Audio,
Contact,
Dice,
Document,
Game,
Invoice,
LiveLocation,
Location,
MaybeArray,
Message,
MessageAction,
MessageMediaType,
Peer,
Photo,
Poll,
RepliedMessageInfo,
RepliedMessageOrigin,
Sticker,
StickerSourceType,
StickerType,
User,
Venue,
Video,
Voice,
WebPage,
_RepliedMessageAssertionsByOrigin,
} from '@mtcute/core'
import {
@ -68,41 +81,44 @@ export const media: UpdateFilter<Message, { media: Exclude<Message['media'], nul
/**
* 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 =>
msg.media?.type === type
}
/** Filter messages containing a photo */
export const photo = mediaOf('photo')
export const photo: UpdateFilter<Message, { media: Photo }> = mediaOf('photo')
/** Filter messages containing a dice */
export const dice = mediaOf('dice')
export const dice: UpdateFilter<Message, { media: Dice }> = mediaOf('dice')
/** 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 */
export const audio = mediaOf('audio')
export const audio: UpdateFilter<Message, { media: Audio }> = mediaOf('audio')
/** 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 */
export const sticker = mediaOf('sticker')
export const sticker: UpdateFilter<Message, { media: Sticker }> = mediaOf('sticker')
/** 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) */
export const anyVideo = mediaOf('video')
export const anyVideo: UpdateFilter<Message, { media: Video }> = mediaOf('video')
/** 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 */
export const liveLocation = mediaOf('live_location')
export const liveLocation: UpdateFilter<Message, { media: LiveLocation }> = mediaOf('live_location')
/** 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 */
export const webpage = mediaOf('webpage')
export const webpage: UpdateFilter<Message, { media: WebPage }> = mediaOf('webpage')
/** Filter messages containing a venue */
export const venue = mediaOf('venue')
export const venue: UpdateFilter<Message, { media: Venue }> = mediaOf('venue')
/** Filter messages containing a poll */
export const poll = mediaOf('poll')
export const poll: UpdateFilter<Message, { media: Poll }> = mediaOf('poll')
/** 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).
@ -224,7 +240,10 @@ export function action<T extends Exclude<MessageAction, null>['type']>(type: May
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 =>
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.
*/
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) => {
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
* 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) => {
try {
await msg.getCompleteSender()

View file

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

View file

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

View file

@ -1,4 +1,5 @@
import { LruMap, asyncResettable } from '@mtcute/core/utils.js'
import type { MaybePromise } from '@mtcute/core'
import type { IStateStorageProvider } from './provider.js'
@ -16,14 +17,14 @@ export class StateService {
this._loaded = true
})
async load() {
async load(): Promise<void> {
await this._load.run()
this._vacuumTimer = setInterval(() => {
Promise.resolve(this.provider.state.vacuum(Date.now())).catch(() => {})
}, 300_000)
}
async destroy() {
async destroy(): Promise<void> {
await this.provider.driver.save?.()
await this.provider.driver.destroy?.()
clearInterval(this._vacuumTimer)
@ -68,11 +69,11 @@ export class StateService {
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)
}
resetRateLimit(key: string) {
resetRateLimit(key: string): MaybePromise<void> {
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 { DispatcherParams } from './dispatcher.js'
import { Dispatcher } from './dispatcher.js'
import type { UpdateFilter } from './filters/index.js'
import { filters } from './filters/index.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
*/
async goToStep(state: UpdateState<WizardInternalState>, step: number) {
async goToStep(state: UpdateState<WizardInternalState>, step: number): Promise<void> {
if (step >= this._steps) {
await state.exit()
} else {
@ -72,7 +73,7 @@ export class WizardScene<State extends object> extends Dispatcher<State & Wizard
/**
* Skip N steps
*/
async skip(state: UpdateState<WizardInternalState>, count = 1) {
async skip(state: UpdateState<WizardInternalState>, count = 1): Promise<void> {
const { $step } = (await state.get()) || {}
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`
*/
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)
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
*/
onCurrentStep() {
// eslint-disable-next-line ts/no-empty-object-type
onCurrentStep(): UpdateFilter<any, {}, WizardInternalState> {
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 = 4
export const WEB_LOCATION_FLAG = 1 << 24
export const FILE_REFERENCE_FLAG = 1 << 25
export const WEB_LOCATION_FLAG: number = 1 << 24
export const FILE_REFERENCE_FLAG: number = 1 << 25
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).
*/
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
*/
export class FakeTlsPacketCodec extends WrappedCodec implements IPacketCodec {
protected _stream = Buffer.alloc(0)
protected _stream: Buffer = Buffer.alloc(0)
private _header!: Buffer
private _isFirstTls = true

View file

@ -1,5 +1,6 @@
import type { Interface as RlInterface } 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 {
@ -157,7 +158,7 @@ export class TelegramClient extends TelegramClientBase {
return downloadToFile(this, filename, location, params)
}
downloadAsNodeStream(location: FileDownloadLocation, params?: FileDownloadParameters | undefined) {
downloadAsNodeStream(location: FileDownloadLocation, params?: FileDownloadParameters | undefined): Readable {
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`)
/** @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 => {
// eslint-disable-next-line no-console
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 {
constructor(
readonly filename = ':memory:',
readonly params?: SqliteStorageDriverOptions,
readonly params?: SqliteStorageDriverOptions | undefined,
) {
super()
}

View file

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

View file

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

View file

@ -7,7 +7,11 @@ import type { UploadFileLike } from '@mtcute/core'
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') {
file = createReadStream(file)
}

View file

@ -136,5 +136,5 @@ export abstract class BaseTcpTransport extends EventEmitter implements ITelegram
}
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