some changes

i've been wanting to name a commit like this for my entire life, lol. seriously though, a lot has changed:
 - extracted TL-related stuff to `@mtcute/tl-utils` and `@mtcute/tl-runtime`, rewrote codegen in TS
 - updated to layer 134, moved to int64 identifiers
 - rewritten networking (mtproto), rewritten updates handling
 - *lots* of refactoring

 still a very early version though, there are a lot of improvements to be made, but at least it runs, lol

 also tl-reference will not be updated anytime soon because i want to rewrite it
This commit is contained in:
teidesu 2021-11-23 00:03:59 +03:00
parent a834fbfa8d
commit ec736f8590
244 changed files with 12560 additions and 6951 deletions

View file

@ -36,6 +36,7 @@ module.exports = {
'@typescript-eslint/no-empty-function': 'off', '@typescript-eslint/no-empty-function': 'off',
'@typescript-eslint/no-this-alias': 'off', '@typescript-eslint/no-this-alias': 'off',
'prefer-rest-params': 'off', 'prefer-rest-params': 'off',
'no-prototype-builtins': 'off',
'@typescript-eslint/ban-types': 'off', '@typescript-eslint/ban-types': 'off',
'@typescript-eslint/adjacent-overload-signatures': 'off', '@typescript-eslint/adjacent-overload-signatures': 'off',
'@typescript-eslint/no-namespace': 'off', '@typescript-eslint/no-namespace': 'off',

View file

@ -12,12 +12,13 @@
"build": "tsc" "build": "tsc"
}, },
"dependencies": { "dependencies": {
"@types/long": "^4.0.1",
"@types/node": "^15.12.1", "@types/node": "^15.12.1",
"@mtcute/tl": "~1.131.0", "@mtcute/tl": "~134.0",
"@mtcute/core": "^1.0.0", "@mtcute/core": "^1.0.0",
"@mtcute/file-id": "^1.0.0", "@mtcute/file-id": "^1.0.0",
"eager-async-pool": "^1.0.0", "eager-async-pool": "^1.0.0",
"file-type": "^16.2.0", "file-type": "^16.2.0",
"big-integer": "1.6.48" "long": "^4.0.0"
} }
} }

View file

