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-this-alias': 'off',
'prefer-rest-params': 'off',
'no-prototype-builtins': 'off',
'@typescript-eslint/ban-types': 'off',
'@typescript-eslint/adjacent-overload-signatures': 'off',
'@typescript-eslint/no-namespace': 'off',

View file

@ -12,12 +12,13 @@
"build": "tsc"
},
"dependencies": {
"@types/long": "^4.0.1",
"@types/node": "^15.12.1",
"@mtcute/tl": "~1.131.0",
"@mtcute/tl": "~134.0",
"@mtcute/core": "^1.0.0",
"@mtcute/file-id": "^1.0.0",
"eager-async-pool": "^1.0.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 fs = require('fs')
const prettier = require('prettier')
// not the best way but who cares lol
const { createWriter } = require('../../tl/scripts/common')
const updates = require('./generate-updates')
const targetDir = path.join(__dirname, '../src')
@ -130,7 +128,7 @@ async function addSingleMethod(state, fileName) {
if (!isExported && !isPrivate) {
throwError(
isExported,
stmt,
fileName,
'Public methods MUST be exported.'
)
@ -228,6 +226,14 @@ async function addSingleMethod(state, fileName) {
}
}
} else if (stmt.kind === ts.SyntaxKind.InterfaceDeclaration) {
if (isCopy) {
state.copy.push({
from: relPath,
code: stmt.getText()
})
continue
}
if (!checkForFlag(stmt, '@extension')) continue
const isExported = (stmt.modifiers || []).find(
(mod) => mod.kind === 92 /* ExportKeyword */
@ -260,7 +266,7 @@ async function addSingleMethod(state, fileName) {
}
async function main() {
const output = createWriter('../src/client.ts', __dirname)
const output = fs.createWriteStream(path.join(__dirname, '../src/client.ts'))
const state = {
imports: {},
fields: [],
@ -282,23 +288,22 @@ async function main() {
output.write(
'/* THIS FILE WAS AUTO-GENERATED */\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]) => {
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 }) => {
output.write(`// from ${from}\n${code}\n`)
})
output.write(
'\nexport interface TelegramClient extends BaseTelegramClient {'
'\nexport interface TelegramClient extends BaseTelegramClient {\n'
)
output.tab()
output.write(`/**
* Register a raw update handler
@ -306,14 +311,14 @@ async function main() {
* @param name Event name
* @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
*
* @param name Event name
* @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) => {
output.write(`/**
@ -322,7 +327,7 @@ async function main() {
* @param name Event name
* @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 (!comment.match(/\/\*\*?\s*\*\//))
// empty comment, no need to write it
output.write(comment)
output.write(comment + '\n')
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')
classContents.forEach((line) => output.write(line))
output.untab()
output.write(
'/** @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('}')
// format the resulting file with prettier

View file

@ -1,11 +1,24 @@
const fs = require('fs')
const path = require('path')
const prettier = require('prettier')
const {
snakeToCamel,
camelToPascal,
camelToSnake,
} = require('../../tl/scripts/common')
const snakeToCamel = (s) => {
return s.replace(/(?<!^|_)(_[a-z0-9])/gi, ($1) => {
return $1.substr(1).toUpperCase()
})
}
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() {
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 { moveStickerInSet } from './methods/stickers/move-sticker-in-set'
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 {
_dispatchUpdate,
_fetchUpdatesState,
_handleUpdate,
_keepAliveAction,
_loadStorage,
_onStop,
_saveStorage,
_updatesLoop,
catchUp,
enableRps,
getCurrentRpsIncoming,
getCurrentRpsProcessing,
startUpdatesLoop,
stopUpdatesLoop,
} from './methods/updates'
import { blockUser } from './methods/users/block-user'
import { deleteProfilePhotos } from './methods/users/delete-profile-photos'
@ -181,7 +198,6 @@ import {
ChatMember,
ChatMemberUpdate,
ChatPreview,
ChatsIndex,
ChosenInlineResult,
Conversation,
DeleteMessageUpdate,
@ -203,6 +219,7 @@ import {
ParsedUpdate,
PartialExcept,
PartialOnly,
PeersIndex,
Photo,
Poll,
PollUpdate,
@ -219,17 +236,27 @@ import {
User,
UserStatusUpdate,
UserTypingUpdate,
UsersIndex,
} from './types'
import {
AsyncLock,
MaybeArray,
MaybeAsync,
TelegramConnection,
} from '@mtcute/core'
import { tdFileId } from '@mtcute/file-id'
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 {
/**
* Register a raw update handler
@ -241,8 +268,7 @@ export interface TelegramClient extends BaseTelegramClient {
name: 'raw_update',
handler: (
upd: tl.TypeUpdate | tl.TypeMessage,
users: UsersIndex,
chats: ChatsIndex
peers: PeersIndex
) => void
): this
/**
@ -3268,12 +3294,58 @@ export interface TelegramClient extends BaseTelegramClient {
progressCallback?: (uploaded: number, total: number) => void
}
): 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
/**
* Catch up with the server by loading missed updates.
*
*/
catchUp(): Promise<void>
catchUp(): void
/**
* Block a user
*
@ -3413,8 +3485,12 @@ export interface TelegramClient extends BaseTelegramClient {
* Useful when an `InputPeer` is needed.
*
* @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
*
@ -3483,11 +3559,21 @@ export class TelegramClient extends BaseTelegramClient {
protected _selfUsername: string | null
protected _pendingConversations: Record<number, Conversation[]>
protected _hasConversations: boolean
protected _downloadConnections: Record<number, TelegramConnection>
protected _connectionsForInline: Record<number, TelegramConnection>
protected _downloadConnections: Record<number, SessionConnection>
protected _connectionsForInline: Record<number, SessionConnection>
protected _parseModes: Record<string, IMessageEntityParser>
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 _rpsIncoming?: RpsMeter
protected _rpsProcessing?: RpsMeter
protected _pts?: number
protected _qts?: number
protected _date?: number
@ -3506,12 +3592,33 @@ export class TelegramClient extends BaseTelegramClient {
this._userId = null
this._isBot = false
this._selfUsername = null
this.log.prefix = '[USER N/A] '
this._pendingConversations = {}
this._hasConversations = false
this._downloadConnections = {}
this._connectionsForInline = {}
this._parseModes = {}
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()
// we dont need to initialize state fields since
// 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')
}
acceptTos = acceptTos
checkPassword = checkPassword
getPasswordHint = getPasswordHint
@ -3667,12 +3773,19 @@ export class TelegramClient extends BaseTelegramClient {
getStickerSet = getStickerSet
moveStickerInSet = moveStickerInSet
setStickerSetThumb = setStickerSetThumb
enableRps = enableRps
getCurrentRpsIncoming = getCurrentRpsIncoming
getCurrentRpsProcessing = getCurrentRpsProcessing
protected _fetchUpdatesState = _fetchUpdatesState
protected _loadStorage = _loadStorage
startUpdatesLoop = startUpdatesLoop
stopUpdatesLoop = stopUpdatesLoop
protected _onStop = _onStop
protected _saveStorage = _saveStorage
protected _dispatchUpdate = _dispatchUpdate
_handleUpdate = _handleUpdate
catchUp = catchUp
protected _updatesLoop = _updatesLoop
protected _keepAliveAction = _keepAliveAction
blockUser = blockUser
deleteProfilePhotos = deleteProfilePhotos

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -148,15 +148,28 @@ export async function start(
// 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) {
this._catchUpChannels = !!params.catchUp
if (params.catchUp) {
await this.catchUp()
} else {
if (!params.catchUp) {
// otherwise we will catch up as soon as we receive a new update
await this._fetchUpdatesState()
}
this.startUpdatesLoop()
if (params.catchUp) {
this.catchUp()
}
}
return me
@ -165,9 +178,7 @@ export async function start(
}
if (!params.phone && !params.botToken)
throw new MtArgumentError(
'Neither phone nor bot token were provided'
)
throw new MtArgumentError('Neither phone nor bot token were provided')
let phone = params.phone ? await resolveMaybeDynamic(params.phone) : null
if (phone) {
@ -249,9 +260,7 @@ export async function start(
result = await this.checkPassword(password)
} catch (e) {
if (typeof params.password !== 'function') {
throw new MtArgumentError(
'Provided password was invalid'
)
throw new MtArgumentError('Provided password was invalid')
}
if (e instanceof PasswordHashInvalidError) {

View file

@ -3,12 +3,10 @@ import {
InputPeerLike,
MtInvalidPeerTypeError,
GameHighScore,
PeersIndex,
} from '../../types'
import { tl } from '@mtcute/tl'
import {
createUsersChatsIndex,
normalizeToInputUser,
} from '../../utils/peer-utils'
import { normalizeToInputUser } from '../../utils/peer-utils'
/**
* Get high scores of a game
@ -43,9 +41,9 @@ export async function getGameHighScores(
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 }
)
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 { Chat, MtTypeAssertionError } from '../../types'
import { Chat } from '../../types'
import { assertIsUpdatesGroup } from '../../utils/updates-utils'
/**

View file

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

View file

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

View file

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

View file

@ -1,7 +1,6 @@
import { TelegramClient } from '../../client'
import { InputPeerLike, MtInvalidPeerTypeError } from '../../types'
import { InputPeerLike, MtInvalidPeerTypeError, PeersIndex } from '../../types'
import {
createUsersChatsIndex,
isInputPeerChannel,
isInputPeerChat,
isInputPeerUser,
@ -48,15 +47,15 @@ export async function getChatMember(
? []
: res.fullChat.participants.participants
const { users } = createUsersChatsIndex(res)
const peers = PeersIndex.from(res)
for (const m of members) {
if (
(user._ === 'inputPeerSelf' &&
(users[m.userId] as tl.RawUser).self) ||
(peers.user(m.userId) as tl.RawUser).self) ||
(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,
})
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')
}

View file

@ -2,10 +2,10 @@ import {
ChatMember,
InputPeerLike,
MtInvalidPeerTypeError,
PeersIndex,
} from '../../types'
import { TelegramClient } from '../../client'
import {
createUsersChatsIndex,
isInputPeerChannel,
isInputPeerChat,
normalizeToInputChannel,
@ -13,6 +13,7 @@ import {
import { assertTypeIs } from '../../utils/type-assertion'
import { tl } from '@mtcute/tl'
import { ArrayWithTotal } from '../../types'
import Long from 'long'
/**
* 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.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
return ret
@ -140,7 +143,7 @@ export async function getChatMembers(
filter,
offset: params.offset ?? 0,
limit: params.limit ?? 200,
hash: 0,
hash: Long.ZERO,
})
assertTypeIs(
@ -149,9 +152,11 @@ export async function getChatMembers(
'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
return ret
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,7 +1,7 @@
import { TelegramClient } from '../../client'
import { User } from '../../types'
import { assertTypeIs } from '../../utils/type-assertion'
import { tl } from '@mtcute/tl'
import Long from 'long'
/**
* 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[]> {
const res = await this.call({
_: 'contacts.getContacts',
hash: 0,
hash: Long.ZERO,
})
assertTypeIs('getContacts', res, 'contacts.contacts')

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,6 +1,5 @@
import { TelegramClient } from '../../client'
import { ChatInviteLink, InputPeerLike } from '../../types'
import { createUsersChatsIndex } from '../../utils/peer-utils'
import { ChatInviteLink, InputPeerLike, PeersIndex } from '../../types'
import { normalizeDate } from '../../utils/misc-utils'
/**
@ -43,7 +42,7 @@ export async function editInviteLink(
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 { ChatInviteLink, InputPeerLike, User } from '../../types'
import { createUsersChatsIndex } from '../../utils/peer-utils'
import { ChatInviteLink, InputPeerLike, PeersIndex, User } from '../../types'
import { tl } from '@mtcute/tl'
/**
@ -40,18 +39,18 @@ export async function* getInviteLinkMembers(
if (!res.importers.length) break
const { users } = createUsersChatsIndex(res)
const peers = PeersIndex.from(res)
const last = res.importers[res.importers.length - 1]
offsetDate = last.date
offsetUser = {
_: 'inputUser',
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) {
const user = new User(this, users[it.userId])
const user = new User(this, peers.user(it.userId))
yield {
user,

View file

@ -1,6 +1,5 @@
import { TelegramClient } from '../../client'
import { ChatInviteLink, InputPeerLike } from '../../types'
import { createUsersChatsIndex } from '../../utils/peer-utils'
import { ChatInviteLink, InputPeerLike, PeersIndex } from '../../types'
/**
* Get detailed information about an invite link
@ -20,7 +19,7 @@ export async function getInviteLink(
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,
InputPeerLike,
MtInvalidPeerTypeError,
PeersIndex,
} from '../../types'
import {
createUsersChatsIndex,
normalizeToInputUser,
} from '../../utils/peer-utils'
import { normalizeToInputUser } from '../../utils/peer-utils'
import { tl } from '@mtcute/tl'
/**
@ -74,14 +72,14 @@ export async function* getInviteLinks(
if (!res.invites.length) break
const { users } = createUsersChatsIndex(res)
const peers = PeersIndex.from(res)
const last = res.invites[res.invites.length - 1]
offsetDate = last.date
offsetLink = last.link
for (const it of res.invites) {
yield new ChatInviteLink(this, it, users)
yield new ChatInviteLink(this, it, peers)
}
current += res.invites.length

View file

@ -2,9 +2,8 @@ import { TelegramClient } from '../../client'
import {
ChatInviteLink,
InputPeerLike,
MtTypeAssertionError,
MtTypeAssertionError, PeersIndex,
} from '../../types'
import { createUsersChatsIndex } from '../../utils/peer-utils'
/**
* Get primary invite link of a chat
@ -31,7 +30,7 @@ export async function getPrimaryInviteLink(
'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 { ChatInviteLink, InputPeerLike } from '../../types'
import { createUsersChatsIndex } from '../../utils/peer-utils'
import { ChatInviteLink, InputPeerLike, PeersIndex } from '../../types'
/**
* Revoke an invite link.
@ -25,12 +24,12 @@ export async function revokeInviteLink(
revoked: true,
})
const { users } = createUsersChatsIndex(res)
const peers = PeersIndex.from(res)
const invite =
res._ === 'messages.exportedChatInviteReplaced'
? res.newInvite
: 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 { InputPeerLike, MtTypeAssertionError, Poll } from '../../types'
import { createUsersChatsIndex } from '../../utils/peer-utils'
import bigInt from 'big-integer'
import { InputPeerLike, MtTypeAssertionError, PeersIndex, Poll } from '../../types'
import { assertTypeIs } from '../../utils/type-assertion'
import { assertIsUpdatesGroup } from '../../utils/updates-utils'
import Long from 'long'
/**
* Close a poll sent by you.
@ -28,7 +27,7 @@ export async function closePoll(
_: 'inputMediaPoll',
poll: {
_: 'poll',
id: bigInt.zero,
id: Long.ZERO,
closed: true,
question: '',
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 { InputPeerLike } from '../../types'
import { MaybeArray } from '@mtcute/core'
import {
isInputPeerChannel,
normalizeToInputChannel,
} from '../../utils/peer-utils'
import { createDummyUpdate } from '../../utils/updates-utils'
/**
* Delete scheduled messages.

View file

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

View file

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

View file

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

View file

@ -1,10 +1,7 @@
import { TelegramClient } from '../../client'
import { MaybeArray } from '@mtcute/core'
import {
createUsersChatsIndex,
} from '../../utils/peer-utils'
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.
@ -73,14 +70,13 @@ export async function getMessagesUnsafe(
res._
)
const { users, chats } = createUsersChatsIndex(res)
const peers = PeersIndex.from(res)
const ret = res.messages
.map((msg) => {
if (msg._ === 'messageEmpty') return null
const ret = res.messages.map((msg) => {
if (msg._ === 'messageEmpty') return null
return new Message(this, msg, users, chats)
})
return new Message(this, msg, peers)
})
return isSingle ? ret[0] : ret
}

View file

@ -1,12 +1,11 @@
import { TelegramClient } from '../../client'
import { MaybeArray } from '@mtcute/core'
import {
createUsersChatsIndex,
isInputPeerChannel,
normalizeToInputChannel,
} from '../../utils/peer-utils'
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
@ -84,7 +83,7 @@ export async function getMessages(
res._
)
const { users, chats } = createUsersChatsIndex(res)
const peers = PeersIndex.from(res)
const ret = res.messages.map((msg) => {
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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,18 +1,17 @@
import { TelegramClient } from '../../client'
import {
BotKeyboard, FormattedString,
BotKeyboard,
FormattedString,
InputMediaLike,
InputPeerLike,
Message, MtArgumentError,
Message,
MtArgumentError,
ReplyMarkup,
} from '../../types'
import {
normalizeDate,
normalizeMessageId,
randomUlong,
} from '../../utils/misc-utils'
import { normalizeDate, normalizeMessageId } from '../../utils/misc-utils'
import { tl } from '@mtcute/tl'
import { MessageNotFoundError } from '@mtcute/tl/errors'
import { randomLong } from '@mtcute/core'
/**
* 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)
if (!msg)
throw new MessageNotFoundError()
if (!msg) throw new MessageNotFoundError()
}
const res = await this.call({
@ -167,7 +165,7 @@ export async function sendMedia(
media: inputMedia,
silent: params.silent,
replyToMsgId: replyTo,
randomId: randomUlong(),
randomId: randomLong(),
scheduleDate: normalizeDate(params.schedule),
replyMarkup,
message,

View file

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

View file

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

View file

@ -3,10 +3,10 @@ import {
InputPeerLike,
MtArgumentError,
MtTypeAssertionError,
PeersIndex,
Poll,
} from '../../types'
import { MaybeArray, MessageNotFoundError } from '@mtcute/core'
import { createUsersChatsIndex } from '../../utils/peer-utils'
import { assertTypeIs } from '../../utils/type-assertion'
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 { InputFileLike, InputStickerSetItem, StickerSet } from '../../types'
import { InputStickerSetItem, StickerSet } from '../../types'
import { tl } from '@mtcute/tl'
const MASK_POS = {

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -10,43 +10,22 @@ import { makeInspectable } from '../utils'
* including ones that are embedded directly into the entity.
*/
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(
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.TypeInputWebFileLocation
| Buffer
@ -54,13 +33,15 @@ export class FileLocation {
| tl.TypeInputFileLocation
| tl.TypeInputWebFileLocation
| 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.
*/
export class WebDocument extends FileLocation {
readonly raw: tl.TypeWebDocument
constructor(client: TelegramClient, raw: tl.TypeWebDocument) {
constructor(client: TelegramClient, readonly raw: tl.TypeWebDocument) {
super(
client,
raw._ === 'webDocument'

View file

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

View file

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

View file

@ -7,8 +7,6 @@ import { makeInspectable } from '../utils'
export class Dice {
readonly type = 'dice' as const
readonly obj: tl.RawMessageMediaDice
/**
* A simple 6-sided dice.
*
@ -138,9 +136,7 @@ export class Dice {
*/
static readonly TYPE_SLOTS = '🎰'
constructor(obj: tl.RawMessageMediaDice) {
this.obj = obj
}
constructor(readonly obj: tl.RawMessageMediaDice) {}
/**
* 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.
*/
export class RawDocument extends FileLocation {
/**
* Raw TL object with the document itself
*/
readonly doc: tl.RawDocument
constructor(client: TelegramClient, doc: tl.RawDocument) {
constructor(client: TelegramClient, readonly raw: tl.RawDocument) {
super(
client,
{
_: 'inputDocumentFileLocation',
id: doc.id,
fileReference: doc.fileReference,
accessHash: doc.accessHash,
id: raw.id,
fileReference: raw.fileReference,
accessHash: raw.accessHash,
thumbSize: '',
},
doc.size,
doc.dcId
raw.size,
raw.dcId
)
this.doc = doc
this.raw = raw
}
private _fileName?: string | null
@ -39,7 +34,7 @@ export class RawDocument extends FileLocation {
*/
get fileName(): string | null {
if (this._fileName === undefined) {
const attr = this.doc.attributes.find(
const attr = this.raw.attributes.find(
(it) => it._ === 'documentAttributeFilename'
)
this._fileName = attr
@ -54,14 +49,14 @@ export class RawDocument extends FileLocation {
* File MIME type, as defined by the sender.
*/
get mimeType(): string {
return this.doc.mimeType
return this.raw.mimeType
}
/**
* Date the document was sent
*/
get date(): Date {
return new Date(this.doc.date * 1000)
return new Date(this.raw.date * 1000)
}
private _thumbnails?: Thumbnail[]
@ -72,9 +67,9 @@ export class RawDocument extends FileLocation {
*/
get thumbnails(): ReadonlyArray<Thumbnail> {
if (!this._thumbnails) {
this._thumbnails = this.doc.thumbs
? this.doc.thumbs.map(
(sz) => new Thumbnail(this.client, this.doc, sz)
this._thumbnails = this.raw.thumbs
? this.raw.thumbs.map(
(sz) => new Thumbnail(this.client, this.raw, sz)
)
: []
}
@ -102,9 +97,9 @@ export class RawDocument extends FileLocation {
get inputDocument(): tl.TypeInputDocument {
return {
_: 'inputDocument',
id: this.doc.id,
accessHash: this.doc.accessHash,
fileReference: this.doc.fileReference,
id: this.raw.id,
accessHash: this.raw.accessHash,
fileReference: this.raw.fileReference,
}
}
@ -133,12 +128,12 @@ export class RawDocument extends FileLocation {
if (!this._fileId) {
this._fileId = toFileId({
type: this._fileIdType(),
dcId: this.doc.dcId,
fileReference: this.doc.fileReference,
dcId: this.raw.dcId,
fileReference: this.raw.fileReference,
location: {
_: 'common',
id: this.doc.id,
accessHash: this.doc.accessHash,
id: this.raw.id,
accessHash: this.raw.accessHash,
},
})
}
@ -154,7 +149,7 @@ export class RawDocument extends FileLocation {
if (!this._uniqueFileId) {
this._uniqueFileId = toUniqueFileId(td.FileType.Document, {
_: 'common',
id: this.doc.id,
id: this.raw.id,
})
}

View file

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

View file

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

View file

@ -7,13 +7,10 @@ import { TelegramClient } from '../../client'
* A point on the map
*/
export class RawLocation {
readonly client: TelegramClient
readonly geo: tl.RawGeoPoint
constructor(client: TelegramClient, geo: tl.RawGeoPoint) {
this.client = client
this.geo = geo
}
constructor(
readonly client: TelegramClient,
readonly geo: tl.RawGeoPoint
) {}
/**
* Geo point latitude
@ -112,11 +109,8 @@ export class Location extends RawLocation {
export class LiveLocation extends RawLocation {
readonly type = 'live_location' as const
readonly live: tl.RawMessageMediaGeoLive
constructor(client: TelegramClient, live: tl.RawMessageMediaGeoLive) {
constructor(client: TelegramClient, readonly live: tl.RawMessageMediaGeoLive) {
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 { TelegramClient } from '../../client'
import { MessageEntity } from '../messages'
import bigInt from 'big-integer'
import { UsersIndex } from '../peers'
import { PeersIndex } from '../peers'
import Long from 'long'
export namespace Poll {
export interface PollAnswer {
@ -40,23 +40,12 @@ export namespace Poll {
export class Poll {
readonly type = 'poll' as const
readonly client: TelegramClient
readonly raw: tl.TypePoll
readonly results?: tl.TypePollResults
readonly _users: UsersIndex
constructor(
client: TelegramClient,
raw: tl.TypePoll,
users: UsersIndex,
results?: tl.TypePollResults
) {
this.client = client
this.raw = raw
this._users = users
this.results = results
}
readonly client: TelegramClient,
readonly raw: tl.TypePoll,
readonly _peers: PeersIndex,
readonly results?: tl.TypePollResults
) {}
/**
* Unique identifier of the poll
@ -151,6 +140,7 @@ export class Poll {
}
private _entities?: MessageEntity[]
/**
* Format entities for {@link solution}, only available
* in case you have already answered
@ -206,7 +196,7 @@ export class Poll {
poll: {
_: 'poll',
closed: false,
id: bigInt.zero,
id: Long.ZERO,
publicVoters: this.raw.publicVoters,
multipleChoice: this.raw.multipleChoice,
question: this.raw.question,

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -8,7 +8,9 @@ import { Message } from '../messages'
import { ChatPermissions } from './chat-permissions'
import { ChatLocation } from './chat-location'
import { ChatInviteLink } from './chat-invite-link'
import { ChatsIndex, UsersIndex } from './index'
import { PeersIndex } from './index'
import { toggleChannelIdMark } from '../../../../core'
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) */
@ -371,8 +373,7 @@ function _actionFromTl(
message: new Message(
this.client,
e.message,
this._users,
this._chats
this._peers
),
}
case 'channelAdminLogEventActionEditMessage':
@ -381,14 +382,12 @@ function _actionFromTl(
old: new Message(
this.client,
e.prevMessage,
this._users,
this._chats
this._peers
),
new: new Message(
this.client,
e.newMessage,
this._users,
this._chats
this._peers
),
}
case 'channelAdminLogEventActionDeleteMessage':
@ -397,8 +396,7 @@ function _actionFromTl(
message: new Message(
this.client,
e.message,
this._users,
this._chats
this._peers
),
}
case 'channelAdminLogEventActionParticipantLeave':
@ -406,7 +404,7 @@ function _actionFromTl(
case 'channelAdminLogEventActionParticipantInvite':
return {
type: 'user_invited',
member: new ChatMember(this.client, e.participant, this._users),
member: new ChatMember(this.client, e.participant, this._peers),
}
case 'channelAdminLogEventActionParticipantToggleBan':
return {
@ -414,9 +412,9 @@ function _actionFromTl(
old: new ChatMember(
this.client,
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':
return {
@ -424,9 +422,9 @@ function _actionFromTl(
old: new ChatMember(
this.client,
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':
return {
@ -452,15 +450,14 @@ function _actionFromTl(
message: new Message(
this.client,
e.message,
this._users,
this._chats
this._peers
),
}
case 'channelAdminLogEventActionChangeLinkedChat':
return {
type: 'linked_chat_changed',
old: e.prevValue,
new: e.newValue,
old: toggleChannelIdMark(e.prevValue),
new: toggleChannelIdMark(e.newValue),
}
case 'channelAdminLogEventActionChangeLocation':
return {
@ -503,23 +500,23 @@ function _actionFromTl(
case 'channelAdminLogEventActionParticipantJoinByInvite':
return {
type: 'user_joined_invite',
link: new ChatInviteLink(this.client, e.invite, this._users),
link: new ChatInviteLink(this.client, e.invite, this._peers),
}
case 'channelAdminLogEventActionExportedInviteDelete':
return {
type: 'invite_deleted',
link: new ChatInviteLink(this.client, e.invite, this._users),
link: new ChatInviteLink(this.client, e.invite, this._peers),
}
case 'channelAdminLogEventActionExportedInviteRevoke':
return {
type: 'invite_revoked',
link: new ChatInviteLink(this.client, e.invite, this._users),
link: new ChatInviteLink(this.client, e.invite, this._peers),
}
case 'channelAdminLogEventActionExportedInviteEdit':
return {
type: 'invite_edited',
old: new ChatInviteLink(this.client, e.prevInvite, this._users),
new: new ChatInviteLink(this.client, e.newInvite, this._users),
old: new ChatInviteLink(this.client, e.prevInvite, this._peers),
new: new ChatInviteLink(this.client, e.newInvite, this._peers),
}
case 'channelAdminLogEventActionChangeHistoryTTL':
return {
@ -533,22 +530,11 @@ function _actionFromTl(
}
export class ChatEvent {
readonly client: TelegramClient
readonly raw: tl.TypeChannelAdminLogEvent
readonly _users: UsersIndex
readonly _chats: ChatsIndex
constructor(
client: TelegramClient,
raw: tl.TypeChannelAdminLogEvent,
users: UsersIndex,
chats: ChatsIndex
readonly client: TelegramClient,
readonly raw: tl.TypeChannelAdminLogEvent,
readonly _peers: PeersIndex,
) {
this.client = client
this.raw = raw
this._users = users
this._chats = chats
}
/**
@ -574,7 +560,7 @@ export class ChatEvent {
*/
get actor(): User {
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

View file

@ -2,7 +2,7 @@ import { makeInspectable } from '../utils'
import { TelegramClient } from '../../client'
import { tl } from '@mtcute/tl'
import { User } from './user'
import { UsersIndex } from './index'
import { PeersIndex } from './index'
export namespace ChatInviteLink {
export interface JoinedMember {
@ -15,19 +15,11 @@ export namespace ChatInviteLink {
* An invite link
*/
export class ChatInviteLink {
readonly client: TelegramClient
readonly raw: tl.RawChatInviteExported
readonly _users?: UsersIndex
constructor(
client: TelegramClient,
raw: tl.RawChatInviteExported,
users?: UsersIndex
readonly client: TelegramClient,
readonly raw: tl.RawChatInviteExported,
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
*/
get creator(): User | null {
if (!this._users) return null
if (!this._peers) return null
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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -8,6 +8,7 @@ export * from './chat-member'
export * from './chat-event'
export * from './chat-invite-link'
export * from './typing-status'
export * from './peers-index'
/**
* 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
* 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 user's phone number (only for contacts)
* - `"me"` and `"self"` which will be replaced with the current user/bot
* - 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 =
| string
@ -36,6 +40,3 @@ export type InputPeerLike =
| tl.TypeInputPeer
| tl.TypeInputUser
| 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