@ -2,8 +2,6 @@ const ts = require('typescript')
const path = require('path') const path = require('path')
const fs = require('fs') const fs = require('fs')
const prettier = require('prettier') const prettier = require('prettier')
// not the best way but who cares lol
const { createWriter } = require('../../tl/scripts/common')
const updates = require('./generate-updates') const updates = require('./generate-updates')
const targetDir = path.join(__dirname, '../src') const targetDir = path.join(__dirname, '../src')
@ -130,7 +128,7 @@ async function addSingleMethod(state, fileName) {
if (!isExported && !isPrivate) { if (!isExported && !isPrivate) {
throwError( throwError(
isExported, stmt,
fileName, fileName,
'Public methods MUST be exported.' 'Public methods MUST be exported.'
) )
@ -228,6 +226,14 @@ async function addSingleMethod(state, fileName) {
} }
} }
} else if (stmt.kind === ts.SyntaxKind.InterfaceDeclaration) { } else if (stmt.kind === ts.SyntaxKind.InterfaceDeclaration) {
if (isCopy) {
state.copy.push({
from: relPath,
code: stmt.getText()
})
continue
}
if (!checkForFlag(stmt, '@extension')) continue if (!checkForFlag(stmt, '@extension')) continue
const isExported = (stmt.modifiers || []).find( const isExported = (stmt.modifiers || []).find(
(mod) => mod.kind === 92 /* ExportKeyword */ (mod) => mod.kind === 92 /* ExportKeyword */
@ -260,7 +266,7 @@ async function addSingleMethod(state, fileName) {
} }
async function main() { async function main() {
const output = createWriter('../src/client.ts', __dirname) const output = fs.createWriteStream(path.join(__dirname, '../src/client.ts'))
const state = { const state = {
imports: {}, imports: {},
fields: [], fields: [],
@ -282,23 +288,22 @@ async function main() {
output.write( output.write(
'/* THIS FILE WAS AUTO-GENERATED */\n' + '/* THIS FILE WAS AUTO-GENERATED */\n' +
"import { BaseTelegramClient } from '@mtcute/core'\n" + "import { BaseTelegramClient } from '@mtcute/core'\n" +
"import { tl } from '@mtcute/tl'" "import { tl } from '@mtcute/tl'\n"
) )
Object.entries(state.imports).forEach(([module, items]) => { Object.entries(state.imports).forEach(([module, items]) => {
items = [...items] items = [...items]
output.write(`import { ${items.sort().join(', ')} } from '${module}'`) output.write(`import { ${items.sort().join(', ')} } from '${module}'\n`)
}) })
output.write() output.write('\n')
state.copy.forEach(({ from, code }) => { state.copy.forEach(({ from, code }) => {
output.write(`// from ${from}\n${code}\n`) output.write(`// from ${from}\n${code}\n`)
}) })
output.write( output.write(
'\nexport interface TelegramClient extends BaseTelegramClient {' '\nexport interface TelegramClient extends BaseTelegramClient {\n'
) )
output.tab()
output.write(`/** output.write(`/**
* Register a raw update handler * Register a raw update handler
@ -306,14 +311,14 @@ async function main() {
* @param name Event name * @param name Event name
* @param handler Raw update handler * @param handler Raw update handler
*/ */
on(name: 'raw_update', handler: ((upd: tl.TypeUpdate | tl.TypeMessage, users: UsersIndex, chats: ChatsIndex) => void)): this on(name: 'raw_update', handler: ((upd: tl.TypeUpdate | tl.TypeMessage, peers: PeersIndex) => void)): this
/** /**
* Register a parsed update handler * Register a parsed update handler
* *
* @param name Event name * @param name Event name
* @param handler Raw update handler * @param handler Raw update handler
*/ */
on(name: 'update', handler: ((upd: ParsedUpdate) => void)): this`) on(name: 'update', handler: ((upd: ParsedUpdate) => void)): this\n`)
updates.types.forEach((type) => { updates.types.forEach((type) => {
output.write(`/** output.write(`/**
@ -322,7 +327,7 @@ async function main() {
* @param name Event name * @param name Event name
* @param handler ${updates.toSentence(type, 'full')} * @param handler ${updates.toSentence(type, 'full')}
*/ */
on(name: '${type.typeName}', handler: ((upd: ${type.updateType}) => void)): this`) on(name: '${type.typeName}', handler: ((upd: ${type.updateType}) => void)): this\n`)
}) })
@ -441,10 +446,10 @@ on(name: '${type.typeName}', handler: ((upd: ${type.updateType}) => void)): this
if (!isPrivate && !hasOverloads) { if (!isPrivate && !hasOverloads) {
if (!comment.match(/\/\*\*?\s*\*\//)) if (!comment.match(/\/\*\*?\s*\*\//))
// empty comment, no need to write it // empty comment, no need to write it
output.write(comment) output.write(comment + '\n')
output.write( output.write(
`${name}${generics}(${parameters})${returnType}` `${name}${generics}(${parameters})${returnType}\n`
) )
} }
@ -456,27 +461,22 @@ on(name: '${type.typeName}', handler: ((upd: ${type.updateType}) => void)): this
} }
} }
) )
output.untab()
output.write('}')
output.write(
'/** @internal */\nexport class TelegramClient extends BaseTelegramClient {'
)
output.tab()
state.fields.forEach(({ code }) => output.write('protected ' + code))
output.write('constructor(opts: BaseTelegramClient.Options) {')
output.tab()
output.write('super(opts)')
state.init.forEach((code) => {
output.write(code)
})
output.untab()
output.write('}\n') output.write('}\n')
classContents.forEach((line) => output.write(line)) output.write(
output.untab() '/** @internal */\nexport class TelegramClient extends BaseTelegramClient {\n'
)
state.fields.forEach(({ code }) => output.write(`protected ${code}\n`))
output.write('constructor(opts: BaseTelegramClient.Options) {\n')
output.write('super(opts)\n')
state.init.forEach((code) => {
output.write(code + '\n')
})
output.write('}\n')
classContents.forEach((line) => output.write(line + '\n'))
output.write('}') output.write('}')
// format the resulting file with prettier // format the resulting file with prettier

View file

@ -1,11 +1,24 @@
const fs = require('fs') const fs = require('fs')
const path = require('path') const path = require('path')
const prettier = require('prettier') const prettier = require('prettier')
const {
snakeToCamel, const snakeToCamel = (s) => {
camelToPascal, return s.replace(/(?<!^|_)(_[a-z0-9])/gi, ($1) => {
camelToSnake, return $1.substr(1).toUpperCase()
} = require('../../tl/scripts/common') })
}
const camelToPascal = (s) =>
s[0].toUpperCase() + s.substr(1)
const camelToSnake = (s) => {
return s.replace(
/(?<=[a-zA-Z0-9])([A-Z0-9]+(?=[A-Z]|$)|[A-Z0-9])/g,
($1) => {
return '_' + $1.toLowerCase()
}
)
}
function parseUpdateTypes() { function parseUpdateTypes() {
const lines = fs const lines = fs

View file

@ -145,14 +145,31 @@ import { getInstalledStickers } from './methods/stickers/get-installed-stickers'
import { getStickerSet } from './methods/stickers/get-sticker-set' import { getStickerSet } from './methods/stickers/get-sticker-set'
import { moveStickerInSet } from './methods/stickers/move-sticker-in-set' import { moveStickerInSet } from './methods/stickers/move-sticker-in-set'
import { setStickerSetThumb } from './methods/stickers/set-sticker-set-thumb' import { setStickerSetThumb } from './methods/stickers/set-sticker-set-thumb'
import { ConditionVariable } from '@mtcute/core/src/utils/condition-variable'
import {
AsyncLock,
Deque,
MaybeArray,
MaybeAsync,
SessionConnection,
SortedLinkedList,
} from '@mtcute/core'
import { RpsMeter } from './utils/rps-meter'
import { import {
_dispatchUpdate, _dispatchUpdate,
_fetchUpdatesState, _fetchUpdatesState,
_handleUpdate, _handleUpdate,
_keepAliveAction, _keepAliveAction,
_loadStorage, _loadStorage,
_onStop,
_saveStorage, _saveStorage,
_updatesLoop,
catchUp, catchUp,
enableRps,
getCurrentRpsIncoming,
getCurrentRpsProcessing,
startUpdatesLoop,
stopUpdatesLoop,
} from './methods/updates' } from './methods/updates'
import { blockUser } from './methods/users/block-user' import { blockUser } from './methods/users/block-user'
import { deleteProfilePhotos } from './methods/users/delete-profile-photos' import { deleteProfilePhotos } from './methods/users/delete-profile-photos'
@ -181,7 +198,6 @@ import {
ChatMember, ChatMember,
ChatMemberUpdate, ChatMemberUpdate,
ChatPreview, ChatPreview,
ChatsIndex,
ChosenInlineResult, ChosenInlineResult,
Conversation, Conversation,
DeleteMessageUpdate, DeleteMessageUpdate,
@ -203,6 +219,7 @@ import {
ParsedUpdate, ParsedUpdate,
PartialExcept, PartialExcept,
PartialOnly, PartialOnly,
PeersIndex,
Photo, Photo,
Poll, Poll,
PollUpdate, PollUpdate,
@ -219,17 +236,27 @@ import {
User, User,
UserStatusUpdate, UserStatusUpdate,
UserTypingUpdate, UserTypingUpdate,
UsersIndex,
} from './types' } from './types'
import {
AsyncLock,
MaybeArray,
MaybeAsync,
TelegramConnection,
} from '@mtcute/core'
import { tdFileId } from '@mtcute/file-id' import { tdFileId } from '@mtcute/file-id'
import { Logger } from '@mtcute/core/src/utils/logger' import { Logger } from '@mtcute/core/src/utils/logger'
// from methods/updates.ts
interface PendingUpdateContainer {
upd: tl.TypeUpdates
seqStart: number
seqEnd: number
}
// from methods/updates.ts
interface PendingUpdate {
update: tl.TypeUpdate
channelId?: number
pts?: number
ptsBefore?: number
qtsBefore?: number
timeout?: number
peers?: PeersIndex
}
export interface TelegramClient extends BaseTelegramClient { export interface TelegramClient extends BaseTelegramClient {
/** /**
* Register a raw update handler * Register a raw update handler
@ -241,8 +268,7 @@ export interface TelegramClient extends BaseTelegramClient {
name: 'raw_update', name: 'raw_update',
handler: ( handler: (
upd: tl.TypeUpdate | tl.TypeMessage, upd: tl.TypeUpdate | tl.TypeMessage,
users: UsersIndex, peers: PeersIndex
chats: ChatsIndex
) => void ) => void
): this ): this
/** /**
@ -3268,12 +3294,58 @@ export interface TelegramClient extends BaseTelegramClient {
progressCallback?: (uploaded: number, total: number) => void progressCallback?: (uploaded: number, total: number) => void
} }
): Promise<StickerSet> ): Promise<StickerSet>
/**
* Enable RPS meter.
* Only available in NodeJS v10.7.0 and newer
*
* > **Note**: This may have negative impact on performance
*
* @param size Sampling size
* @param time Window time
*/
enableRps(size?: number, time?: number): void
/**
* Get current average incoming RPS
*
* Incoming RPS is calculated based on
* incoming update containers. Normally,
* they should be around the same, except
* rare situations when processing rps
* may peak.
*
*/
getCurrentRpsIncoming(): number
/**
* Get current average processing RPS
*
* Processing RPS is calculated based on
* dispatched updates. Normally,
* they should be around the same, except
* rare situations when processing rps
* may peak.
*
*/
getCurrentRpsProcessing(): number
/**
* **ADVANCED**
*
* Manually start updates loop.
* Usually done automatically inside {@link start}
*/
startUpdatesLoop(): void
/**
* **ADVANCED**
*
* Manually stop updates loop.
* Usually done automatically when stopping the client with {@link close}
*/
stopUpdatesLoop(): void
_handleUpdate(update: tl.TypeUpdates, noDispatch?: boolean): void _handleUpdate(update: tl.TypeUpdates, noDispatch?: boolean): void
/** /**
* Catch up with the server by loading missed updates. * Catch up with the server by loading missed updates.
* *
*/ */
catchUp(): Promise<void> catchUp(): void
/** /**
* Block a user * Block a user
* *
@ -3413,8 +3485,12 @@ export interface TelegramClient extends BaseTelegramClient {
* Useful when an `InputPeer` is needed. * Useful when an `InputPeer` is needed.
* *
* @param peerId The peer identifier that you want to extract the `InputPeer` from. * @param peerId The peer identifier that you want to extract the `InputPeer` from.
* @param force (default: `false`) Whether to force re-fetch the peer from the server
*/ */
resolvePeer(peerId: InputPeerLike): Promise<tl.TypeInputPeer> resolvePeer(
peerId: InputPeerLike,
force?: boolean
): Promise<tl.TypeInputPeer>
/** /**
* Change user status to offline or online * Change user status to offline or online
* *
@ -3483,11 +3559,21 @@ export class TelegramClient extends BaseTelegramClient {
protected _selfUsername: string | null protected _selfUsername: string | null
protected _pendingConversations: Record<number, Conversation[]> protected _pendingConversations: Record<number, Conversation[]>
protected _hasConversations: boolean protected _hasConversations: boolean
protected _downloadConnections: Record<number, TelegramConnection> protected _downloadConnections: Record<number, SessionConnection>
protected _connectionsForInline: Record<number, TelegramConnection> protected _connectionsForInline: Record<number, SessionConnection>
protected _parseModes: Record<string, IMessageEntityParser> protected _parseModes: Record<string, IMessageEntityParser>
protected _defaultParseMode: string | null protected _defaultParseMode: string | null
protected _updatesLoopActive: boolean
protected _updatesLoopCv: ConditionVariable
protected _pendingUpdateContainers: SortedLinkedList<PendingUpdateContainer>
protected _pendingPtsUpdates: SortedLinkedList<PendingUpdate>
protected _pendingPtsUpdatesPostponed: SortedLinkedList<PendingUpdate>
protected _pendingQtsUpdates: SortedLinkedList<PendingUpdate>
protected _pendingQtsUpdatesPostponed: SortedLinkedList<PendingUpdate>
protected _pendingUnorderedUpdates: Deque<PendingUpdate>
protected _updLock: AsyncLock protected _updLock: AsyncLock
protected _rpsIncoming?: RpsMeter
protected _rpsProcessing?: RpsMeter
protected _pts?: number protected _pts?: number
protected _qts?: number protected _qts?: number
protected _date?: number protected _date?: number
@ -3506,12 +3592,33 @@ export class TelegramClient extends BaseTelegramClient {
this._userId = null this._userId = null
this._isBot = false this._isBot = false
this._selfUsername = null this._selfUsername = null
this.log.prefix = '[USER N/A] '
this._pendingConversations = {} this._pendingConversations = {}
this._hasConversations = false this._hasConversations = false
this._downloadConnections = {} this._downloadConnections = {}
this._connectionsForInline = {} this._connectionsForInline = {}
this._parseModes = {} this._parseModes = {}
this._defaultParseMode = null this._defaultParseMode = null
this._updatesLoopActive = false
this._updatesLoopCv = new ConditionVariable()
this._pendingUpdateContainers = new SortedLinkedList(
(a, b) => a.seqStart - b.seqStart
)
this._pendingPtsUpdates = new SortedLinkedList(
(a, b) => a.ptsBefore! - b.ptsBefore!
)
this._pendingPtsUpdatesPostponed = new SortedLinkedList(
(a, b) => a.ptsBefore! - b.ptsBefore!
)
this._pendingQtsUpdates = new SortedLinkedList(
(a, b) => a.qtsBefore! - b.qtsBefore!
)
this._pendingQtsUpdatesPostponed = new SortedLinkedList(
(a, b) => a.qtsBefore! - b.qtsBefore!
)
this._pendingUnorderedUpdates = new Deque()
this._updLock = new AsyncLock() this._updLock = new AsyncLock()
// we dont need to initialize state fields since // we dont need to initialize state fields since
// they are always loaded either from the server, or from storage. // they are always loaded either from the server, or from storage.
@ -3527,7 +3634,6 @@ export class TelegramClient extends BaseTelegramClient {
this._updsLog = this.log.create('updates') this._updsLog = this.log.create('updates')
} }
acceptTos = acceptTos acceptTos = acceptTos
checkPassword = checkPassword checkPassword = checkPassword
getPasswordHint = getPasswordHint getPasswordHint = getPasswordHint
@ -3667,12 +3773,19 @@ export class TelegramClient extends BaseTelegramClient {
getStickerSet = getStickerSet getStickerSet = getStickerSet
moveStickerInSet = moveStickerInSet moveStickerInSet = moveStickerInSet
setStickerSetThumb = setStickerSetThumb setStickerSetThumb = setStickerSetThumb
enableRps = enableRps
getCurrentRpsIncoming = getCurrentRpsIncoming
getCurrentRpsProcessing = getCurrentRpsProcessing
protected _fetchUpdatesState = _fetchUpdatesState protected _fetchUpdatesState = _fetchUpdatesState
protected _loadStorage = _loadStorage protected _loadStorage = _loadStorage
startUpdatesLoop = startUpdatesLoop
stopUpdatesLoop = stopUpdatesLoop
protected _onStop = _onStop
protected _saveStorage = _saveStorage protected _saveStorage = _saveStorage
protected _dispatchUpdate = _dispatchUpdate protected _dispatchUpdate = _dispatchUpdate
_handleUpdate = _handleUpdate _handleUpdate = _handleUpdate
catchUp = catchUp catchUp = catchUp
protected _updatesLoop = _updatesLoop
protected _keepAliveAction = _keepAliveAction protected _keepAliveAction = _keepAliveAction
blockUser = blockUser blockUser = blockUser
deleteProfilePhotos = deleteProfilePhotos deleteProfilePhotos = deleteProfilePhotos

View file

@ -32,8 +32,7 @@ import {
Photo, Photo,
ChatEvent, ChatEvent,
ChatInviteLink, ChatInviteLink,
UsersIndex, PeersIndex,
ChatsIndex,
GameHighScore, GameHighScore,
ArrayWithTotal, ArrayWithTotal,
BotCommands, BotCommands,
@ -60,7 +59,7 @@ import {
import { import {
MaybeArray, MaybeArray,
MaybeAsync, MaybeAsync,
TelegramConnection, SessionConnection,
AsyncLock, AsyncLock,
} from '@mtcute/core' } from '@mtcute/core'

View file

@ -1,4 +1,5 @@
import { TelegramClient } from '../../client' import { TelegramClient } from '../../client'
import { tl } from '@mtcute/tl'
// @extension // @extension
interface AuthState { interface AuthState {
@ -17,4 +18,5 @@ function _initializeAuthState(this: TelegramClient) {
this._userId = null this._userId = null
this._isBot = false this._isBot = false
this._selfUsername = null this._selfUsername = null
this.log.prefix = '[USER N/A] '
} }

View file

@ -37,11 +37,17 @@ export async function checkPassword(
'user' 'user'
) )
this.log.prefix = `[USER ${this._userId}] `
this._userId = res.user.id this._userId = res.user.id
this._isBot = false this._isBot = false
this._selfChanged = true this._selfChanged = true
this._selfUsername = res.user.username ?? null
await this._fetchUpdatesState() await this._fetchUpdatesState()
await this._saveStorage() await this._saveStorage()
// telegram ignores invokeWithoutUpdates for auth methods
if (this._disableUpdates) this.primaryConnection._resetSession()
else this.startUpdatesLoop()
return new User(this, res.user) return new User(this, res.user)
} }

View file

@ -33,6 +33,7 @@ export async function signInBot(
'user' 'user'
) )
this.log.prefix = `[USER ${this._userId}] `
this._userId = res.user.id this._userId = res.user.id
this._isBot = true this._isBot = true
this._selfUsername = res.user.username! this._selfUsername = res.user.username!
@ -40,5 +41,9 @@ export async function signInBot(
await this._fetchUpdatesState() await this._fetchUpdatesState()
await this._saveStorage() await this._saveStorage()
// telegram ignores invokeWithoutUpdates for auth methods
if (this._disableUpdates) this.primaryConnection._resetSession()
else this.startUpdatesLoop()
return new User(this, res.user) return new User(this, res.user)
} }

View file

@ -41,6 +41,7 @@ export async function signIn(
assertTypeIs('signIn (@ auth.signIn -> user)', res.user, 'user') assertTypeIs('signIn (@ auth.signIn -> user)', res.user, 'user')
this.log.prefix = `[USER ${this._userId}] `
this._userId = res.user.id this._userId = res.user.id
this._isBot = false this._isBot = false
this._selfChanged = true this._selfChanged = true
@ -48,5 +49,9 @@ export async function signIn(
await this._fetchUpdatesState() await this._fetchUpdatesState()
await this._saveStorage() await this._saveStorage()
// telegram ignores invokeWithoutUpdates for auth methods
if (this._disableUpdates) this.primaryConnection._resetSession()
else this.startUpdatesLoop()
return new User(this, res.user) return new User(this, res.user)
} }

View file

@ -32,11 +32,16 @@ export async function signUp(
assertTypeIs('signUp (@ auth.signUp)', res, 'auth.authorization') assertTypeIs('signUp (@ auth.signUp)', res, 'auth.authorization')
assertTypeIs('signUp (@ auth.signUp -> user)', res.user, 'user') assertTypeIs('signUp (@ auth.signUp -> user)', res.user, 'user')
this.log.prefix = `[USER ${this._userId}] `
this._userId = res.user.id this._userId = res.user.id
this._isBot = false this._isBot = false
this._selfChanged = true this._selfChanged = true
await this._fetchUpdatesState() await this._fetchUpdatesState()
await this._saveStorage() await this._saveStorage()
// telegram ignores invokeWithoutUpdates for auth methods
if (this._disableUpdates) this.primaryConnection._resetSession()
else this.startUpdatesLoop()
return new User(this, res.user) return new User(this, res.user)
} }

View file

@ -148,15 +148,28 @@ export async function start(
// user is already authorized // user is already authorized
this.log.prefix = `[USER ${me.id}] `
this.log.info(
'Logged in as %s (ID: %s, username: %s, bot: %s)',
me.displayName,
me.id,
me.username,
me.isBot
)
if (!this._disableUpdates) { if (!this._disableUpdates) {
this._catchUpChannels = !!params.catchUp this._catchUpChannels = !!params.catchUp
if (params.catchUp) { if (!params.catchUp) {
await this.catchUp()
} else {
// otherwise we will catch up as soon as we receive a new update // otherwise we will catch up as soon as we receive a new update
await this._fetchUpdatesState() await this._fetchUpdatesState()
} }
this.startUpdatesLoop()
if (params.catchUp) {
this.catchUp()
}
} }
return me return me
@ -165,9 +178,7 @@ export async function start(
} }
if (!params.phone && !params.botToken) if (!params.phone && !params.botToken)
throw new MtArgumentError( throw new MtArgumentError('Neither phone nor bot token were provided')
'Neither phone nor bot token were provided'
)
let phone = params.phone ? await resolveMaybeDynamic(params.phone) : null let phone = params.phone ? await resolveMaybeDynamic(params.phone) : null
if (phone) { if (phone) {
@ -249,9 +260,7 @@ export async function start(
result = await this.checkPassword(password) result = await this.checkPassword(password)
} catch (e) { } catch (e) {
if (typeof params.password !== 'function') { if (typeof params.password !== 'function') {
throw new MtArgumentError( throw new MtArgumentError('Provided password was invalid')
'Provided password was invalid'
)
} }
if (e instanceof PasswordHashInvalidError) { if (e instanceof PasswordHashInvalidError) {

View file

@ -3,12 +3,10 @@ import {
InputPeerLike, InputPeerLike,
MtInvalidPeerTypeError, MtInvalidPeerTypeError,
GameHighScore, GameHighScore,
PeersIndex,
} from '../../types' } from '../../types'
import { tl } from '@mtcute/tl' import { tl } from '@mtcute/tl'
import { import { normalizeToInputUser } from '../../utils/peer-utils'
createUsersChatsIndex,
normalizeToInputUser,
} from '../../utils/peer-utils'
/** /**
* Get high scores of a game * Get high scores of a game
@ -43,9 +41,9 @@ export async function getGameHighScores(
userId: user, userId: user,
}) })
const { users } = createUsersChatsIndex(res) const peers = PeersIndex.from(res)
return res.scores.map((score) => new GameHighScore(this, score, users)) return res.scores.map((score) => new GameHighScore(this, score, peers))
} }
/** /**
@ -81,7 +79,7 @@ export async function getInlineGameHighScores(
{ connection } { connection }
) )
const { users } = createUsersChatsIndex(res) const peers = PeersIndex.from(res)
return res.scores.map((score) => new GameHighScore(this, score, users)) return res.scores.map((score) => new GameHighScore(this, score, peers))
} }

View file

@ -1,5 +1,5 @@
import { TelegramClient } from '../../client' import { TelegramClient } from '../../client'
import { Chat, MtTypeAssertionError } from '../../types' import { Chat } from '../../types'
import { assertIsUpdatesGroup } from '../../utils/updates-utils' import { assertIsUpdatesGroup } from '../../utils/updates-utils'
/** /**

View file

@ -1,8 +1,7 @@
import { TelegramClient } from '../../client' import { TelegramClient } from '../../client'
import { MaybeArray } from '@mtcute/core' import { MaybeArray } from '@mtcute/core'
import { Chat, InputPeerLike, MtTypeAssertionError } from '../../types' import { Chat, InputPeerLike } from '../../types'
import { normalizeToInputUser } from '../../utils/peer-utils' import { normalizeToInputUser } from '../../utils/peer-utils'
import { tl } from '@mtcute/tl'
import { assertIsUpdatesGroup } from '../../utils/updates-utils' import { assertIsUpdatesGroup } from '../../utils/updates-utils'
/** /**

View file

@ -1,5 +1,5 @@
import { TelegramClient } from '../../client' import { TelegramClient } from '../../client'
import { Chat, MtTypeAssertionError } from '../../types' import { Chat } from '../../types'
import { assertIsUpdatesGroup } from '../../utils/updates-utils' import { assertIsUpdatesGroup } from '../../utils/updates-utils'
/** /**

View file

@ -2,16 +2,15 @@ import { TelegramClient } from '../../client'
import { import {
InputPeerLike, InputPeerLike,
MtInvalidPeerTypeError, MtInvalidPeerTypeError,
ChatEvent, ChatEvent, PeersIndex,
} from '../../types' } from '../../types'
import { tl } from '@mtcute/tl' import { tl } from '@mtcute/tl'
import { MaybeArray } from '@mtcute/core' import { MaybeArray } from '@mtcute/core'
import bigInt from 'big-integer'
import { import {
createUsersChatsIndex,
normalizeToInputChannel, normalizeToInputChannel,
normalizeToInputUser, normalizeToInputUser,
} from '../../utils/peer-utils' } from '../../utils/peer-utils'
import Long from 'long'
/** /**
* Get chat event log ("Recent actions" in official * Get chat event log ("Recent actions" in official
@ -89,8 +88,8 @@ export async function* getChatEventLog(
if (!channel) throw new MtInvalidPeerTypeError(chatId, 'channel') if (!channel) throw new MtInvalidPeerTypeError(chatId, 'channel')
let current = 0 let current = 0
let maxId = params.maxId ?? bigInt.zero let maxId = params.maxId ?? Long.ZERO
const minId = params.minId ?? bigInt.zero const minId = params.minId ?? Long.ZERO
const query = params.query ?? '' const query = params.query ?? ''
const total = params.limit || Infinity const total = params.limit || Infinity
@ -216,12 +215,12 @@ export async function* getChatEventLog(
if (!res.events.length) break if (!res.events.length) break
const { users, chats } = createUsersChatsIndex(res) const peers = PeersIndex.from(res)
const last = res.events[res.events.length - 1] const last = res.events[res.events.length - 1]
maxId = last.id maxId = last.id
for (const evt of res.events) { for (const evt of res.events) {
const parsed = new ChatEvent(this, evt, users, chats) const parsed = new ChatEvent(this, evt, peers)
if ( if (
localFilter && localFilter &&

View file

@ -1,7 +1,6 @@
import { TelegramClient } from '../../client' import { TelegramClient } from '../../client'
import { InputPeerLike, MtInvalidPeerTypeError } from '../../types' import { InputPeerLike, MtInvalidPeerTypeError, PeersIndex } from '../../types'
import { import {
createUsersChatsIndex,
isInputPeerChannel, isInputPeerChannel,
isInputPeerChat, isInputPeerChat,
isInputPeerUser, isInputPeerUser,
@ -48,15 +47,15 @@ export async function getChatMember(
? [] ? []
: res.fullChat.participants.participants : res.fullChat.participants.participants
const { users } = createUsersChatsIndex(res) const peers = PeersIndex.from(res)
for (const m of members) { for (const m of members) {
if ( if (
(user._ === 'inputPeerSelf' && (user._ === 'inputPeerSelf' &&
(users[m.userId] as tl.RawUser).self) || (peers.user(m.userId) as tl.RawUser).self) ||
(user._ === 'inputPeerUser' && m.userId === user.userId) (user._ === 'inputPeerUser' && m.userId === user.userId)
) { ) {
return new ChatMember(this, m, users) return new ChatMember(this, m, peers)
} }
} }
@ -68,8 +67,8 @@ export async function getChatMember(
participant: user, participant: user,
}) })
const { users } = createUsersChatsIndex(res) const peers = PeersIndex.from(res)
return new ChatMember(this, res.participant, users) return new ChatMember(this, res.participant, peers)
} else throw new MtInvalidPeerTypeError(chatId, 'chat or channel') } else throw new MtInvalidPeerTypeError(chatId, 'chat or channel')
} }

View file

@ -2,10 +2,10 @@ import {
ChatMember, ChatMember,
InputPeerLike, InputPeerLike,
MtInvalidPeerTypeError, MtInvalidPeerTypeError,
PeersIndex,
} from '../../types' } from '../../types'
import { TelegramClient } from '../../client' import { TelegramClient } from '../../client'
import { import {
createUsersChatsIndex,
isInputPeerChannel, isInputPeerChannel,
isInputPeerChat, isInputPeerChat,
normalizeToInputChannel, normalizeToInputChannel,
@ -13,6 +13,7 @@ import {
import { assertTypeIs } from '../../utils/type-assertion' import { assertTypeIs } from '../../utils/type-assertion'
import { tl } from '@mtcute/tl' import { tl } from '@mtcute/tl'
import { ArrayWithTotal } from '../../types' import { ArrayWithTotal } from '../../types'
import Long from 'long'
/** /**
* Get a chunk of members of some chat. * Get a chunk of members of some chat.
@ -94,9 +95,11 @@ export async function getChatMembers(
if (params.offset) members = members.slice(params.offset) if (params.offset) members = members.slice(params.offset)
if (params.limit) members = members.slice(0, params.limit) if (params.limit) members = members.slice(0, params.limit)
const { users } = createUsersChatsIndex(res) const peers = PeersIndex.from(res)
const ret = members.map((m) => new ChatMember(this, m, users)) as ArrayWithTotal<ChatMember> const ret = members.map(
(m) => new ChatMember(this, m, peers)
) as ArrayWithTotal<ChatMember>
ret.total = ret.length ret.total = ret.length
return ret return ret
@ -140,7 +143,7 @@ export async function getChatMembers(
filter, filter,
offset: params.offset ?? 0, offset: params.offset ?? 0,
limit: params.limit ?? 200, limit: params.limit ?? 200,
hash: 0, hash: Long.ZERO,
}) })
assertTypeIs( assertTypeIs(
@ -149,9 +152,11 @@ export async function getChatMembers(
'channels.channelParticipants' 'channels.channelParticipants'
) )
const { users } = createUsersChatsIndex(res) const peers = PeersIndex.from(res)
const ret = res.participants.map((i) => new ChatMember(this, i, users)) as ArrayWithTotal<ChatMember> const ret = res.participants.map(
(i) => new ChatMember(this, i, peers)
) as ArrayWithTotal<ChatMember>
ret.total = res.count ret.total = res.count
return ret return ret
} }

View file

@ -1,5 +1,5 @@
import { TelegramClient } from '../../client' import { TelegramClient } from '../../client'
import { Chat, MtTypeAssertionError } from '../../types' import { Chat } from '../../types'
import { assertTypeIs } from '../../utils/type-assertion' import { assertTypeIs } from '../../utils/type-assertion'
import { getMarkedPeerId } from '@mtcute/core' import { getMarkedPeerId } from '@mtcute/core'
import { tl } from 'packages/tl' import { tl } from 'packages/tl'

View file

@ -1,10 +1,5 @@
import { TelegramClient } from '../../client' import { TelegramClient } from '../../client'
import { import { Chat, InputPeerLike, MtNotFoundError } from '../../types'
Chat,
InputPeerLike,
MtNotFoundError,
MtTypeAssertionError,
} from '../../types'
import { import {
INVITE_LINK_REGEX, INVITE_LINK_REGEX,
normalizeToInputChannel, normalizeToInputChannel,

View file

@ -1,5 +1,5 @@
import { TelegramClient } from '../../client' import { TelegramClient } from '../../client'
import { Chat, InputPeerLike, MtTypeAssertionError } from '../../types' import { Chat, InputPeerLike } from '../../types'
import { tl } from '@mtcute/tl' import { tl } from '@mtcute/tl'
import { assertIsUpdatesGroup } from '../../utils/updates-utils' import { assertIsUpdatesGroup } from '../../utils/updates-utils'

View file

@ -2,11 +2,9 @@ import { TelegramClient } from '../../client'
import { import {
InputPeerLike, InputPeerLike,
MtInvalidPeerTypeError, MtInvalidPeerTypeError,
MtTypeAssertionError,
User, User,
} from '../../types' } from '../../types'
import { normalizeToInputUser } from '../../utils/peer-utils' import { normalizeToInputUser } from '../../utils/peer-utils'
import { tl } from '@mtcute/tl'
import { assertIsUpdatesGroup } from '../../utils/updates-utils' import { assertIsUpdatesGroup } from '../../utils/updates-utils'
/** /**

View file

@ -3,11 +3,9 @@ import { MaybeArray } from '@mtcute/core'
import { import {
InputPeerLike, InputPeerLike,
MtInvalidPeerTypeError, MtInvalidPeerTypeError,
MtTypeAssertionError,
User, User,
} from '../../types' } from '../../types'
import { normalizeToInputUser } from '../../utils/peer-utils' import { normalizeToInputUser } from '../../utils/peer-utils'
import { tl } from '@mtcute/tl'
import { assertIsUpdatesGroup } from '../../utils/updates-utils' import { assertIsUpdatesGroup } from '../../utils/updates-utils'
/** /**

View file

@ -1,7 +1,7 @@
import { TelegramClient } from '../../client' import { TelegramClient } from '../../client'
import { User } from '../../types' import { User } from '../../types'
import { assertTypeIs } from '../../utils/type-assertion' import { assertTypeIs } from '../../utils/type-assertion'
import { tl } from '@mtcute/tl' import Long from 'long'
/** /**
* Get list of contacts from your Telegram contacts list. * Get list of contacts from your Telegram contacts list.
@ -10,7 +10,7 @@ import { tl } from '@mtcute/tl'
export async function getContacts(this: TelegramClient): Promise<User[]> { export async function getContacts(this: TelegramClient): Promise<User[]> {
const res = await this.call({ const res = await this.call({
_: 'contacts.getContacts', _: 'contacts.getContacts',
hash: 0, hash: Long.ZERO,
}) })
assertTypeIs('getContacts', res, 'contacts.contacts') assertTypeIs('getContacts', res, 'contacts.contacts')

View file

@ -1,7 +1,7 @@
import { TelegramClient } from '../../client' import { TelegramClient } from '../../client'
import { tl } from '@mtcute/tl' import { tl } from '@mtcute/tl'
import { PartialOnly } from '@mtcute/core' import { PartialOnly } from '@mtcute/core'
import bigInt from 'big-integer' import Long from 'long'
/** /**
* Import contacts to your Telegram contacts list. * Import contacts to your Telegram contacts list.
@ -13,11 +13,11 @@ export async function importContacts(
this: TelegramClient, this: TelegramClient,
contacts: PartialOnly<Omit<tl.RawInputPhoneContact, '_'>, 'clientId'>[] contacts: PartialOnly<Omit<tl.RawInputPhoneContact, '_'>, 'clientId'>[]
): Promise<tl.contacts.RawImportedContacts> { ): Promise<tl.contacts.RawImportedContacts> {
let seq = bigInt.zero let seq = Long.ZERO
const contactsNorm: tl.RawInputPhoneContact[] = contacts.map((input) => ({ const contactsNorm: tl.RawInputPhoneContact[] = contacts.map((input) => ({
_: 'inputPhoneContact', _: 'inputPhoneContact',
clientId: (seq = seq.plus(1)), clientId: (seq = seq.add(1)),
...input, ...input,
})) }))

View file

@ -2,12 +2,10 @@ import { TelegramClient } from '../../client'
import { import {
Dialog, Dialog,
MtArgumentError, MtArgumentError,
MtTypeAssertionError,
} from '../../types' } from '../../types'
import { normalizeDate } from '../../utils/misc-utils' import { normalizeDate } from '../../utils/misc-utils'
import { createUsersChatsIndex } from '../../utils/peer-utils'
import { tl } from '@mtcute/tl' import { tl } from '@mtcute/tl'
import { getMarkedPeerId } from '@mtcute/core' import Long from 'long'
/** /**
* Iterate over dialogs. * Iterate over dialogs.
@ -241,7 +239,7 @@ export async function* getDialogs(
offsetPeer, offsetPeer,
limit: chunkSize, limit: chunkSize,
hash: 0, hash: Long.ZERO,
}) })
) )
if (!dialogs.length) return if (!dialogs.length) return

View file

@ -1,7 +1,6 @@
import { TelegramClient } from '../../client' import { TelegramClient } from '../../client'
import { tl } from '@mtcute/tl' import { tl } from '@mtcute/tl'
import { Dialog, MtTypeAssertionError } from '../../types' import { Dialog, MtTypeAssertionError, PeersIndex } from '../../types'
import { createUsersChatsIndex } from '../../utils/peer-utils'
import { getMarkedPeerId } from '@mtcute/core' import { getMarkedPeerId } from '@mtcute/core'
/** @internal */ /** @internal */
@ -16,7 +15,7 @@ export function _parseDialogs(
'messages.dialogsNotModified' 'messages.dialogsNotModified'
) )
const { users, chats } = createUsersChatsIndex(res) const peers = PeersIndex.from(res)
const messages: Record<number, tl.TypeMessage> = {} const messages: Record<number, tl.TypeMessage> = {}
res.messages.forEach((msg) => { res.messages.forEach((msg) => {
@ -27,7 +26,5 @@ export function _parseDialogs(
return res.dialogs return res.dialogs
.filter((it) => it._ === 'dialog') .filter((it) => it._ === 'dialog')
.map( .map((it) => new Dialog(this, it as tl.RawDialog, peers, messages))
(it) => new Dialog(this, it as tl.RawDialog, users, chats, messages)
)
} }

View file

@ -1,10 +1,10 @@
import { TelegramConnection } from '@mtcute/core' import { SessionConnection } from '@mtcute/core'
import { TelegramClient } from '../../client' import { TelegramClient } from '../../client'
// @extension // @extension
interface FilesExtension { interface FilesExtension {
_downloadConnections: Record<number, TelegramConnection> _downloadConnections: Record<number, SessionConnection>
} }
// @initialize // @initialize

View file

@ -2,7 +2,6 @@ import { TelegramClient } from '../../client'
import { import {
InputMediaLike, InputMediaLike,
isUploadedFile, isUploadedFile,
MtArgumentError,
UploadFileLike, UploadFileLike,
} from '../../types' } from '../../types'
import { tl } from '@mtcute/tl' import { tl } from '@mtcute/tl'
@ -14,9 +13,9 @@ import {
} from '@mtcute/file-id' } from '@mtcute/file-id'
import { extractFileName } from '../../utils/file-utils' import { extractFileName } from '../../utils/file-utils'
import { assertTypeIs } from '../../utils/type-assertion' import { assertTypeIs } from '../../utils/type-assertion'
import bigInt from 'big-integer'
import { normalizeDate } from '../../utils/misc-utils' import { normalizeDate } from '../../utils/misc-utils'
import { encodeWaveform } from '../../utils/voice-utils' import { encodeWaveform } from '../../utils/voice-utils'
import Long from 'long'
/** /**
* Normalize an {@link InputMediaLike} to `InputMedia`, * Normalize an {@link InputMediaLike} to `InputMedia`,
@ -182,7 +181,7 @@ export async function _normalizeInputMedia(
poll: { poll: {
_: 'poll', _: 'poll',
closed: media.closed, closed: media.closed,
id: bigInt.zero, id: Long.ZERO,
publicVoters: media.public, publicVoters: media.public,
multipleChoice: media.multiple, multipleChoice: media.multiple,
quiz: media.type === 'quiz', quiz: media.type === 'quiz',

View file

@ -7,11 +7,11 @@ import {
import type { ReadStream } from 'fs' import type { ReadStream } from 'fs'
import { Readable } from 'stream' import { Readable } from 'stream'
import { determinePartSize, isProbablyPlainText } from '../../utils/file-utils' import { determinePartSize, isProbablyPlainText } from '../../utils/file-utils'
import { randomUlong } from '../../utils/misc-utils'
import { fromBuffer } from 'file-type' import { fromBuffer } from 'file-type'
import { tl } from '@mtcute/tl' import { tl } from '@mtcute/tl'
import { MtArgumentError, UploadFileLike, UploadedFile } from '../../types' import { MtArgumentError, UploadFileLike, UploadedFile } from '../../types'
import { TelegramClient } from '../../client' import { TelegramClient } from '../../client'
import { randomLong } from '@mtcute/core'
let fs: any = null let fs: any = null
let path: any = null let path: any = null
@ -200,7 +200,7 @@ export async function uploadFile(
const hash = this._crypto.createMd5() const hash = this._crypto.createMd5()
const partCount = ~~((fileSize + partSize - 1) / partSize) const partCount = ~~((fileSize + partSize - 1) / partSize)
this._baseLog.debug( this.log.debug(
'uploading %d bytes file in %d chunks, each %d bytes', 'uploading %d bytes file in %d chunks, each %d bytes',
fileSize, fileSize,
partCount, partCount,
@ -209,7 +209,7 @@ export async function uploadFile(
// why is the file id generated by the client? // why is the file id generated by the client?
// isn't the server supposed to generate it and handle collisions? // isn't the server supposed to generate it and handle collisions?
const fileId = randomUlong() const fileId = randomLong()
let pos = 0 let pos = 0
for (let idx = 0; idx < partCount; idx++) { for (let idx = 0; idx < partCount; idx++) {

View file

@ -1,6 +1,5 @@
import { TelegramClient } from '../../client' import { TelegramClient } from '../../client'
import { ChatInviteLink, InputPeerLike } from '../../types' import { ChatInviteLink, InputPeerLike, PeersIndex } from '../../types'
import { createUsersChatsIndex } from '../../utils/peer-utils'
import { normalizeDate } from '../../utils/misc-utils' import { normalizeDate } from '../../utils/misc-utils'
/** /**
@ -43,7 +42,7 @@ export async function editInviteLink(
usageLimit: params.usageLimit, usageLimit: params.usageLimit,
}) })
const { users } = createUsersChatsIndex(res) const peers = PeersIndex.from(res)
return new ChatInviteLink(this, res.invite, users) return new ChatInviteLink(this, res.invite, peers)
} }

View file

@ -1,6 +1,5 @@
import { TelegramClient } from '../../client' import { TelegramClient } from '../../client'
import { ChatInviteLink, InputPeerLike, User } from '../../types' import { ChatInviteLink, InputPeerLike, PeersIndex, User } from '../../types'
import { createUsersChatsIndex } from '../../utils/peer-utils'
import { tl } from '@mtcute/tl' import { tl } from '@mtcute/tl'
/** /**
@ -40,18 +39,18 @@ export async function* getInviteLinkMembers(
if (!res.importers.length) break if (!res.importers.length) break
const { users } = createUsersChatsIndex(res) const peers = PeersIndex.from(res)
const last = res.importers[res.importers.length - 1] const last = res.importers[res.importers.length - 1]
offsetDate = last.date offsetDate = last.date
offsetUser = { offsetUser = {
_: 'inputUser', _: 'inputUser',
userId: last.userId, userId: last.userId,
accessHash: (users[last.userId] as tl.RawUser).accessHash!, accessHash: (peers.user(last.userId) as tl.RawUser).accessHash!,
} }
for (const it of res.importers) { for (const it of res.importers) {
const user = new User(this, users[it.userId]) const user = new User(this, peers.user(it.userId))
yield { yield {
user, user,

View file

@ -1,6 +1,5 @@
import { TelegramClient } from '../../client' import { TelegramClient } from '../../client'
import { ChatInviteLink, InputPeerLike } from '../../types' import { ChatInviteLink, InputPeerLike, PeersIndex } from '../../types'
import { createUsersChatsIndex } from '../../utils/peer-utils'
/** /**
* Get detailed information about an invite link * Get detailed information about an invite link
@ -20,7 +19,7 @@ export async function getInviteLink(
link, link,
}) })
const { users } = createUsersChatsIndex(res) const peers = PeersIndex.from(res)
return new ChatInviteLink(this, res.invite, users) return new ChatInviteLink(this, res.invite, peers)
} }

View file

@ -3,11 +3,9 @@ import {
ChatInviteLink, ChatInviteLink,
InputPeerLike, InputPeerLike,
MtInvalidPeerTypeError, MtInvalidPeerTypeError,
PeersIndex,
} from '../../types' } from '../../types'
import { import { normalizeToInputUser } from '../../utils/peer-utils'
createUsersChatsIndex,
normalizeToInputUser,
} from '../../utils/peer-utils'
import { tl } from '@mtcute/tl' import { tl } from '@mtcute/tl'
/** /**
@ -74,14 +72,14 @@ export async function* getInviteLinks(
if (!res.invites.length) break if (!res.invites.length) break
const { users } = createUsersChatsIndex(res) const peers = PeersIndex.from(res)
const last = res.invites[res.invites.length - 1] const last = res.invites[res.invites.length - 1]
offsetDate = last.date offsetDate = last.date
offsetLink = last.link offsetLink = last.link
for (const it of res.invites) { for (const it of res.invites) {
yield new ChatInviteLink(this, it, users) yield new ChatInviteLink(this, it, peers)
} }
current += res.invites.length current += res.invites.length

View file

@ -2,9 +2,8 @@ import { TelegramClient } from '../../client'
import { import {
ChatInviteLink, ChatInviteLink,
InputPeerLike, InputPeerLike,
MtTypeAssertionError, MtTypeAssertionError, PeersIndex,
} from '../../types' } from '../../types'
import { createUsersChatsIndex } from '../../utils/peer-utils'
/** /**
* Get primary invite link of a chat * Get primary invite link of a chat
@ -31,7 +30,7 @@ export async function getPrimaryInviteLink(
'false' 'false'
) )
const { users } = createUsersChatsIndex(res) const peers = PeersIndex.from(res)
return new ChatInviteLink(this, res.invites[0], users) return new ChatInviteLink(this, res.invites[0], peers)
} }

View file

@ -1,6 +1,5 @@
import { TelegramClient } from '../../client' import { TelegramClient } from '../../client'
import { ChatInviteLink, InputPeerLike } from '../../types' import { ChatInviteLink, InputPeerLike, PeersIndex } from '../../types'
import { createUsersChatsIndex } from '../../utils/peer-utils'
/** /**
* Revoke an invite link. * Revoke an invite link.
@ -25,12 +24,12 @@ export async function revokeInviteLink(
revoked: true, revoked: true,
}) })
const { users } = createUsersChatsIndex(res) const peers = PeersIndex.from(res)
const invite = const invite =
res._ === 'messages.exportedChatInviteReplaced' res._ === 'messages.exportedChatInviteReplaced'
? res.newInvite ? res.newInvite
: res.invite : res.invite
return new ChatInviteLink(this, invite, users) return new ChatInviteLink(this, invite, peers)
} }

View file

@ -1,9 +1,8 @@
import { TelegramClient } from '../../client' import { TelegramClient } from '../../client'
import { InputPeerLike, MtTypeAssertionError, Poll } from '../../types' import { InputPeerLike, MtTypeAssertionError, PeersIndex, Poll } from '../../types'
import { createUsersChatsIndex } from '../../utils/peer-utils'
import bigInt from 'big-integer'
import { assertTypeIs } from '../../utils/type-assertion' import { assertTypeIs } from '../../utils/type-assertion'
import { assertIsUpdatesGroup } from '../../utils/updates-utils' import { assertIsUpdatesGroup } from '../../utils/updates-utils'
import Long from 'long'
/** /**
* Close a poll sent by you. * Close a poll sent by you.
@ -28,7 +27,7 @@ export async function closePoll(
_: 'inputMediaPoll', _: 'inputMediaPoll',
poll: { poll: {
_: 'poll', _: 'poll',
id: bigInt.zero, id: Long.ZERO,
closed: true, closed: true,
question: '', question: '',
answers: [], answers: [],
@ -54,7 +53,7 @@ export async function closePoll(
) )
} }
const { users } = createUsersChatsIndex(res) const peers = PeersIndex.from(res)
return new Poll(this, upd.poll, users, upd.results) return new Poll(this, upd.poll, peers, upd.results)
} }

View file

@ -1,11 +1,6 @@
import { TelegramClient } from '../../client' import { TelegramClient } from '../../client'
import { InputPeerLike } from '../../types' import { InputPeerLike } from '../../types'
import { MaybeArray } from '@mtcute/core' import { MaybeArray } from '@mtcute/core'
import {
isInputPeerChannel,
normalizeToInputChannel,
} from '../../utils/peer-utils'
import { createDummyUpdate } from '../../utils/updates-utils'
/** /**
* Delete scheduled messages. * Delete scheduled messages.

View file

@ -1,7 +1,6 @@
import { TelegramClient } from '../../client' import { TelegramClient } from '../../client'
import { tl } from '@mtcute/tl' import { tl } from '@mtcute/tl'
import { Message, MtTypeAssertionError } from '../../types' import { Message, MtTypeAssertionError, PeersIndex } from '../../types'
import { createUsersChatsIndex } from '../../utils/peer-utils'
import { assertIsUpdatesGroup } from '../../utils/updates-utils' import { assertIsUpdatesGroup } from '../../utils/updates-utils'
/** @internal */ /** @internal */
@ -24,13 +23,12 @@ export function _findMessageInUpdate(
u._ === 'updateNewChannelMessage' || u._ === 'updateNewChannelMessage' ||
u._ === 'updateNewScheduledMessage')) u._ === 'updateNewScheduledMessage'))
) { ) {
const { users, chats } = createUsersChatsIndex(res) const peers = PeersIndex.from(res)
return new Message( return new Message(
this, this,
u.message, u.message,
users, peers,
chats,
u._ === 'updateNewScheduledMessage' u._ === 'updateNewScheduledMessage'
) )
} }

View file

@ -5,12 +5,11 @@ import {
InputPeerLike, InputPeerLike,
Message, Message,
MtArgumentError, MtArgumentError,
MtTypeAssertionError, PeersIndex,
} from '../../types' } from '../../types'
import { MaybeArray } from '@mtcute/core' import { MaybeArray, randomLong } from '@mtcute/core'
import { tl } from '@mtcute/tl' import { tl } from '@mtcute/tl'
import { createUsersChatsIndex } from '../../utils/peer-utils' import { normalizeDate } from '../../utils/misc-utils'
import { normalizeDate, randomUlong } from '../../utils/misc-utils'
import { assertIsUpdatesGroup } from '../../utils/updates-utils' import { assertIsUpdatesGroup } from '../../utils/updates-utils'
/** /**
@ -234,7 +233,7 @@ export async function forwardMessages(
silent: params.silent, silent: params.silent,
scheduleDate: normalizeDate(params.schedule), scheduleDate: normalizeDate(params.schedule),
randomId: [...Array((messages as number[]).length)].map(() => randomId: [...Array((messages as number[]).length)].map(() =>
randomUlong() randomLong()
), ),
}) })
@ -242,7 +241,7 @@ export async function forwardMessages(
this._handleUpdate(res, true) this._handleUpdate(res, true)
const { users, chats } = createUsersChatsIndex(res) const peers = PeersIndex.from(res)
const forwarded: Message[] = [] const forwarded: Message[] = []
res.updates.forEach((upd) => { res.updates.forEach((upd) => {
@ -250,7 +249,14 @@ export async function forwardMessages(
case 'updateNewMessage': case 'updateNewMessage':
case 'updateNewChannelMessage': case 'updateNewChannelMessage':
case 'updateNewScheduledMessage': case 'updateNewScheduledMessage':
forwarded.push(new Message(this, upd.message, users, chats)) forwarded.push(
new Message(
this,
upd.message,
peers,
upd._ === 'updateNewScheduledMessage'
)
)
break break
} }
}) })

View file

@ -1,7 +1,6 @@
import { TelegramClient } from '../../client' import { TelegramClient } from '../../client'
import { InputPeerLike, Message } from '../../types' import { InputPeerLike, Message, PeersIndex } from '../../types'
import { tl } from '@mtcute/tl' import { tl } from '@mtcute/tl'
import { createUsersChatsIndex } from '../../utils/peer-utils'
/** @internal */ /** @internal */
export async function _getDiscussionMessage( export async function _getDiscussionMessage(
@ -71,7 +70,7 @@ export async function getDiscussionMessage(
return null return null
const msg = res.messages[0] const msg = res.messages[0]
const { users, chats } = createUsersChatsIndex(res) const peers = PeersIndex.from(res)
return new Message(this, msg, users, chats) return new Message(this, msg, peers)
} }

View file

@ -1,7 +1,7 @@
import { TelegramClient } from '../../client' import { TelegramClient } from '../../client'
import { InputPeerLike, Message, MtTypeAssertionError } from '../../types' import { InputPeerLike, Message, MtTypeAssertionError, PeersIndex } from '../../types'
import { createUsersChatsIndex } from '../../utils/peer-utils'
import { normalizeDate } from '../../utils/misc-utils' import { normalizeDate } from '../../utils/misc-utils'
import Long from 'long'
/** /**
* Retrieve a chunk of the chat history. * Retrieve a chunk of the chat history.
@ -70,7 +70,7 @@ export async function getHistory(
limit, limit,
maxId: 0, maxId: 0,
minId: 0, minId: 0,
hash: 0, hash: Long.ZERO,
}) })
if (res._ === 'messages.messagesNotModified') if (res._ === 'messages.messagesNotModified')
@ -80,11 +80,11 @@ export async function getHistory(
res._ res._
) )
const { users, chats } = createUsersChatsIndex(res) const peers = PeersIndex.from(res)
const msgs = res.messages const msgs = res.messages
.filter((msg) => msg._ !== 'messageEmpty') .filter((msg) => msg._ !== 'messageEmpty')
.map((msg) => new Message(this, msg, users, chats)) .map((msg) => new Message(this, msg, peers))
if (params.reverse) msgs.reverse() if (params.reverse) msgs.reverse()

View file

@ -1,10 +1,7 @@
import { TelegramClient } from '../../client' import { TelegramClient } from '../../client'
import { MaybeArray } from '@mtcute/core' import { MaybeArray } from '@mtcute/core'
import {
createUsersChatsIndex,
} from '../../utils/peer-utils'
import { tl } from '@mtcute/tl' import { tl } from '@mtcute/tl'
import { Message, MtTypeAssertionError } from '../../types' import { Message, MtTypeAssertionError, PeersIndex } from '../../types'
/** /**
* Get a single message from PM or legacy group by its ID. * Get a single message from PM or legacy group by its ID.
@ -73,14 +70,13 @@ export async function getMessagesUnsafe(
res._ res._
) )
const { users, chats } = createUsersChatsIndex(res) const peers = PeersIndex.from(res)
const ret = res.messages const ret = res.messages.map((msg) => {
.map((msg) => { if (msg._ === 'messageEmpty') return null
if (msg._ === 'messageEmpty') return null
return new Message(this, msg, users, chats) return new Message(this, msg, peers)
}) })
return isSingle ? ret[0] : ret return isSingle ? ret[0] : ret
} }

View file

@ -1,12 +1,11 @@
import { TelegramClient } from '../../client' import { TelegramClient } from '../../client'
import { MaybeArray } from '@mtcute/core' import { MaybeArray } from '@mtcute/core'
import { import {
createUsersChatsIndex,
isInputPeerChannel, isInputPeerChannel,
normalizeToInputChannel, normalizeToInputChannel,
} from '../../utils/peer-utils' } from '../../utils/peer-utils'
import { tl } from '@mtcute/tl' import { tl } from '@mtcute/tl'
import { Message, InputPeerLike, MtTypeAssertionError } from '../../types' import { Message, InputPeerLike, MtTypeAssertionError, PeersIndex } from '../../types'
/** /**
* Get a single message in chat by its ID * Get a single message in chat by its ID
@ -84,7 +83,7 @@ export async function getMessages(
res._ res._
) )
const { users, chats } = createUsersChatsIndex(res) const peers = PeersIndex.from(res)
const ret = res.messages.map((msg) => { const ret = res.messages.map((msg) => {
if (msg._ === 'messageEmpty') return null if (msg._ === 'messageEmpty') return null
@ -109,7 +108,7 @@ export async function getMessages(
} }
} }
return new Message(this, msg, users, chats) return new Message(this, msg, peers)
}) })
return isSingle ? ret[0] : ret return isSingle ? ret[0] : ret

View file

@ -1,12 +1,11 @@
import { TelegramClient } from '../../client' import { TelegramClient } from '../../client'
import { MaybeArray } from '@mtcute/core' import { MaybeArray } from '@mtcute/core'
import { import {
createUsersChatsIndex, Message,
isInputPeerChannel, InputPeerLike,
normalizeToInputChannel, MtTypeAssertionError,
} from '../../utils/peer-utils' PeersIndex,
import { tl } from '@mtcute/tl' } from '../../types'
import { Message, InputPeerLike, MtTypeAssertionError } from '../../types'
/** /**
* Get a single scheduled message in chat by its ID * Get a single scheduled message in chat by its ID
@ -60,12 +59,12 @@ export async function getScheduledMessages(
res._ res._
) )
const { users, chats } = createUsersChatsIndex(res) const peers = PeersIndex.from(res)
const ret = res.messages.map((msg) => { const ret = res.messages.map((msg) => {
if (msg._ === 'messageEmpty') return null if (msg._ === 'messageEmpty') return null
return new Message(this, msg, users, chats) return new Message(this, msg, peers, true)
}) })
return isSingle ? ret[0] : ret return isSingle ? ret[0] : ret

View file

@ -1,11 +1,11 @@
import { TelegramClient } from '../../client' import { TelegramClient } from '../../client'
import { tl } from '@mtcute/tl' import { tl } from '@mtcute/tl'
import { TelegramConnection } from '@mtcute/core' import { SessionConnection } from '@mtcute/core'
import { parseInlineMessageId } from '../../utils/inline-utils' import { parseInlineMessageId } from '../../utils/inline-utils'
// @extension // @extension
interface InlineExtension { interface InlineExtension {
_connectionsForInline: Record<number, TelegramConnection> _connectionsForInline: Record<number, SessionConnection>
} }
// @initialize // @initialize
@ -17,7 +17,7 @@ function _initializeInline(this: TelegramClient) {
export async function _normalizeInline( export async function _normalizeInline(
this: TelegramClient, this: TelegramClient,
id: string | tl.TypeInputBotInlineMessageID id: string | tl.TypeInputBotInlineMessageID
): Promise<[tl.TypeInputBotInlineMessageID, TelegramConnection]> { ): Promise<[tl.TypeInputBotInlineMessageID, SessionConnection]> {
if (typeof id === 'string') { if (typeof id === 'string') {
id = parseInlineMessageId(id) id = parseInlineMessageId(id)
} }

View file

@ -1,7 +1,6 @@
import { TelegramClient } from '../../client' import { TelegramClient } from '../../client'
import { Message, MtTypeAssertionError } from '../../types' import { Message, MtTypeAssertionError, PeersIndex } from '../../types'
import { tl } from '@mtcute/tl' import { tl } from '@mtcute/tl'
import { createUsersChatsIndex } from '../../utils/peer-utils'
import { SearchFilters } from '../../types' import { SearchFilters } from '../../types'
/** /**
@ -77,11 +76,11 @@ export async function* searchGlobal(
res._ res._
) )
const { users, chats } = createUsersChatsIndex(res) const peers = PeersIndex.from(res)
const msgs = res.messages const msgs = res.messages
.filter((msg) => msg._ !== 'messageEmpty') .filter((msg) => msg._ !== 'messageEmpty')
.map((msg) => new Message(this, msg, users, chats)) .map((msg) => new Message(this, msg, peers))
if (!msgs.length) break if (!msgs.length) break

View file

@ -1,8 +1,8 @@
import { TelegramClient } from '../../client' import { TelegramClient } from '../../client'
import { InputPeerLike, Message, MtTypeAssertionError } from '../../types' import { InputPeerLike, Message, MtTypeAssertionError, PeersIndex } from '../../types'
import { tl } from '@mtcute/tl' import { tl } from '@mtcute/tl'
import { createUsersChatsIndex } from '../../utils/peer-utils'
import { SearchFilters } from '../../types' import { SearchFilters } from '../../types'
import Long from 'long'
/** /**
* Search for messages inside a specific chat * Search for messages inside a specific chat
@ -88,7 +88,7 @@ export async function* searchMessages(
minId: 0, minId: 0,
maxId: 0, maxId: 0,
fromId: fromUser, fromId: fromUser,
hash: 0, hash: Long.ZERO,
}) })
if (res._ === 'messages.messagesNotModified') if (res._ === 'messages.messagesNotModified')
@ -98,11 +98,11 @@ export async function* searchMessages(
res._ res._
) )
const { users, chats } = createUsersChatsIndex(res) const peers = PeersIndex.from(res)
const msgs = res.messages const msgs = res.messages
.filter((msg) => msg._ !== 'messageEmpty') .filter((msg) => msg._ !== 'messageEmpty')
.map((msg) => new Message(this, msg, users, chats)) .map((msg) => new Message(this, msg, peers))
if (!msgs.length) break if (!msgs.length) break

View file

@ -3,18 +3,17 @@ import {
BotKeyboard, InputFileLike, BotKeyboard, InputFileLike,
InputMediaLike, InputMediaLike,
InputPeerLike, InputPeerLike,
Message, MtArgumentError, Message, MtArgumentError, PeersIndex,
ReplyMarkup, ReplyMarkup,
} from '../../types' } from '../../types'
import { import {
normalizeDate, normalizeDate,
normalizeMessageId, normalizeMessageId,
randomUlong,
} from '../../utils/misc-utils' } from '../../utils/misc-utils'
import { tl } from '@mtcute/tl' import { tl } from '@mtcute/tl'
import { assertIsUpdatesGroup } from '../../utils/updates-utils' import { assertIsUpdatesGroup } from '../../utils/updates-utils'
import { createUsersChatsIndex } from '../../utils/peer-utils'
import { MessageNotFoundError } from '@mtcute/tl/errors' import { MessageNotFoundError } from '@mtcute/tl/errors'
import { randomLong } from '@mtcute/core'
/** /**
* Send a group of media. * Send a group of media.
@ -165,7 +164,7 @@ export async function sendMediaGroup(
multiMedia.push({ multiMedia.push({
_: 'inputSingleMedia', _: 'inputSingleMedia',
randomId: randomUlong(), randomId: randomLong(),
media: inputMedia, media: inputMedia,
message, message,
entities, entities,
@ -178,7 +177,7 @@ export async function sendMediaGroup(
multiMedia, multiMedia,
silent: params.silent, silent: params.silent,
replyToMsgId: replyTo, replyToMsgId: replyTo,
randomId: randomUlong(), randomId: randomLong(),
scheduleDate: normalizeDate(params.schedule), scheduleDate: normalizeDate(params.schedule),
replyMarkup, replyMarkup,
clearDraft: params.clearDraft, clearDraft: params.clearDraft,
@ -187,7 +186,7 @@ export async function sendMediaGroup(
assertIsUpdatesGroup('_findMessageInUpdate', res) assertIsUpdatesGroup('_findMessageInUpdate', res)
this._handleUpdate(res, true) this._handleUpdate(res, true)
const { users, chats } = createUsersChatsIndex(res) const peers = PeersIndex.from(res)
const msgs = res.updates const msgs = res.updates
.filter( .filter(
@ -201,8 +200,7 @@ export async function sendMediaGroup(
new Message( new Message(
this, this,
(u as any).message, (u as any).message,
users, peers,
chats,
u._ === 'updateNewScheduledMessage' u._ === 'updateNewScheduledMessage'
) )
) )

View file

@ -1,18 +1,17 @@
import { TelegramClient } from '../../client' import { TelegramClient } from '../../client'
import { import {
BotKeyboard, FormattedString, BotKeyboard,
FormattedString,
InputMediaLike, InputMediaLike,
InputPeerLike, InputPeerLike,
Message, MtArgumentError, Message,
MtArgumentError,
ReplyMarkup, ReplyMarkup,
} from '../../types' } from '../../types'
import { import { normalizeDate, normalizeMessageId } from '../../utils/misc-utils'
normalizeDate,
normalizeMessageId,
randomUlong,
} from '../../utils/misc-utils'
import { tl } from '@mtcute/tl' import { tl } from '@mtcute/tl'
import { MessageNotFoundError } from '@mtcute/tl/errors' import { MessageNotFoundError } from '@mtcute/tl/errors'
import { randomLong } from '@mtcute/core'
/** /**
* Send a single media (a photo or a document-based media) * Send a single media (a photo or a document-based media)
@ -157,8 +156,7 @@ export async function sendMedia(
const msg = await this.getMessages(peer, replyTo) const msg = await this.getMessages(peer, replyTo)
if (!msg) if (!msg) throw new MessageNotFoundError()
throw new MessageNotFoundError()
} }
const res = await this.call({ const res = await this.call({
@ -167,7 +165,7 @@ export async function sendMedia(
media: inputMedia, media: inputMedia,
silent: params.silent, silent: params.silent,
replyToMsgId: replyTo, replyToMsgId: replyTo,
randomId: randomUlong(), randomId: randomLong(),
scheduleDate: normalizeDate(params.schedule), scheduleDate: normalizeDate(params.schedule),
replyMarkup, replyMarkup,
message, message,

View file

@ -1,8 +1,7 @@
import { InputPeerLike, Message } from '../../types' import { InputPeerLike, Message, PeersIndex } from '../../types'
import { MaybeArray } from '@mtcute/core' import { MaybeArray } from '@mtcute/core'
import { TelegramClient } from '../../client' import { TelegramClient } from '../../client'
import { assertIsUpdatesGroup } from '../../utils/updates-utils' import { assertIsUpdatesGroup } from '../../utils/updates-utils'
import { createUsersChatsIndex } from '../../utils/peer-utils'
/** /**
* Send s previously scheduled message. * Send s previously scheduled message.
@ -56,7 +55,7 @@ export async function sendScheduled(
assertIsUpdatesGroup('sendScheduled', res) assertIsUpdatesGroup('sendScheduled', res)
this._handleUpdate(res, true) this._handleUpdate(res, true)
const { users, chats } = createUsersChatsIndex(res) const peers = PeersIndex.from(res)
const msgs = res.updates const msgs = res.updates
.filter( .filter(
@ -69,8 +68,7 @@ export async function sendScheduled(
new Message( new Message(
this, this,
(u as any).message, (u as any).message,
users, peers
chats
) )
) )

View file

@ -4,19 +4,16 @@ import { inputPeerToPeer } from '../../utils/peer-utils'
import { import {
normalizeDate, normalizeDate,
normalizeMessageId, normalizeMessageId,
randomUlong,
} from '../../utils/misc-utils' } from '../../utils/misc-utils'
import { import {
InputPeerLike, InputPeerLike,
Message, Message,
BotKeyboard, BotKeyboard,
ReplyMarkup, ReplyMarkup,
UsersIndex,
MtTypeAssertionError, MtTypeAssertionError,
ChatsIndex, MtArgumentError, FormattedString, PeersIndex,
MtArgumentError, FormattedString,
} from '../../types' } from '../../types'
import { getMarkedPeerId, MessageNotFoundError } from '@mtcute/core' import { getMarkedPeerId, MessageNotFoundError, randomLong } from '@mtcute/core'
import { createDummyUpdate } from '../../utils/updates-utils' import { createDummyUpdate } from '../../utils/updates-utils'
/** /**
@ -143,7 +140,7 @@ export async function sendText(
noWebpage: params.disableWebPreview, noWebpage: params.disableWebPreview,
silent: params.silent, silent: params.silent,
replyToMsgId: replyTo, replyToMsgId: replyTo,
randomId: randomUlong(), randomId: randomLong(),
scheduleDate: normalizeDate(params.schedule), scheduleDate: normalizeDate(params.schedule),
replyMarkup, replyMarkup,
message, message,
@ -170,8 +167,7 @@ export async function sendText(
this._date = res.date this._date = res.date
this._handleUpdate(createDummyUpdate(res.pts, res.ptsCount)) this._handleUpdate(createDummyUpdate(res.pts, res.ptsCount))
const users: UsersIndex = {} const peers = new PeersIndex()
const chats: ChatsIndex = {}
const fetchPeer = async ( const fetchPeer = async (
peer: tl.TypePeer | tl.TypeInputPeer peer: tl.TypePeer | tl.TypeInputPeer
@ -206,13 +202,13 @@ export async function sendText(
switch (cached._) { switch (cached._) {
case 'user': case 'user':
users[cached.id] = cached peers.users[cached.id] = cached
break break
case 'chat': case 'chat':
case 'chatForbidden': case 'chatForbidden':
case 'channel': case 'channel':
case 'channelForbidden': case 'channelForbidden':
chats[cached.id] = cached peers.chats[cached.id] = cached
break break
default: default:
throw new MtTypeAssertionError( throw new MtTypeAssertionError(
@ -226,7 +222,7 @@ export async function sendText(
await fetchPeer(peer) await fetchPeer(peer)
await fetchPeer(msg.fromId!) await fetchPeer(msg.fromId!)
const ret = new Message(this, msg, users, chats) const ret = new Message(this, msg, peers)
this._pushConversationMessage(ret) this._pushConversationMessage(ret)
return ret return ret
} }

View file

@ -3,10 +3,10 @@ import {
InputPeerLike, InputPeerLike,
MtArgumentError, MtArgumentError,
MtTypeAssertionError, MtTypeAssertionError,
PeersIndex,
Poll, Poll,
} from '../../types' } from '../../types'
import { MaybeArray, MessageNotFoundError } from '@mtcute/core' import { MaybeArray, MessageNotFoundError } from '@mtcute/core'
import { createUsersChatsIndex } from '../../utils/peer-utils'
import { assertTypeIs } from '../../utils/type-assertion' import { assertTypeIs } from '../../utils/type-assertion'
import { assertIsUpdatesGroup } from '../../utils/updates-utils' import { assertIsUpdatesGroup } from '../../utils/updates-utils'
@ -74,7 +74,7 @@ export async function sendVote(
) )
} }
const { users } = createUsersChatsIndex(res) const peers = PeersIndex.from(res)
return new Poll(this, upd.poll, users, upd.results) return new Poll(this, upd.poll, peers, upd.results)
} }

View file

@ -1,5 +1,5 @@
import { TelegramClient } from '../../client' import { TelegramClient } from '../../client'
import { InputFileLike, InputStickerSetItem, StickerSet } from '../../types' import { InputStickerSetItem, StickerSet } from '../../types'
import { tl } from '@mtcute/tl' import { tl } from '@mtcute/tl'
const MASK_POS = { const MASK_POS = {

View file

@ -1,6 +1,7 @@
import { TelegramClient } from '../../client' import { TelegramClient } from '../../client'
import { StickerSet } from '../../types' import { StickerSet } from '../../types'
import { assertTypeIs } from '../../utils/type-assertion' import { assertTypeIs } from '../../utils/type-assertion'
import Long from 'long'
/** /**
* Get a list of all installed sticker packs * Get a list of all installed sticker packs
@ -17,7 +18,7 @@ export async function getInstalledStickers(
): Promise<StickerSet[]> { ): Promise<StickerSet[]> {
const res = await this.call({ const res = await this.call({
_: 'messages.getAllStickers', _: 'messages.getAllStickers',
hash: 0, hash: Long.ZERO,
}) })
assertTypeIs('getInstalledStickers', res, 'messages.allStickers') assertTypeIs('getInstalledStickers', res, 'messages.allStickers')

File diff suppressed because it is too large Load diff

View file

@ -2,6 +2,7 @@ import { InputPeerLike, MtInvalidPeerTypeError } from '../../types'
import { TelegramClient } from '../../client' import { TelegramClient } from '../../client'
import { Chat } from '../../types' import { Chat } from '../../types'
import { normalizeToInputUser } from '../../utils/peer-utils' import { normalizeToInputUser } from '../../utils/peer-utils'
import Long from 'long'
/** /**
* Get a list of common chats you have with a given user * Get a list of common chats you have with a given user

View file

@ -15,9 +15,20 @@ export function getMe(this: TelegramClient): Promise<User> {
_: 'inputUserSelf', _: 'inputUserSelf',
}, },
], ],
}).then(([user]) => { }).then(async ([user]) => {
assertTypeIs('getMe (@ users.getUsers)', user, 'user') assertTypeIs('getMe (@ users.getUsers)', user, 'user')
if (this._userId !== user.id) {
// there is such possibility, e.g. when
// using a string session without `self`,
// or logging out and re-logging in
// we need to update the fields accordingly,
// and force-save the session
this._userId = user.id
this._isBot = !!user.bot
await this._saveStorage()
}
this._selfUsername = user.username ?? null this._selfUsername = user.username ?? null
return new User(this, user) return new User(this, user)

View file

@ -1,6 +1,4 @@
import { TelegramClient } from '../../client' import { TelegramClient } from '../../client'
import { User } from '../../types'
import { assertTypeIs } from '../../utils/type-assertion'
/** /**
* Get currently authorized user's username. * Get currently authorized user's username.

View file

@ -1,8 +1,8 @@
import { TelegramClient } from '../../client' import { TelegramClient } from '../../client'
import { InputPeerLike, MtInvalidPeerTypeError, Photo } from '../../types' import { InputPeerLike, MtInvalidPeerTypeError, Photo } from '../../types'
import { normalizeToInputUser } from '../../utils/peer-utils' import { normalizeToInputUser } from '../../utils/peer-utils'
import bigInt from 'big-integer'
import { tl } from '@mtcute/tl' import { tl } from '@mtcute/tl'
import Long from 'long'
/** /**
* Get a list of profile pictures of a user * Get a list of profile pictures of a user
@ -40,7 +40,7 @@ export async function getProfilePhotos(
userId: peer, userId: peer,
offset: params.offset ?? 0, offset: params.offset ?? 0,
limit: params.limit ?? 100, limit: params.limit ?? 100,
maxId: bigInt.zero, maxId: Long.ZERO,
}) })
return res.photos.map((it) => new Photo(this, it as tl.RawPhoto)) return res.photos.map((it) => new Photo(this, it as tl.RawPhoto))

View file

@ -1,7 +1,6 @@
import { InputPeerLike, User } from '../../types' import { InputPeerLike, User } from '../../types'
import { TelegramClient } from '../../client' import { TelegramClient } from '../../client'
import { MaybeArray } from '@mtcute/core' import { MaybeArray } from '@mtcute/core'
import { tl } from '@mtcute/tl'
import { normalizeToInputUser } from '../../utils/peer-utils' import { normalizeToInputUser } from '../../utils/peer-utils'
/** /**

View file

@ -2,7 +2,7 @@ import { TelegramClient } from '../../client'
import { InputPeerLike, MtInvalidPeerTypeError, Photo } from '../../types' import { InputPeerLike, MtInvalidPeerTypeError, Photo } from '../../types'
import { normalizeToInputUser } from '../../utils/peer-utils' import { normalizeToInputUser } from '../../utils/peer-utils'
import { tl } from '@mtcute/tl' import { tl } from '@mtcute/tl'
import bigInt from 'big-integer' import Long from 'long'
/** /**
* Iterate over profile photos * Iterate over profile photos
@ -54,7 +54,7 @@ export async function* iterProfilePhotos(
const limit = Math.min(params.chunkSize || 100, total) const limit = Math.min(params.chunkSize || 100, total)
const maxId = params.maxId || bigInt.zero const maxId = params.maxId || Long.ZERO
for (;;) { for (;;) {
const res = await this.call({ const res = await this.call({

View file

@ -1,9 +1,9 @@
import { tl } from '@mtcute/tl' import { tl } from '@mtcute/tl'
import { TelegramClient } from '../../client' import { TelegramClient } from '../../client'
import { InputPeerLike, MtNotFoundError } from '../../types' import { InputPeerLike, MtNotFoundError } from '../../types'
import { getBasicPeerType, getMarkedPeerId, MAX_CHANNEL_ID } from '@mtcute/core' import { getBasicPeerType, getMarkedPeerId, toggleChannelIdMark } from '@mtcute/core'
import bigInt from 'big-integer'
import { normalizeToInputPeer } from '../../utils/peer-utils' import { normalizeToInputPeer } from '../../utils/peer-utils'
import Long from 'long'
import { assertTypeIs } from '../../utils/type-assertion' import { assertTypeIs } from '../../utils/type-assertion'
/** /**
@ -11,11 +11,13 @@ import { assertTypeIs } from '../../utils/type-assertion'
* Useful when an `InputPeer` is needed. * Useful when an `InputPeer` is needed.
* *
* @param peerId The peer identifier that you want to extract the `InputPeer` from. * @param peerId The peer identifier that you want to extract the `InputPeer` from.
* @param force Whether to force re-fetch the peer from the server
* @internal * @internal
*/ */
export async function resolvePeer( export async function resolvePeer(
this: TelegramClient, this: TelegramClient,
peerId: InputPeerLike peerId: InputPeerLike,
force = false
): Promise<tl.TypeInputPeer> { ): Promise<tl.TypeInputPeer> {
// for convenience we also accept tl objects directly // for convenience we also accept tl objects directly
if (typeof peerId === 'object') { if (typeof peerId === 'object') {
@ -26,7 +28,7 @@ export async function resolvePeer(
} }
} }
if (typeof peerId === 'number') { if (typeof peerId === 'number' && !force) {
const fromStorage = await this.storage.getPeerById(peerId) const fromStorage = await this.storage.getPeerById(peerId)
if (fromStorage) return fromStorage if (fromStorage) return fromStorage
} }
@ -42,7 +44,7 @@ export async function resolvePeer(
const res = await this.call({ const res = await this.call({
_: 'contacts.getContacts', _: 'contacts.getContacts',
hash: 0, hash: Long.ZERO,
}) })
assertTypeIs('contacts.getContacts', res, 'contacts.contacts') assertTypeIs('contacts.getContacts', res, 'contacts.contacts')
@ -62,8 +64,10 @@ export async function resolvePeer(
) )
} else { } else {
// username // username
const fromStorage = await this.storage.getPeerByUsername(peerId) if (!force) {
if (fromStorage) return fromStorage const fromStorage = await this.storage.getPeerByUsername(peerId)
if (fromStorage) return fromStorage
}
const res = await this.call({ const res = await this.call({
_: 'contacts.resolveUsername', _: 'contacts.resolveUsername',
@ -122,7 +126,7 @@ export async function resolvePeer(
{ {
_: 'inputUser', _: 'inputUser',
userId: peerId, userId: peerId,
accessHash: bigInt.zero, accessHash: Long.ZERO,
}, },
], ],
}) })
@ -159,14 +163,15 @@ export async function resolvePeer(
// break // break
} }
case 'channel': { case 'channel': {
const id = MAX_CHANNEL_ID - peerId const id = toggleChannelIdMark(peerId as number)
const res = await this.call({ const res = await this.call({
_: 'channels.getChannels', _: 'channels.getChannels',
id: [ id: [
{ {
_: 'inputChannel', _: 'inputChannel',
channelId: MAX_CHANNEL_ID - peerId, channelId: id,
accessHash: bigInt.zero, accessHash: Long.ZERO,
}, },
], ],
}) })

View file

@ -5,7 +5,7 @@ import { Message } from '../messages'
import { MtArgumentError } from '../errors' import { MtArgumentError } from '../errors'
import { BasicPeerType, getBasicPeerType, getMarkedPeerId } from '@mtcute/core' import { BasicPeerType, getBasicPeerType, getMarkedPeerId } from '@mtcute/core'
import { encodeInlineMessageId } from '../../utils/inline-utils' import { encodeInlineMessageId } from '../../utils/inline-utils'
import { User, UsersIndex } from '../peers' import { User, PeersIndex } from '../peers'
import { MessageNotFoundError } from '@mtcute/core' import { MessageNotFoundError } from '@mtcute/core'
/** /**
@ -13,22 +13,13 @@ import { MessageNotFoundError } from '@mtcute/core'
* of an inline keyboard. * of an inline keyboard.
*/ */
export class CallbackQuery { export class CallbackQuery {
readonly client: TelegramClient
readonly raw:
| tl.RawUpdateBotCallbackQuery
| tl.RawUpdateInlineBotCallbackQuery
readonly _users: UsersIndex
constructor( constructor(
client: TelegramClient, readonly client: TelegramClient,
raw: tl.RawUpdateBotCallbackQuery | tl.RawUpdateInlineBotCallbackQuery, readonly raw:
users: UsersIndex | tl.RawUpdateBotCallbackQuery
) { | tl.RawUpdateInlineBotCallbackQuery,
this.client = client readonly _peers: PeersIndex
this.raw = raw ) {}
this._users = users
}
/** /**
* ID of this callback query * ID of this callback query
@ -43,7 +34,10 @@ export class CallbackQuery {
*/ */
get user(): User { get user(): User {
if (!this._user) { if (!this._user) {
this._user = new User(this.client, this._users[this.raw.userId]) this._user = new User(
this.client,
this._peers.user(this.raw.userId)
)
} }
return this._user return this._user

View file

@ -1,26 +1,17 @@
import { makeInspectable } from '../utils' import { makeInspectable } from '../utils'
import { TelegramClient } from '../../client' import { TelegramClient } from '../../client'
import { tl } from '@mtcute/tl' import { tl } from '@mtcute/tl'
import { User, UsersIndex } from '../peers' import { PeersIndex, User } from '../peers'
/** /**
* Game high score * Game high score
*/ */
export class GameHighScore { export class GameHighScore {
readonly client: TelegramClient
readonly raw: tl.RawHighScore
readonly _users: UsersIndex
constructor( constructor(
client: TelegramClient, readonly client: TelegramClient,
raw: tl.RawHighScore, readonly raw: tl.RawHighScore,
users: UsersIndex readonly _peers: PeersIndex
) { ) {}
this.client = client
this.raw = raw
this._users = users
}
private _user?: User private _user?: User
/** /**
@ -28,7 +19,10 @@ export class GameHighScore {
*/ */
get user(): User { get user(): User {
if (!this._user) { if (!this._user) {
this._user = new User(this.client, this._users[this.raw.userId]) this._user = new User(
this.client,
this._peers.user(this.raw.userId)
)
} }
return this._user return this._user

View file

@ -1,6 +1,6 @@
import { makeInspectable } from '../utils' import { makeInspectable } from '../utils'
import { tl } from '@mtcute/tl' import { tl } from '@mtcute/tl'
import { PeerType, User, UsersIndex } from '../peers' import { PeersIndex, PeerType, User } from '../peers'
import { TelegramClient } from '../../client' import { TelegramClient } from '../../client'
import { Location } from '../media' import { Location } from '../media'
import { InputInlineResult } from './input' import { InputInlineResult } from './input'
@ -14,20 +14,11 @@ const PEER_TYPE_MAP: Record<tl.TypeInlineQueryPeerType['_'], PeerType> = {
} }
export class InlineQuery { export class InlineQuery {
readonly client: TelegramClient
readonly raw: tl.RawUpdateBotInlineQuery
/** Map of users in this message. Mainly for internal use */
readonly _users: UsersIndex
constructor( constructor(
client: TelegramClient, readonly client: TelegramClient,
raw: tl.RawUpdateBotInlineQuery, readonly raw: tl.RawUpdateBotInlineQuery,
users: UsersIndex readonly _peers: PeersIndex
) { ) {
this.client = client
this.raw = raw
this._users = users
} }
/** /**
@ -43,7 +34,7 @@ export class InlineQuery {
*/ */
get user(): User { get user(): User {
if (!this._user) { if (!this._user) {
this._user = new User(this.client, this._users[this.raw.userId]) this._user = new User(this.client, this._peers.user(this.raw.userId))
} }
return this._user return this._user

View file

@ -1,4 +1,4 @@
import { AsyncLock, getMarkedPeerId, MaybeAsync } from '@mtcute/core' import { AsyncLock, Deque, getMarkedPeerId, MaybeAsync } from '@mtcute/core'
import { import {
ControllablePromise, ControllablePromise,
createControllablePromise, createControllablePromise,
@ -12,7 +12,6 @@ import { FormattedString } from './parser'
import { Message } from './messages' import { Message } from './messages'
import { tl } from '@mtcute/tl' import { tl } from '@mtcute/tl'
import { TimeoutError } from '@mtcute/tl/errors' import { TimeoutError } from '@mtcute/tl/errors'
import { Queue } from '../utils/queue'
interface QueuedHandler<T> { interface QueuedHandler<T> {
promise: ControllablePromise<T> promise: ControllablePromise<T>
@ -39,12 +38,12 @@ export class Conversation {
private _lastMessage!: number private _lastMessage!: number
private _lastReceivedMessage!: number private _lastReceivedMessage!: number
private _queuedNewMessage = new Queue<QueuedHandler<Message>>() private _queuedNewMessage = new Deque<QueuedHandler<Message>>()
private _pendingNewMessages = new Queue<Message>() private _pendingNewMessages = new Deque<Message>()
private _lock = new AsyncLock() private _lock = new AsyncLock()
private _pendingEditMessage: Record<number, QueuedHandler<Message>> = {} private _pendingEditMessage: Record<number, QueuedHandler<Message>> = {}
private _recentEdits = new Queue<Message>(10) private _recentEdits = new Deque<Message>(10)
private _pendingRead: Record<number, QueuedHandler<void>> = {} private _pendingRead: Record<number, QueuedHandler<void>> = {}
@ -276,7 +275,7 @@ export class Conversation {
}, timeout) }, timeout)
} }
this._queuedNewMessage.push({ this._queuedNewMessage.pushBack({
promise, promise,
check: filter, check: filter,
timeout: timer, timeout: timer,
@ -476,12 +475,12 @@ export class Conversation {
private _onNewMessage(msg: Message) { private _onNewMessage(msg: Message) {
if (msg.chat.id !== this._chatId) return if (msg.chat.id !== this._chatId) return
if (this._queuedNewMessage.empty()) { if (!this._queuedNewMessage.length) {
this._pendingNewMessages.push(msg) this._pendingNewMessages.pushBack(msg)
return return
} }
const it = this._queuedNewMessage.peek()! const it = this._queuedNewMessage.peekFront()!
// order does matter for new messages // order does matter for new messages
this._lock.acquire().then(async () => { this._lock.acquire().then(async () => {
@ -489,7 +488,7 @@ export class Conversation {
if (!it.check || (await it.check(msg))) { if (!it.check || (await it.check(msg))) {
if (it.timeout) clearTimeout(it.timeout) if (it.timeout) clearTimeout(it.timeout)
it.promise.resolve(msg) it.promise.resolve(msg)
this._queuedNewMessage.pop() this._queuedNewMessage.popFront()
} }
} catch (e) { } catch (e) {
this.client['_emitError'](e) this.client['_emitError'](e)
@ -507,7 +506,7 @@ export class Conversation {
const it = this._pendingEditMessage[msg.id] const it = this._pendingEditMessage[msg.id]
if (!it && !fromRecent) { if (!it && !fromRecent) {
this._recentEdits.push(msg) this._recentEdits.pushBack(msg)
return return
} }
@ -536,21 +535,21 @@ export class Conversation {
} }
private _processPendingNewMessages() { private _processPendingNewMessages() {
if (this._pendingNewMessages.empty()) return if (!this._pendingNewMessages.length) return
let it let it
while ((it = this._pendingNewMessages.pop())) { while ((it = this._pendingNewMessages.popFront())) {
this._onNewMessage(it) this._onNewMessage(it)
} }
} }
private _processRecentEdits() { private _processRecentEdits() {
if (this._recentEdits.empty()) return if (!this._recentEdits.length) return
let it = this._recentEdits.first const iter = this._recentEdits.iter()
do { let it
if (!it) break while (!(it = iter.next()).done) {
this._onEditMessage(it.v, true) this._onEditMessage(it.value, true)
} while ((it = it.n)) }
} }
} }

View file

@ -10,43 +10,22 @@ import { makeInspectable } from '../utils'
* including ones that are embedded directly into the entity. * including ones that are embedded directly into the entity.
*/ */
export class FileLocation { export class FileLocation {
/**
* Client that was used to create this object
*/
readonly client: TelegramClient
/**
* Location of the file.
*
* Either a TL object declaring remote file location,
* a Buffer containing actual file content (for stripped thumbnails and vector previews),
* or a function that will return either of those.
*
* When a function is passed, it will be lazily resolved the
* first time downloading the file.
*/
readonly location:
| tl.TypeInputFileLocation
| tl.TypeInputWebFileLocation
| Buffer
| (() =>
| tl.TypeInputFileLocation
| tl.TypeInputWebFileLocation
| Buffer)
/**
* File size in bytes, when available
*/
readonly fileSize?: number
/**
* DC ID of the file, when available
*/
readonly dcId?: number
constructor( constructor(
client: TelegramClient, /**
location: * Client that was used to create this object
*/
readonly client: TelegramClient,
/**
* Location of the file.
*
* Either a TL object declaring remote file location,
* a Buffer containing actual file content (for stripped thumbnails and vector previews),
* or a function that will return either of those.
*
* When a function is passed, it will be lazily resolved the
* first time downloading the file.
*/
readonly location:
| tl.TypeInputFileLocation | tl.TypeInputFileLocation
| tl.TypeInputWebFileLocation | tl.TypeInputWebFileLocation
| Buffer | Buffer
@ -54,13 +33,15 @@ export class FileLocation {
| tl.TypeInputFileLocation | tl.TypeInputFileLocation
| tl.TypeInputWebFileLocation | tl.TypeInputWebFileLocation
| Buffer), | Buffer),
fileSize?: number, /**
dcId?: number * File size in bytes, when available
*/
readonly fileSize?: number,
/**
* DC ID of the file, when available
*/
readonly dcId?: number
) { ) {
this.client = client
this.location = location
this.fileSize = fileSize
this.dcId = dcId
} }
/** /**

View file

@ -21,9 +21,7 @@ const STUB_LOCATION = () => {
* > To be sure, check `isDownloadable` property. * > To be sure, check `isDownloadable` property.
*/ */
export class WebDocument extends FileLocation { export class WebDocument extends FileLocation {
readonly raw: tl.TypeWebDocument constructor(client: TelegramClient, readonly raw: tl.TypeWebDocument) {
constructor(client: TelegramClient, raw: tl.TypeWebDocument) {
super( super(
client, client,
raw._ === 'webDocument' raw._ === 'webDocument'

View file

@ -10,8 +10,6 @@ import { tdFileId } from '@mtcute/file-id'
export class Audio extends RawDocument { export class Audio extends RawDocument {
readonly type = 'audio' as const readonly type = 'audio' as const
readonly attr: tl.RawDocumentAttributeAudio
protected _fileIdType(): tdFileId.FileType { protected _fileIdType(): tdFileId.FileType {
return tdFileId.FileType.Audio return tdFileId.FileType.Audio
} }
@ -19,10 +17,9 @@ export class Audio extends RawDocument {
constructor( constructor(
client: TelegramClient, client: TelegramClient,
doc: tl.RawDocument, doc: tl.RawDocument,
attr: tl.RawDocumentAttributeAudio readonly attr: tl.RawDocumentAttributeAudio
) { ) {
super(client, doc) super(client, doc)
this.attr = attr
} }
/** /**

View file

@ -7,11 +7,7 @@ import { makeInspectable } from '../utils'
export class Contact { export class Contact {
readonly type = 'contact' as const readonly type = 'contact' as const
readonly obj: tl.RawMessageMediaContact constructor(readonly obj: tl.RawMessageMediaContact) {}
constructor(obj: tl.RawMessageMediaContact) {
this.obj = obj
}
/** /**
* Contact's phone number * Contact's phone number

View file

@ -7,8 +7,6 @@ import { makeInspectable } from '../utils'
export class Dice { export class Dice {
readonly type = 'dice' as const readonly type = 'dice' as const
readonly obj: tl.RawMessageMediaDice
/** /**
* A simple 6-sided dice. * A simple 6-sided dice.
* *
@ -138,9 +136,7 @@ export class Dice {
*/ */
static readonly TYPE_SLOTS = '🎰' static readonly TYPE_SLOTS = '🎰'
constructor(obj: tl.RawMessageMediaDice) { constructor(readonly obj: tl.RawMessageMediaDice) {}
this.obj = obj
}
/** /**
* An emoji which was originally sent. * An emoji which was originally sent.

View file

@ -11,25 +11,20 @@ import { tdFileId as td, toFileId, toUniqueFileId } from '@mtcute/file-id'
* This also includes audios, videos, voices etc. * This also includes audios, videos, voices etc.
*/ */
export class RawDocument extends FileLocation { export class RawDocument extends FileLocation {
/** constructor(client: TelegramClient, readonly raw: tl.RawDocument) {
* Raw TL object with the document itself
*/
readonly doc: tl.RawDocument
constructor(client: TelegramClient, doc: tl.RawDocument) {
super( super(
client, client,
{ {
_: 'inputDocumentFileLocation', _: 'inputDocumentFileLocation',
id: doc.id, id: raw.id,
fileReference: doc.fileReference, fileReference: raw.fileReference,
accessHash: doc.accessHash, accessHash: raw.accessHash,
thumbSize: '', thumbSize: '',
}, },
doc.size, raw.size,
doc.dcId raw.dcId
) )
this.doc = doc this.raw = raw
} }
private _fileName?: string | null private _fileName?: string | null
@ -39,7 +34,7 @@ export class RawDocument extends FileLocation {
*/ */
get fileName(): string | null { get fileName(): string | null {
if (this._fileName === undefined) { if (this._fileName === undefined) {
const attr = this.doc.attributes.find( const attr = this.raw.attributes.find(
(it) => it._ === 'documentAttributeFilename' (it) => it._ === 'documentAttributeFilename'
) )
this._fileName = attr this._fileName = attr
@ -54,14 +49,14 @@ export class RawDocument extends FileLocation {
* File MIME type, as defined by the sender. * File MIME type, as defined by the sender.
*/ */
get mimeType(): string { get mimeType(): string {
return this.doc.mimeType return this.raw.mimeType
} }
/** /**
* Date the document was sent * Date the document was sent
*/ */
get date(): Date { get date(): Date {
return new Date(this.doc.date * 1000) return new Date(this.raw.date * 1000)
} }
private _thumbnails?: Thumbnail[] private _thumbnails?: Thumbnail[]
@ -72,9 +67,9 @@ export class RawDocument extends FileLocation {
*/ */
get thumbnails(): ReadonlyArray<Thumbnail> { get thumbnails(): ReadonlyArray<Thumbnail> {
if (!this._thumbnails) { if (!this._thumbnails) {
this._thumbnails = this.doc.thumbs this._thumbnails = this.raw.thumbs
? this.doc.thumbs.map( ? this.raw.thumbs.map(
(sz) => new Thumbnail(this.client, this.doc, sz) (sz) => new Thumbnail(this.client, this.raw, sz)
) )
: [] : []
} }
@ -102,9 +97,9 @@ export class RawDocument extends FileLocation {
get inputDocument(): tl.TypeInputDocument { get inputDocument(): tl.TypeInputDocument {
return { return {
_: 'inputDocument', _: 'inputDocument',
id: this.doc.id, id: this.raw.id,
accessHash: this.doc.accessHash, accessHash: this.raw.accessHash,
fileReference: this.doc.fileReference, fileReference: this.raw.fileReference,
} }
} }
@ -133,12 +128,12 @@ export class RawDocument extends FileLocation {
if (!this._fileId) { if (!this._fileId) {
this._fileId = toFileId({ this._fileId = toFileId({
type: this._fileIdType(), type: this._fileIdType(),
dcId: this.doc.dcId, dcId: this.raw.dcId,
fileReference: this.doc.fileReference, fileReference: this.raw.fileReference,
location: { location: {
_: 'common', _: 'common',
id: this.doc.id, id: this.raw.id,
accessHash: this.doc.accessHash, accessHash: this.raw.accessHash,
}, },
}) })
} }
@ -154,7 +149,7 @@ export class RawDocument extends FileLocation {
if (!this._uniqueFileId) { if (!this._uniqueFileId) {
this._uniqueFileId = toUniqueFileId(td.FileType.Document, { this._uniqueFileId = toUniqueFileId(td.FileType.Document, {
_: 'common', _: 'common',
id: this.doc.id, id: this.raw.id,
}) })
} }

View file

@ -7,13 +7,7 @@ import { makeInspectable } from '../utils'
export class Game { export class Game {
readonly type = 'game' as const readonly type = 'game' as const
readonly game: tl.RawGame constructor(readonly client: TelegramClient, readonly game: tl.RawGame) {}
readonly client: TelegramClient
constructor(client: TelegramClient, game: tl.RawGame) {
this.client = client
this.game = game
}
/** /**
* Unique identifier of the game. * Unique identifier of the game.

View file

@ -10,13 +10,10 @@ import { MtArgumentError } from '../errors'
export class Invoice { export class Invoice {
readonly type = 'invoice' as const readonly type = 'invoice' as const
readonly client: TelegramClient constructor(
readonly raw: tl.RawMessageMediaInvoice readonly client: TelegramClient,
readonly raw: tl.RawMessageMediaInvoice
constructor(client: TelegramClient, raw: tl.RawMessageMediaInvoice) { ) {}
this.client = client
this.raw = raw
}
/** /**
* Whether the shipping address was requested * Whether the shipping address was requested

View file

@ -7,13 +7,10 @@ import { TelegramClient } from '../../client'
* A point on the map * A point on the map
*/ */
export class RawLocation { export class RawLocation {
readonly client: TelegramClient constructor(
readonly geo: tl.RawGeoPoint readonly client: TelegramClient,
readonly geo: tl.RawGeoPoint
constructor(client: TelegramClient, geo: tl.RawGeoPoint) { ) {}
this.client = client
this.geo = geo
}
/** /**
* Geo point latitude * Geo point latitude
@ -112,11 +109,8 @@ export class Location extends RawLocation {
export class LiveLocation extends RawLocation { export class LiveLocation extends RawLocation {
readonly type = 'live_location' as const readonly type = 'live_location' as const
readonly live: tl.RawMessageMediaGeoLive constructor(client: TelegramClient, readonly live: tl.RawMessageMediaGeoLive) {
constructor(client: TelegramClient, live: tl.RawMessageMediaGeoLive) {
super(client, live.geo as tl.RawGeoPoint) super(client, live.geo as tl.RawGeoPoint)
this.live = live
} }
/** /**

View file

@ -2,8 +2,8 @@ import { makeInspectable } from '../utils'
import { tl } from '@mtcute/tl' import { tl } from '@mtcute/tl'
import { TelegramClient } from '../../client' import { TelegramClient } from '../../client'
import { MessageEntity } from '../messages' import { MessageEntity } from '../messages'
import bigInt from 'big-integer' import { PeersIndex } from '../peers'
import { UsersIndex } from '../peers' import Long from 'long'
export namespace Poll { export namespace Poll {
export interface PollAnswer { export interface PollAnswer {
@ -40,23 +40,12 @@ export namespace Poll {
export class Poll { export class Poll {
readonly type = 'poll' as const readonly type = 'poll' as const
readonly client: TelegramClient
readonly raw: tl.TypePoll
readonly results?: tl.TypePollResults
readonly _users: UsersIndex
constructor( constructor(
client: TelegramClient, readonly client: TelegramClient,
raw: tl.TypePoll, readonly raw: tl.TypePoll,
users: UsersIndex, readonly _peers: PeersIndex,
results?: tl.TypePollResults readonly results?: tl.TypePollResults
) { ) {}
this.client = client
this.raw = raw
this._users = users
this.results = results
}
/** /**
* Unique identifier of the poll * Unique identifier of the poll
@ -151,6 +140,7 @@ export class Poll {
} }
private _entities?: MessageEntity[] private _entities?: MessageEntity[]
/** /**
* Format entities for {@link solution}, only available * Format entities for {@link solution}, only available
* in case you have already answered * in case you have already answered
@ -206,7 +196,7 @@ export class Poll {
poll: { poll: {
_: 'poll', _: 'poll',
closed: false, closed: false,
id: bigInt.zero, id: Long.ZERO,
publicVoters: this.raw.publicVoters, publicVoters: this.raw.publicVoters,
multipleChoice: this.raw.multipleChoice, multipleChoice: this.raw.multipleChoice,
question: this.raw.question, question: this.raw.question,

View file

@ -42,9 +42,6 @@ const MASK_POS = ['forehead', 'eyes', 'mouth', 'chin'] as const
export class Sticker extends RawDocument { export class Sticker extends RawDocument {
readonly type = 'sticker' as const readonly type = 'sticker' as const
readonly attr: tl.RawDocumentAttributeSticker
readonly attrSize?: tl.RawDocumentAttributeImageSize
protected _fileIdType(): tdFileId.FileType { protected _fileIdType(): tdFileId.FileType {
return tdFileId.FileType.Sticker return tdFileId.FileType.Sticker
} }
@ -52,12 +49,10 @@ export class Sticker extends RawDocument {
constructor( constructor(
client: TelegramClient, client: TelegramClient,
doc: tl.RawDocument, doc: tl.RawDocument,
attr: tl.RawDocumentAttributeSticker, readonly attr: tl.RawDocumentAttributeSticker,
attrSize?: tl.RawDocumentAttributeImageSize readonly attrSize?: tl.RawDocumentAttributeImageSize
) { ) {
super(client, doc) super(client, doc)
this.attr = attr
this.attrSize = attrSize
} }
/** /**
@ -177,7 +172,7 @@ export class Sticker extends RawDocument {
const set = await this.getStickerSet() const set = await this.getStickerSet()
if (!set) return '' if (!set) return ''
return set.stickers.find((it) => it.sticker.doc.id.eq(this.doc.id))! return set.stickers.find((it) => it.sticker.raw.id.eq(this.raw.id))!
.emoji .emoji
} }
} }

View file

@ -10,7 +10,7 @@ import { MtArgumentError, MtTypeAssertionError } from '../errors'
import { assertTypeIs } from '../../utils/type-assertion' import { assertTypeIs } from '../../utils/type-assertion'
import { makeInspectable } from '../utils' import { makeInspectable } from '../utils'
import { tdFileId as td, toFileId, toUniqueFileId } from '@mtcute/file-id' import { tdFileId as td, toFileId, toUniqueFileId } from '@mtcute/file-id'
import bigInt from 'big-integer' import Long from 'long'
/** /**
* One size of some thumbnail * One size of some thumbnail
@ -171,8 +171,8 @@ export class Thumbnail extends FileLocation {
fileReference: null, fileReference: null,
location: { location: {
_: 'photo', _: 'photo',
id: bigInt.zero, id: Long.ZERO,
accessHash: bigInt.zero, accessHash: Long.ZERO,
source: { source: {
_: 'stickerSetThumbnailVersion', _: 'stickerSetThumbnailVersion',
id: this._media.id, id: this._media.id,
@ -226,7 +226,7 @@ export class Thumbnail extends FileLocation {
if (this._media._ === 'stickerSet') { if (this._media._ === 'stickerSet') {
this._uniqueFileId = toUniqueFileId(td.FileType.Thumbnail, { this._uniqueFileId = toUniqueFileId(td.FileType.Thumbnail, {
_: 'photo', _: 'photo',
id: bigInt.zero, id: Long.ZERO,
source: { source: {
_: 'stickerSetThumbnailVersion', _: 'stickerSetThumbnailVersion',
id: this._media.id, id: this._media.id,

View file

@ -31,13 +31,10 @@ export namespace Venue {
export class Venue { export class Venue {
readonly type = 'venue' as const readonly type = 'venue' as const
readonly client: TelegramClient constructor(
readonly raw: tl.RawMessageMediaVenue readonly client: TelegramClient,
readonly raw: tl.RawMessageMediaVenue
constructor(client: TelegramClient, raw: tl.RawMessageMediaVenue) { ) {}
this.client = client
this.raw = raw
}
private _location?: Location private _location?: Location
/** /**

View file

@ -12,10 +12,6 @@ import { tdFileId } from '@mtcute/file-id'
export class Video extends RawDocument { export class Video extends RawDocument {
readonly type = 'video' as const readonly type = 'video' as const
readonly attr:
| tl.RawDocumentAttributeVideo
| tl.RawDocumentAttributeImageSize
protected _fileIdType(): tdFileId.FileType { protected _fileIdType(): tdFileId.FileType {
return this.isRound return this.isRound
? tdFileId.FileType.VideoNote ? tdFileId.FileType.VideoNote
@ -27,10 +23,11 @@ export class Video extends RawDocument {
constructor( constructor(
client: TelegramClient, client: TelegramClient,
doc: tl.RawDocument, doc: tl.RawDocument,
attr: tl.RawDocumentAttributeVideo | tl.RawDocumentAttributeImageSize readonly attr:
| tl.RawDocumentAttributeVideo
| tl.RawDocumentAttributeImageSize
) { ) {
super(client, doc) super(client, doc)
this.attr = attr
} }
/** /**
@ -65,7 +62,7 @@ export class Video extends RawDocument {
if (!this._isAnimation) { if (!this._isAnimation) {
this._isAnimation = this._isAnimation =
this.attr._ === 'documentAttributeImageSize' || this.attr._ === 'documentAttributeImageSize' ||
this.doc.attributes.some( this.raw.attributes.some(
(it) => it._ === 'documentAttributeAnimated' (it) => it._ === 'documentAttributeAnimated'
) )
} }

View file

@ -11,8 +11,6 @@ import { decodeWaveform } from '../../utils/voice-utils'
export class Voice extends RawDocument { export class Voice extends RawDocument {
readonly type = 'voice' as const readonly type = 'voice' as const
readonly attr: tl.RawDocumentAttributeAudio
protected _fileIdType(): tdFileId.FileType { protected _fileIdType(): tdFileId.FileType {
return tdFileId.FileType.VoiceNote return tdFileId.FileType.VoiceNote
} }
@ -20,10 +18,9 @@ export class Voice extends RawDocument {
constructor( constructor(
client: TelegramClient, client: TelegramClient,
doc: tl.RawDocument, doc: tl.RawDocument,
attr: tl.RawDocumentAttributeAudio readonly attr: tl.RawDocumentAttributeAudio
) { ) {
super(client, doc) super(client, doc)
this.attr = attr
} }
/** /**

View file

@ -19,13 +19,7 @@ import { MtArgumentError } from '../errors'
export class WebPage { export class WebPage {
readonly type = 'web_page' as const readonly type = 'web_page' as const
readonly client: TelegramClient constructor(readonly client: TelegramClient, readonly raw: tl.RawWebPage) {}
readonly raw: tl.RawWebPage
constructor(client: TelegramClient, raw: tl.RawWebPage) {
this.client = client
this.raw = raw
}
/** /**
* Unique ID of the preview * Unique ID of the preview

View file

@ -1,6 +1,6 @@
import { TelegramClient } from '../../client' import { TelegramClient } from '../../client'
import { tl } from '@mtcute/tl' import { tl } from '@mtcute/tl'
import { Chat, ChatsIndex, UsersIndex } from '../peers' import { Chat, PeersIndex } from '../peers'
import { Message } from './message' import { Message } from './message'
import { DraftMessage } from './draft-message' import { DraftMessage } from './draft-message'
import { makeInspectable } from '../utils' import { makeInspectable } from '../utils'
@ -13,31 +13,12 @@ import { getMarkedPeerId, MessageNotFoundError } from '@mtcute/core'
* in Telegram's main window. * in Telegram's main window.
*/ */
export class Dialog { export class Dialog {
readonly client: TelegramClient
readonly raw: tl.RawDialog
/** Map of users in this object. Mainly for internal use */
readonly _users: UsersIndex
/** Map of chats in this object. Mainly for internal use */
readonly _chats: ChatsIndex
/** Map of messages in this object. Mainly for internal use */
readonly _messages: Record<number, tl.TypeMessage>
constructor( constructor(
client: TelegramClient, readonly client: TelegramClient,
raw: tl.RawDialog, readonly raw: tl.RawDialog,
users: UsersIndex, readonly _peers: PeersIndex,
chats: ChatsIndex, readonly _messages: Record<number, tl.TypeMessage>
messages: Record<number, tl.TypeMessage> ) {}
) {
this.client = client
this.raw = raw
this._users = users
this._chats = chats
this._messages = messages
}
/** /**
* Find pinned dialogs from a list of dialogs * Find pinned dialogs from a list of dialogs
@ -172,12 +153,12 @@ export class Dialog {
switch (peer._) { switch (peer._) {
case 'peerChannel': case 'peerChannel':
case 'peerChat': case 'peerChat':
chat = this._chats[ chat = this._peers.chat(
peer._ === 'peerChannel' ? peer.channelId : peer.chatId peer._ === 'peerChannel' ? peer.channelId : peer.chatId
] )
break break
default: default:
chat = this._users[peer.userId] chat = this._peers.user(peer.userId)
break break
} }
@ -200,8 +181,7 @@ export class Dialog {
this._lastMessage = new Message( this._lastMessage = new Message(
this.client, this.client,
this._messages[cid], this._messages[cid],
this._users, this._peers
this._chats
) )
} else { } else {
throw new MessageNotFoundError() throw new MessageNotFoundError()

View file

@ -10,19 +10,11 @@ import { makeInspectable } from '../utils'
import { InputMediaWithCaption } from '../media' import { InputMediaWithCaption } from '../media'
export class DraftMessage { export class DraftMessage {
readonly client: TelegramClient
readonly raw: tl.RawDraftMessage
private _chatId: InputPeerLike
constructor( constructor(
client: TelegramClient, readonly client: TelegramClient,
raw: tl.RawDraftMessage, readonly raw: tl.RawDraftMessage,
chatId: InputPeerLike readonly _chatId: InputPeerLike
) { ) {
this.client = client
this.raw = raw
this._chatId = chatId
} }
/** /**

View file

@ -69,7 +69,7 @@ export function _messageMediaFromTl(
case 'messageMediaVenue': case 'messageMediaVenue':
return new Venue(this.client, m) return new Venue(this.client, m)
case 'messageMediaPoll': case 'messageMediaPoll':
return new Poll(this.client, m.poll, this._users, m.results) return new Poll(this.client, m.poll, this._peers, m.results)
case 'messageMediaInvoice': case 'messageMediaInvoice':
return new Invoice(this.client, m) return new Invoice(this.client, m)
default: default:

View file

@ -1,7 +1,7 @@
import { User, Chat, InputPeerLike, UsersIndex, ChatsIndex } from '../peers' import { User, Chat, InputPeerLike, PeersIndex } from '../peers'
import { tl } from '@mtcute/tl' import { tl } from '@mtcute/tl'
import { BotKeyboard, ReplyMarkup } from '../bots' import { BotKeyboard, ReplyMarkup } from '../bots'
import { getMarkedPeerId, MAX_CHANNEL_ID } from '@mtcute/core' import { getMarkedPeerId, toggleChannelIdMark } from '@mtcute/core'
import { import {
MtArgumentError, MtArgumentError,
MtTypeAssertionError, MtTypeAssertionError,
@ -96,44 +96,31 @@ export namespace Message {
* A Telegram message. * A Telegram message.
*/ */
export class Message { export class Message {
/** Telegram client that received this message */
readonly client: TelegramClient
/** /**
* Raw TL object. * Raw TL object.
*/ */
readonly raw: tl.RawMessage | tl.RawMessageService readonly raw: tl.RawMessage | tl.RawMessageService
/** Map of users in this message. Mainly for internal use */
readonly _users: UsersIndex
/** Map of chats in this message. Mainly for internal use */
readonly _chats: ChatsIndex
constructor( constructor(
client: TelegramClient, readonly client: TelegramClient,
raw: tl.TypeMessage, raw: tl.TypeMessage,
users: UsersIndex, readonly _peers: PeersIndex,
chats: ChatsIndex, /**
isScheduled = false * Whether the message is scheduled.
* If it is, then its {@link date} is set to future.
*/
readonly isScheduled = false
) { ) {
if (raw._ === 'messageEmpty') if (raw._ === 'messageEmpty')
throw new MtTypeAssertionError('Message#ctor', 'not messageEmpty', 'messageEmpty') throw new MtTypeAssertionError(
'Message#ctor',
this.client = client 'not messageEmpty',
this._users = users 'messageEmpty'
this._chats = chats )
this.raw = raw this.raw = raw
this.isScheduled = isScheduled
} }
/**
* Whether the message is scheduled.
* If it is, then its {@link date} is set to future.
*/
readonly isScheduled: boolean
/** Unique message identifier inside this chat */ /** Unique message identifier inside this chat */
get id(): number { get id(): number {
return this.raw.id return this.raw.id
@ -197,7 +184,7 @@ export class Message {
if (this.raw.peerId._ === 'peerUser') { if (this.raw.peerId._ === 'peerUser') {
this._sender = new User( this._sender = new User(
this.client, this.client,
this._users[this.raw.peerId.userId] this._peers.user(this.raw.peerId.userId)
) )
} else { } else {
// anon admin, return the chat // anon admin, return the chat
@ -208,13 +195,13 @@ export class Message {
case 'peerChannel': // forwarded channel post case 'peerChannel': // forwarded channel post
this._sender = new Chat( this._sender = new Chat(
this.client, this.client,
this._chats[from.channelId] this._peers.chat(from.channelId)
) )
break break
case 'peerUser': case 'peerUser':
this._sender = new User( this._sender = new User(
this.client, this.client,
this._users[from.userId] this._peers.user(from.userId)
) )
break break
default: default:
@ -239,8 +226,7 @@ export class Message {
this._chat = Chat._parseFromMessage( this._chat = Chat._parseFromMessage(
this.client, this.client,
this.raw, this.raw,
this._users, this._peers
this._chats
) )
} }
@ -274,13 +260,13 @@ export class Message {
case 'peerChannel': case 'peerChannel':
sender = new Chat( sender = new Chat(
this.client, this.client,
this._chats[fwd.fromId.channelId] this._peers.chat(fwd.fromId.channelId)
) )
break break
case 'peerUser': case 'peerUser':
sender = new User( sender = new User(
this.client, this.client,
this._users[fwd.fromId.userId] this._peers.user(fwd.fromId.userId)
) )
break break
default: default:
@ -329,7 +315,7 @@ export class Message {
if (r.comments) { if (r.comments) {
const o = (obj as unknown) as Message.MessageCommentsInfo const o = (obj as unknown) as Message.MessageCommentsInfo
o.discussion = r.channelId! o.discussion = getMarkedPeerId(r.channelId!, 'channel')
o.repliers = o.repliers =
r.recentRepliers?.map((it) => getMarkedPeerId(it)) ?? [] r.recentRepliers?.map((it) => getMarkedPeerId(it)) ?? []
} }
@ -375,7 +361,7 @@ export class Message {
} else { } else {
this._viaBot = new User( this._viaBot = new User(
this.client, this.client,
this._users[this.raw.viaBotId] this._peers.user(this.raw.viaBotId)
) )
} }
} }
@ -515,7 +501,7 @@ export class Message {
if (this.chat.username) { if (this.chat.username) {
return `https://t.me/${this.chat.username}/${this.id}` return `https://t.me/${this.chat.username}/${this.id}`
} else { } else {
return `https://t.me/c/${MAX_CHANNEL_ID - this.chat.id}/${ return `https://t.me/c/${toggleChannelIdMark(this.chat.id)}/${
this.id this.id
}` }`
} }
@ -547,8 +533,7 @@ export class Message {
* this method will also return `null`. * this method will also return `null`.
*/ */
getReplyTo(): Promise<Message | null> { getReplyTo(): Promise<Message | null> {
if (!this.replyToMessageId) if (!this.replyToMessageId) return Promise.resolve(null)
return Promise.resolve(null)
if (this.raw.peerId._ === 'peerChannel') if (this.raw.peerId._ === 'peerChannel')
return this.client.getMessages(this.chat.inputPeer, this.id, true) return this.client.getMessages(this.chat.inputPeer, this.id, true)
@ -795,7 +780,7 @@ export class Message {
* passing positional `text` as object field. * passing positional `text` as object field.
* *
* @param text New message text * @param text New message text
* @param params Additional parameters * @param params? Additional parameters
* @link TelegramClient.editMessage * @link TelegramClient.editMessage
*/ */
editText( editText(
@ -819,7 +804,12 @@ export class Message {
peer: InputPeerLike, peer: InputPeerLike,
params?: Parameters<TelegramClient['forwardMessages']>[3] params?: Parameters<TelegramClient['forwardMessages']>[3]
): Promise<Message> { ): Promise<Message> {
return this.client.forwardMessages(peer, this.chat.inputPeer, this.id, params) return this.client.forwardMessages(
peer,
this.chat.inputPeer,
this.id,
params
)
} }
/** /**
@ -885,7 +875,10 @@ export class Message {
* message. * message.
*/ */
async getDiscussionMessage(): Promise<Message | null> { async getDiscussionMessage(): Promise<Message | null> {
return this.client.getDiscussionMessage(this.chat.inputPeer, this.raw.id) return this.client.getDiscussionMessage(
this.chat.inputPeer,
this.raw.id
)
} }
/** /**

View file

@ -1,5 +1,3 @@
import { tl } from '@mtcute/tl'
/** /**
* Search filters to be used in {@link TelegramClient.searchMessages} * Search filters to be used in {@link TelegramClient.searchMessages}
* and {@link TelegramClient.searchGlobal}. * and {@link TelegramClient.searchGlobal}.

View file

@ -32,7 +32,6 @@ export namespace StickerSet {
* A stickerset (aka sticker pack) * A stickerset (aka sticker pack)
*/ */
export class StickerSet { export class StickerSet {
readonly client: TelegramClient
readonly brief: tl.RawStickerSet readonly brief: tl.RawStickerSet
readonly full?: tl.messages.RawStickerSet readonly full?: tl.messages.RawStickerSet
@ -42,10 +41,9 @@ export class StickerSet {
readonly isFull: boolean readonly isFull: boolean
constructor( constructor(
client: TelegramClient, readonly client: TelegramClient,
raw: tl.RawStickerSet | tl.messages.RawStickerSet raw: tl.RawStickerSet | tl.messages.RawStickerSet
) { ) {
this.client = client
if (raw._ === 'messages.stickerSet') { if (raw._ === 'messages.stickerSet') {
this.full = raw this.full = raw
this.brief = raw.set this.brief = raw.set

View file

@ -6,15 +6,15 @@ import { makeInspectable } from '../utils'
* Account takeout session * Account takeout session
*/ */
export class TakeoutSession { export class TakeoutSession {
private client: TelegramClient
/** /**
* Takeout session id * Takeout session id
*/ */
readonly id: tl.Long readonly id: tl.Long
constructor(client: TelegramClient, session: tl.account.RawTakeout) { constructor(
this.client = client readonly client: TelegramClient,
session: tl.account.RawTakeout
) {
this.id = session.id this.id = session.id
} }

View file

@ -8,7 +8,9 @@ import { Message } from '../messages'
import { ChatPermissions } from './chat-permissions' import { ChatPermissions } from './chat-permissions'
import { ChatLocation } from './chat-location' import { ChatLocation } from './chat-location'
import { ChatInviteLink } from './chat-invite-link' import { ChatInviteLink } from './chat-invite-link'
import { ChatsIndex, UsersIndex } from './index' import { PeersIndex } from './index'
import { toggleChannelIdMark } from '../../../../core'
export namespace ChatEvent { export namespace ChatEvent {
/** A user has joined the group (in the case of big groups, info of the user that has joined isn't shown) */ /** A user has joined the group (in the case of big groups, info of the user that has joined isn't shown) */
@ -371,8 +373,7 @@ function _actionFromTl(
message: new Message( message: new Message(
this.client, this.client,
e.message, e.message,
this._users, this._peers
this._chats
), ),
} }
case 'channelAdminLogEventActionEditMessage': case 'channelAdminLogEventActionEditMessage':
@ -381,14 +382,12 @@ function _actionFromTl(
old: new Message( old: new Message(
this.client, this.client,
e.prevMessage, e.prevMessage,
this._users, this._peers
this._chats
), ),
new: new Message( new: new Message(
this.client, this.client,
e.newMessage, e.newMessage,
this._users, this._peers
this._chats
), ),
} }
case 'channelAdminLogEventActionDeleteMessage': case 'channelAdminLogEventActionDeleteMessage':
@ -397,8 +396,7 @@ function _actionFromTl(
message: new Message( message: new Message(
this.client, this.client,
e.message, e.message,
this._users, this._peers
this._chats
), ),
} }
case 'channelAdminLogEventActionParticipantLeave': case 'channelAdminLogEventActionParticipantLeave':
@ -406,7 +404,7 @@ function _actionFromTl(
case 'channelAdminLogEventActionParticipantInvite': case 'channelAdminLogEventActionParticipantInvite':
return { return {
type: 'user_invited', type: 'user_invited',
member: new ChatMember(this.client, e.participant, this._users), member: new ChatMember(this.client, e.participant, this._peers),
} }
case 'channelAdminLogEventActionParticipantToggleBan': case 'channelAdminLogEventActionParticipantToggleBan':
return { return {
@ -414,9 +412,9 @@ function _actionFromTl(
old: new ChatMember( old: new ChatMember(
this.client, this.client,
e.prevParticipant, e.prevParticipant,
this._users this._peers
), ),
new: new ChatMember(this.client, e.newParticipant, this._users), new: new ChatMember(this.client, e.newParticipant, this._peers),
} }
case 'channelAdminLogEventActionParticipantToggleAdmin': case 'channelAdminLogEventActionParticipantToggleAdmin':
return { return {
@ -424,9 +422,9 @@ function _actionFromTl(
old: new ChatMember( old: new ChatMember(
this.client, this.client,
e.prevParticipant, e.prevParticipant,
this._users this._peers
), ),
new: new ChatMember(this.client, e.newParticipant, this._users), new: new ChatMember(this.client, e.newParticipant, this._peers),
} }
case 'channelAdminLogEventActionChangeStickerSet': case 'channelAdminLogEventActionChangeStickerSet':
return { return {
@ -452,15 +450,14 @@ function _actionFromTl(
message: new Message( message: new Message(
this.client, this.client,
e.message, e.message,
this._users, this._peers
this._chats
), ),
} }
case 'channelAdminLogEventActionChangeLinkedChat': case 'channelAdminLogEventActionChangeLinkedChat':
return { return {
type: 'linked_chat_changed', type: 'linked_chat_changed',
old: e.prevValue, old: toggleChannelIdMark(e.prevValue),
new: e.newValue, new: toggleChannelIdMark(e.newValue),
} }
case 'channelAdminLogEventActionChangeLocation': case 'channelAdminLogEventActionChangeLocation':
return { return {
@ -503,23 +500,23 @@ function _actionFromTl(
case 'channelAdminLogEventActionParticipantJoinByInvite': case 'channelAdminLogEventActionParticipantJoinByInvite':
return { return {
type: 'user_joined_invite', type: 'user_joined_invite',
link: new ChatInviteLink(this.client, e.invite, this._users), link: new ChatInviteLink(this.client, e.invite, this._peers),
} }
case 'channelAdminLogEventActionExportedInviteDelete': case 'channelAdminLogEventActionExportedInviteDelete':
return { return {
type: 'invite_deleted', type: 'invite_deleted',
link: new ChatInviteLink(this.client, e.invite, this._users), link: new ChatInviteLink(this.client, e.invite, this._peers),
} }
case 'channelAdminLogEventActionExportedInviteRevoke': case 'channelAdminLogEventActionExportedInviteRevoke':
return { return {
type: 'invite_revoked', type: 'invite_revoked',
link: new ChatInviteLink(this.client, e.invite, this._users), link: new ChatInviteLink(this.client, e.invite, this._peers),
} }
case 'channelAdminLogEventActionExportedInviteEdit': case 'channelAdminLogEventActionExportedInviteEdit':
return { return {
type: 'invite_edited', type: 'invite_edited',
old: new ChatInviteLink(this.client, e.prevInvite, this._users), old: new ChatInviteLink(this.client, e.prevInvite, this._peers),
new: new ChatInviteLink(this.client, e.newInvite, this._users), new: new ChatInviteLink(this.client, e.newInvite, this._peers),
} }
case 'channelAdminLogEventActionChangeHistoryTTL': case 'channelAdminLogEventActionChangeHistoryTTL':
return { return {
@ -533,22 +530,11 @@ function _actionFromTl(
} }
export class ChatEvent { export class ChatEvent {
readonly client: TelegramClient
readonly raw: tl.TypeChannelAdminLogEvent
readonly _users: UsersIndex
readonly _chats: ChatsIndex
constructor( constructor(
client: TelegramClient, readonly client: TelegramClient,
raw: tl.TypeChannelAdminLogEvent, readonly raw: tl.TypeChannelAdminLogEvent,
users: UsersIndex, readonly _peers: PeersIndex,
chats: ChatsIndex
) { ) {
this.client = client
this.raw = raw
this._users = users
this._chats = chats
} }
/** /**
@ -574,7 +560,7 @@ export class ChatEvent {
*/ */
get actor(): User { get actor(): User {
if (!this._actor) { if (!this._actor) {
this._actor = new User(this.client, this._users[this.raw.userId]) this._actor = new User(this.client, this._peers.user(this.raw.userId))
} }
return this._actor return this._actor

View file

@ -2,7 +2,7 @@ import { makeInspectable } from '../utils'
import { TelegramClient } from '../../client' import { TelegramClient } from '../../client'
import { tl } from '@mtcute/tl' import { tl } from '@mtcute/tl'
import { User } from './user' import { User } from './user'
import { UsersIndex } from './index' import { PeersIndex } from './index'
export namespace ChatInviteLink { export namespace ChatInviteLink {
export interface JoinedMember { export interface JoinedMember {
@ -15,19 +15,11 @@ export namespace ChatInviteLink {
* An invite link * An invite link
*/ */
export class ChatInviteLink { export class ChatInviteLink {
readonly client: TelegramClient
readonly raw: tl.RawChatInviteExported
readonly _users?: UsersIndex
constructor( constructor(
client: TelegramClient, readonly client: TelegramClient,
raw: tl.RawChatInviteExported, readonly raw: tl.RawChatInviteExported,
users?: UsersIndex readonly _peers?: PeersIndex
) { ) {
this.client = client
this.raw = raw
this._users = users
} }
/** /**
@ -45,10 +37,13 @@ export class ChatInviteLink {
* Creator of the invite link, if available * Creator of the invite link, if available
*/ */
get creator(): User | null { get creator(): User | null {
if (!this._users) return null if (!this._peers) return null
if (!this._creator) { if (!this._creator) {
this._creator = new User(this.client, this._users[this.raw.adminId]) this._creator = new User(
this.client,
this._peers.user(this.raw.adminId)
)
} }
return this._creator return this._creator

View file

@ -7,13 +7,10 @@ import { makeInspectable } from '../utils'
* Geolocation of a supergroup * Geolocation of a supergroup
*/ */
export class ChatLocation { export class ChatLocation {
readonly client: TelegramClient constructor(
readonly raw: tl.RawChannelLocation readonly client: TelegramClient,
readonly raw: tl.RawChannelLocation
constructor(client: TelegramClient, raw: tl.RawChannelLocation) { ) {}
this.client = client
this.raw = raw
}
private _location?: Location private _location?: Location
/** /**

View file

@ -4,7 +4,7 @@ import { tl } from '@mtcute/tl'
import { User } from './user' import { User } from './user'
import { assertTypeIs } from '../../utils/type-assertion' import { assertTypeIs } from '../../utils/type-assertion'
import { ChatPermissions } from './chat-permissions' import { ChatPermissions } from './chat-permissions'
import { UsersIndex } from './index' import { PeersIndex } from './index'
export namespace ChatMember { export namespace ChatMember {
/** /**
@ -29,21 +29,11 @@ export namespace ChatMember {
* Information about one chat member * Information about one chat member
*/ */
export class ChatMember { export class ChatMember {
readonly client: TelegramClient
readonly raw: tl.TypeChatParticipant | tl.TypeChannelParticipant
/** Map of users in this object. Mainly for internal use */
readonly _users: UsersIndex
constructor( constructor(
client: TelegramClient, readonly client: TelegramClient,
raw: tl.TypeChatParticipant | tl.TypeChannelParticipant, readonly raw: tl.TypeChatParticipant | tl.TypeChannelParticipant,
users: UsersIndex readonly _peers: PeersIndex
) { ) {}
this.client = client
this.raw = raw
this._users = users
}
private _user?: User private _user?: User
/** /**
@ -59,15 +49,16 @@ export class ChatMember {
this.raw.peer, this.raw.peer,
'peerUser' 'peerUser'
) )
this._user = new User( this._user = new User(
this.client, this.client,
this._users[this.raw.peer.userId] this._peers.user(this.raw.peer.userId)
) )
break break
default: default:
this._user = new User( this._user = new User(
this.client, this.client,
this._users[this.raw.userId] this._peers.user(this.raw.userId)
) )
break break
} }
@ -148,7 +139,7 @@ export class ChatMember {
if ('inviterId' in this.raw && this.raw.inviterId) { if ('inviterId' in this.raw && this.raw.inviterId) {
this._invitedBy = new User( this._invitedBy = new User(
this.client, this.client,
this._users[this.raw.inviterId] this._peers.user(this.raw.inviterId)
) )
} else { } else {
this._invitedBy = null this._invitedBy = null
@ -169,7 +160,7 @@ export class ChatMember {
if (this.raw._ === 'channelParticipantAdmin') { if (this.raw._ === 'channelParticipantAdmin') {
this._promotedBy = new User( this._promotedBy = new User(
this.client, this.client,
this._users[this.raw.promotedBy] this._peers.user(this.raw.promotedBy)
) )
} else { } else {
this._promotedBy = null this._promotedBy = null
@ -190,7 +181,7 @@ export class ChatMember {
if (this.raw._ === 'channelParticipantBanned') { if (this.raw._ === 'channelParticipantBanned') {
this._restrictedBy = new User( this._restrictedBy = new User(
this.client, this.client,
this._users[this.raw.kickedBy] this._peers.user(this.raw.kickedBy)
) )
} else { } else {
this._restrictedBy = null this._restrictedBy = null

View file

@ -4,23 +4,19 @@ import { TelegramClient } from '../../client'
import { makeInspectable } from '../utils' import { makeInspectable } from '../utils'
import { strippedPhotoToJpg } from '../../utils/file-utils' import { strippedPhotoToJpg } from '../../utils/file-utils'
import { tdFileId, toFileId, toUniqueFileId } from '@mtcute/file-id' import { tdFileId, toFileId, toUniqueFileId } from '@mtcute/file-id'
import bigInt from 'big-integer'
import { MAX_CHANNEL_ID } from '@mtcute/core'
import { MtArgumentError } from '../errors' import { MtArgumentError } from '../errors'
import Long from 'long'
import { toggleChannelIdMark } from '../../../../core'
/** /**
* A size of a chat photo * A size of a chat photo
*/ */
export class ChatPhotoSize extends FileLocation { export class ChatPhotoSize extends FileLocation {
readonly obj: tl.RawUserProfilePhoto | tl.RawChatPhoto
readonly peer: tl.TypeInputPeer
readonly big: boolean
constructor( constructor(
client: TelegramClient, readonly client: TelegramClient,
peer: tl.TypeInputPeer, readonly peer: tl.TypeInputPeer,
obj: tl.RawUserProfilePhoto | tl.RawChatPhoto, readonly obj: tl.RawUserProfilePhoto | tl.RawChatPhoto,
big: boolean readonly big: boolean
) { ) {
super( super(
client, client,
@ -57,10 +53,10 @@ export class ChatPhotoSize extends FileLocation {
break break
case 'inputPeerChat': case 'inputPeerChat':
id = -peer.chatId id = -peer.chatId
hash = bigInt.zero hash = Long.ZERO
break break
case 'inputPeerChannel': case 'inputPeerChannel':
id = MAX_CHANNEL_ID - peer.channelId id = toggleChannelIdMark(peer.channelId)
hash = peer.accessHash hash = peer.accessHash
break break
default: default:
@ -75,11 +71,11 @@ export class ChatPhotoSize extends FileLocation {
location: { location: {
_: 'photo', _: 'photo',
id: this.obj.photoId, id: this.obj.photoId,
accessHash: bigInt.zero, accessHash: Long.ZERO,
source: { source: {
_: 'dialogPhoto', _: 'dialogPhoto',
big: this.big, big: this.big,
id: bigInt(id), id: id,
accessHash: hash, accessHash: hash,
}, },
}, },

View file

@ -16,19 +16,15 @@ export namespace ChatPreview {
} }
export class ChatPreview { export class ChatPreview {
readonly client: TelegramClient
readonly invite: tl.RawChatInvite
/** constructor(
* Original invite link used to fetch readonly client: TelegramClient,
* this preview readonly invite: tl.RawChatInvite,
*/ /**
readonly link: string * Original invite link used to fetch this preview
*/
constructor(client: TelegramClient, raw: tl.RawChatInvite, link: string) { readonly link: string
this.client = client ) {
this.invite = raw
this.link = link
} }
/** /**

View file

@ -5,7 +5,7 @@ import { TelegramClient } from '../../client'
import { getMarkedPeerId, MaybeArray } from '@mtcute/core' import { getMarkedPeerId, MaybeArray } from '@mtcute/core'
import { MtArgumentError, MtTypeAssertionError } from '../errors' import { MtArgumentError, MtTypeAssertionError } from '../errors'
import { makeInspectable } from '../utils' import { makeInspectable } from '../utils'
import { ChatsIndex, InputPeerLike, User, UsersIndex } from './index' import { InputPeerLike, PeersIndex, User } from './index'
import { ChatLocation } from './chat-location' import { ChatLocation } from './chat-location'
import { InputMediaLike } from '../media' import { InputMediaLike } from '../media'
import { FormattedString } from '../parser' import { FormattedString } from '../parser'
@ -33,9 +33,6 @@ export namespace Chat {
* A chat. * A chat.
*/ */
export class Chat { export class Chat {
/** Telegram client used for this chat */
readonly client: TelegramClient
/** /**
* Raw peer object that this {@link Chat} represents. * Raw peer object that this {@link Chat} represents.
*/ */
@ -46,15 +43,10 @@ export class Chat {
| tl.RawChatForbidden | tl.RawChatForbidden
| tl.RawChannelForbidden | tl.RawChannelForbidden
/**
* Raw full peer object that this {@link Chat} represents.
*/
readonly fullPeer?: tl.TypeUserFull | tl.TypeChatFull
constructor( constructor(
client: TelegramClient, readonly client: TelegramClient,
peer: tl.TypeUser | tl.TypeChat, peer: tl.TypeUser | tl.TypeChat,
fullPeer?: tl.TypeUserFull | tl.TypeChatFull readonly fullPeer?: tl.TypeUserFull | tl.TypeChatFull
) { ) {
if (!peer) throw new MtArgumentError('peer is not available') if (!peer) throw new MtArgumentError('peer is not available')
@ -478,27 +470,25 @@ export class Chat {
static _parseFromMessage( static _parseFromMessage(
client: TelegramClient, client: TelegramClient,
message: tl.RawMessage | tl.RawMessageService, message: tl.RawMessage | tl.RawMessageService,
users: UsersIndex, peers: PeersIndex
chats: ChatsIndex
): Chat { ): Chat {
return Chat._parseFromPeer(client, message.peerId, users, chats) return Chat._parseFromPeer(client, message.peerId, peers)
} }
/** @internal */ /** @internal */
static _parseFromPeer( static _parseFromPeer(
client: TelegramClient, client: TelegramClient,
peer: tl.TypePeer, peer: tl.TypePeer,
users: UsersIndex, peers: PeersIndex
chats: ChatsIndex
): Chat { ): Chat {
switch (peer._) { switch (peer._) {
case 'peerUser': case 'peerUser':
return new Chat(client, users[peer.userId]) return new Chat(client, peers.user(peer.userId))
case 'peerChat': case 'peerChat':
return new Chat(client, chats[peer.chatId]) return new Chat(client, peers.chat(peer.chatId))
} }
return new Chat(client, chats[peer.channelId]) return new Chat(client, peers.chat(peer.channelId))
} }
/** @internal */ /** @internal */

View file

@ -8,6 +8,7 @@ export * from './chat-member'
export * from './chat-event' export * from './chat-event'
export * from './chat-invite-link' export * from './chat-invite-link'
export * from './typing-status' export * from './typing-status'
export * from './peers-index'
/** /**
* Peer types that have one-to-one relation to tl.Peer* types. * Peer types that have one-to-one relation to tl.Peer* types.
@ -23,11 +24,14 @@ export type PeerType = 'user' | 'bot' | 'group' | 'channel' | 'supergroup'
/** /**
* Type that can be used as an input peer * Type that can be used as an input peer
* to most of the high-level methods. Can be: * to most of the high-level methods. Can be:
* - `number`, representing peer's marked ID * - `number`, representing peer's marked ID*
* - `string`, representing peer's username (w/out preceding `@`) * - `string`, representing peer's username (w/out preceding `@`)
* - `string`, representing user's phone number (only for contacts) * - `string`, representing user's phone number (only for contacts)
* - `"me"` and `"self"` which will be replaced with the current user/bot * - `"me"` and `"self"` which will be replaced with the current user/bot
* - Raw TL object * - Raw TL object
*
* > Telegram has moved to int64 IDs. Though, Levin [has confirmed](https://t.me/tdlibchat/25075)
* > that new IDs *will* still fit into int53, meaning JS integers are fine.
*/ */
export type InputPeerLike = export type InputPeerLike =
| string | string
@ -36,6 +40,3 @@ export type InputPeerLike =
| tl.TypeInputPeer | tl.TypeInputPeer
| tl.TypeInputUser | tl.TypeInputUser
| tl.TypeInputChannel | tl.TypeInputChannel
export type UsersIndex = Record<number, tl.TypeUser>
export type ChatsIndex = Record<number, tl.TypeChat>

View file

@ -0,0 +1,48 @@
import { tl } from '@mtcute/tl'
import { MtArgumentError } from '../errors'
const ERROR_MSG =
'Given peer is not available in this index. This is most likely an internal library error.'
export class PeersIndex {
readonly users: Record<number, tl.TypeUser> = Object.create(null)
readonly chats: Record<number, tl.TypeChat> = Object.create(null)
hasMin = false
static from(obj: {
users?: tl.TypeUser[]
chats?: tl.TypeChat[]
}): PeersIndex {
const index = new PeersIndex()
obj.users?.forEach((user) => {
index.users[user.id] = user
if ((user as any).min) index.hasMin = true
})
obj.chats?.forEach((chat) => {
index.chats[chat.id] = chat
if ((chat as any).min) index.hasMin = true
})
return index
}
user(id: number): tl.TypeUser {
const r = this.users[id]
if (!r) {
throw new MtArgumentError(ERROR_MSG)
}
return r
}
chat(id: number): tl.TypeChat {
const r = this.chats[id]
if (!r) {
throw new MtArgumentError(ERROR_MSG)
}
return r
}
}

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