build: fixed scripts, eslintignore, fixed linter warnings and re-formatted with prettier
This commit is contained in:
parent
5e05b099ed
commit
ae2dbcf03f
146 changed files with 882 additions and 552 deletions
|
@ -2,3 +2,11 @@ private/
|
|||
docs/
|
||||
dist/
|
||||
scripts/
|
||||
|
||||
packages/tl/errors.js
|
||||
packages/tl/errors.d.ts
|
||||
packages/tl/index.js
|
||||
packages/tl/index.d.ts
|
||||
packages/tl/binary/reader.js
|
||||
packages/tl/binary/writer.js
|
||||
packages/tl/binary/rsa-keys.js
|
||||
|
|
|
@ -6,13 +6,10 @@
|
|||
"license": "MIT",
|
||||
"author": "Alisa Sireneva <me@tei.su>",
|
||||
"scripts": {
|
||||
"test": "tsc && mocha dist/tests/**/*.spec.js",
|
||||
"build": "tsc",
|
||||
"test": "lerna run test",
|
||||
"build": "lerna run build",
|
||||
"lint": "eslint packages/**/*.ts",
|
||||
"generate-schema": "node scripts/generate-schema.js",
|
||||
"generate-code": "node packages/client/scripts/generate-client.js && node scripts/generate-types.js && node scripts/generate-binary-reader.js && node scripts/generate-binary-writer.js && node scripts/post-build.js",
|
||||
"generate-all": "npm run generate-schema && npm run generate-code",
|
||||
"build:doc": "node packages/client/scripts/generate-client.js && typedoc"
|
||||
"format": "prettier --write packages/**/*.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"big-integer": "1.6.48",
|
||||
|
|
|
@ -164,7 +164,6 @@ import {
|
|||
Dialog,
|
||||
FileDownloadParameters,
|
||||
GameHighScore,
|
||||
InputChatPermissions,
|
||||
InputFileLike,
|
||||
InputInlineResult,
|
||||
InputMediaLike,
|
||||
|
@ -1444,6 +1443,7 @@ export interface TelegramClient extends BaseTelegramClient {
|
|||
* > into memory at once. This might cause an issue, so use wisely!
|
||||
*
|
||||
* @param params File download parameters
|
||||
|
||||
*/
|
||||
downloadAsBuffer(params: FileDownloadParameters): Promise<Buffer>
|
||||
/**
|
||||
|
@ -1452,6 +1452,7 @@ export interface TelegramClient extends BaseTelegramClient {
|
|||
*
|
||||
* @param filename Local file name to which the remote file will be downloaded
|
||||
* @param params File download parameters
|
||||
|
||||
*/
|
||||
downloadToFile(
|
||||
filename: string,
|
||||
|
@ -1463,6 +1464,7 @@ export interface TelegramClient extends BaseTelegramClient {
|
|||
* consecutive.
|
||||
*
|
||||
* @param params Download parameters
|
||||
|
||||
*/
|
||||
downloadAsIterable(
|
||||
params: FileDownloadParameters
|
||||
|
@ -1472,6 +1474,7 @@ export interface TelegramClient extends BaseTelegramClient {
|
|||
* streaming file contents.
|
||||
*
|
||||
* @param params File download parameters
|
||||
|
||||
*/
|
||||
downloadAsStream(params: FileDownloadParameters): Readable
|
||||
/**
|
||||
|
|
6
packages/client/src/methods/.eslintrc.js
Normal file
6
packages/client/src/methods/.eslintrc.js
Normal file
|
@ -0,0 +1,6 @@
|
|||
module.exports = {
|
||||
rules: {
|
||||
// common when using preprocessor directives
|
||||
'@typescript-eslint/no-unused-vars': 'off',
|
||||
}
|
||||
}
|
|
@ -13,7 +13,6 @@ import {
|
|||
ChatPreview,
|
||||
ChatMember,
|
||||
Dialog,
|
||||
InputChatPermissions,
|
||||
TermsOfService,
|
||||
SentCode,
|
||||
MaybeDynamic,
|
||||
|
|
|
@ -55,6 +55,6 @@ export async function answerCallbackQuery(
|
|||
cacheTime: params.cacheTime ?? 0,
|
||||
alert: params.alert,
|
||||
message: params.text,
|
||||
url: params.url
|
||||
url: params.url,
|
||||
})
|
||||
}
|
||||
|
|
|
@ -93,7 +93,9 @@ export async function answerInlineQuery(
|
|||
): Promise<void> {
|
||||
if (!params) params = {}
|
||||
|
||||
const tlResults = await Promise.all(results.map(it => BotInline._convertToTl(this, it, params!.parseMode)))
|
||||
const tlResults = await Promise.all(
|
||||
results.map((it) => BotInline._convertToTl(this, it, params!.parseMode))
|
||||
)
|
||||
|
||||
await this.call({
|
||||
_: 'messages.setInlineBotResults',
|
||||
|
@ -103,10 +105,12 @@ export async function answerInlineQuery(
|
|||
gallery: params.gallery,
|
||||
private: params.private,
|
||||
nextOffset: params.nextOffset,
|
||||
switchPm: params.switchPm ? {
|
||||
_: 'inlineBotSwitchPM',
|
||||
text: params.switchPm.text,
|
||||
startParam: params.switchPm.parameter
|
||||
} : undefined
|
||||
switchPm: params.switchPm
|
||||
? {
|
||||
_: 'inlineBotSwitchPM',
|
||||
text: params.switchPm.text,
|
||||
startParam: params.switchPm.parameter,
|
||||
}
|
||||
: undefined,
|
||||
})
|
||||
}
|
||||
|
|
|
@ -45,7 +45,7 @@ export async function addChatMembers(
|
|||
const updates = await this.call({
|
||||
_: 'channels.inviteToChannel',
|
||||
channel: normalizeToInputChannel(chat),
|
||||
users: await this.resolvePeerMany(
|
||||
users: await this.resolvePeerMany(
|
||||
users as InputPeerLike[],
|
||||
normalizeToInputUser
|
||||
),
|
||||
|
|
|
@ -21,13 +21,13 @@ export async function archiveChats(
|
|||
folderPeers.push({
|
||||
_: 'inputFolderPeer',
|
||||
peer: await this.resolvePeer(chat),
|
||||
folderId: 1
|
||||
folderId: 1,
|
||||
})
|
||||
}
|
||||
|
||||
const updates = await this.call({
|
||||
_: 'folders.editPeerFolders',
|
||||
folderPeers
|
||||
folderPeers,
|
||||
})
|
||||
this._handleUpdate(updates)
|
||||
}
|
||||
|
|
|
@ -24,7 +24,7 @@ export async function createGroup(
|
|||
): Promise<Chat> {
|
||||
if (!Array.isArray(users)) users = [users]
|
||||
|
||||
const peers = await this.resolvePeerMany(
|
||||
const peers = await this.resolvePeerMany(
|
||||
users as InputPeerLike[],
|
||||
normalizeToInputUser
|
||||
)
|
||||
|
@ -32,7 +32,7 @@ export async function createGroup(
|
|||
const res = await this.call({
|
||||
_: 'messages.createChat',
|
||||
title,
|
||||
users: peers
|
||||
users: peers,
|
||||
})
|
||||
|
||||
assertIsUpdatesGroup('messages.createChat', res)
|
||||
|
|
|
@ -18,7 +18,7 @@ export async function createSupergroup(
|
|||
_: 'channels.createChannel',
|
||||
title,
|
||||
about: description,
|
||||
megagroup: true
|
||||
megagroup: true,
|
||||
})
|
||||
|
||||
assertIsUpdatesGroup('channels.createChannel', res)
|
||||
|
|
|
@ -9,13 +9,16 @@ import { normalizeToInputChannel } from '../../utils/peer-utils'
|
|||
* @param chatId Chat ID or username
|
||||
* @internal
|
||||
*/
|
||||
export async function deleteChannel(this: TelegramClient, chatId: InputPeerLike): Promise<void> {
|
||||
export async function deleteChannel(
|
||||
this: TelegramClient,
|
||||
chatId: InputPeerLike
|
||||
): Promise<void> {
|
||||
const peer = normalizeToInputChannel(await this.resolvePeer(chatId))
|
||||
if (!peer) throw new MtCuteInvalidPeerTypeError(chatId, 'channel')
|
||||
|
||||
const res = await this.call({
|
||||
_: 'channels.deleteChannel',
|
||||
channel: peer
|
||||
channel: peer,
|
||||
})
|
||||
this._handleUpdate(res)
|
||||
}
|
||||
|
|
|
@ -32,12 +32,18 @@ export async function deleteHistory(
|
|||
justClear: mode === 'clear',
|
||||
revoke: mode === 'revoke',
|
||||
peer,
|
||||
maxId
|
||||
maxId,
|
||||
})
|
||||
|
||||
const channel = normalizeToInputChannel(peer)
|
||||
if (channel) {
|
||||
this._handleUpdate(createDummyUpdate(res.pts, res.ptsCount, (channel as tl.RawInputChannel).channelId))
|
||||
this._handleUpdate(
|
||||
createDummyUpdate(
|
||||
res.pts,
|
||||
res.ptsCount,
|
||||
(channel as tl.RawInputChannel).channelId
|
||||
)
|
||||
)
|
||||
} else {
|
||||
this._handleUpdate(createDummyUpdate(res.pts, res.ptsCount))
|
||||
}
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
import { TelegramClient } from '../../client'
|
||||
import { InputPeerLike, MtCuteInvalidPeerTypeError } from '../../types'
|
||||
import { tl } from '@mtcute/tl'
|
||||
import { normalizeToInputChannel, normalizeToInputUser } from '../../utils/peer-utils'
|
||||
import {
|
||||
normalizeToInputChannel,
|
||||
normalizeToInputUser,
|
||||
} from '../../utils/peer-utils'
|
||||
|
||||
/**
|
||||
* Edit supergroup/channel admin rights of a user.
|
||||
|
@ -20,12 +23,10 @@ export async function editAdminRights(
|
|||
rank = ''
|
||||
): Promise<void> {
|
||||
const chat = normalizeToInputChannel(await this.resolvePeer(chatId))
|
||||
if (!chat)
|
||||
throw new MtCuteInvalidPeerTypeError(chatId, 'channel')
|
||||
if (!chat) throw new MtCuteInvalidPeerTypeError(chatId, 'channel')
|
||||
|
||||
const user = normalizeToInputUser(await this.resolvePeer(userId))
|
||||
if (!user)
|
||||
throw new MtCuteInvalidPeerTypeError(userId, 'user')
|
||||
if (!user) throw new MtCuteInvalidPeerTypeError(userId, 'user')
|
||||
|
||||
const res = await this.call({
|
||||
_: 'channels.editAdmin',
|
||||
|
@ -33,9 +34,9 @@ export async function editAdminRights(
|
|||
userId: user,
|
||||
adminRights: {
|
||||
_: 'chatAdminRights',
|
||||
...rights
|
||||
...rights,
|
||||
},
|
||||
rank
|
||||
rank,
|
||||
})
|
||||
|
||||
this._handleUpdate(res)
|
||||
|
|
|
@ -97,10 +97,7 @@ export async function* getChatEventLog(
|
|||
const chunkSize = Math.min(params.chunkSize ?? 100, total)
|
||||
|
||||
const admins: tl.TypeInputUser[] | undefined = params.users
|
||||
? await this.resolvePeerMany(
|
||||
params.users,
|
||||
normalizeToInputUser
|
||||
)
|
||||
? await this.resolvePeerMany(params.users, normalizeToInputUser)
|
||||
: undefined
|
||||
|
||||
let serverFilter:
|
||||
|
|
|
@ -26,9 +26,7 @@ export async function getChatPreview(
|
|||
})
|
||||
|
||||
if (res._ !== 'chatInvite') {
|
||||
throw new MtCuteNotFoundError(
|
||||
`You have already joined this chat!`
|
||||
)
|
||||
throw new MtCuteNotFoundError(`You have already joined this chat!`)
|
||||
}
|
||||
|
||||
return new ChatPreview(this, res, inviteLink)
|
||||
|
|
|
@ -31,19 +31,23 @@ export async function getNearbyChats(
|
|||
|
||||
if (!res.updates.length) return []
|
||||
|
||||
assertTypeIs('contacts.getLocated (@ .updates[0])', res.updates[0], 'updatePeerLocated')
|
||||
assertTypeIs(
|
||||
'contacts.getLocated (@ .updates[0])',
|
||||
res.updates[0],
|
||||
'updatePeerLocated'
|
||||
)
|
||||
|
||||
const chats = res.chats.map((it) => new Chat(this, it))
|
||||
|
||||
const index: Record<number, Chat> = {}
|
||||
chats.forEach((c) => index[c.id] = c)
|
||||
chats.forEach((c) => (index[c.id] = c))
|
||||
|
||||
res.updates[0].peers.forEach((peer) => {
|
||||
if (peer._ === 'peerSelfLocated') return
|
||||
|
||||
const id = getMarkedPeerId(peer.peer)
|
||||
if (index[id]) {
|
||||
(index[id] as tl.Mutable<Chat>).distance = peer.distance
|
||||
;(index[id] as tl.Mutable<Chat>).distance = peer.distance
|
||||
}
|
||||
})
|
||||
|
||||
|
|
|
@ -41,7 +41,7 @@ export async function* iterChatMembers(
|
|||
offset,
|
||||
limit,
|
||||
query: params.query,
|
||||
type: params.type
|
||||
type: params.type,
|
||||
})
|
||||
|
||||
if (!members.length) break
|
||||
|
|
|
@ -17,6 +17,6 @@ export async function markChatUnread(
|
|||
_: 'inputDialogPeer',
|
||||
peer: await this.resolvePeer(chatId),
|
||||
},
|
||||
unread: true
|
||||
unread: true,
|
||||
})
|
||||
}
|
||||
|
|
|
@ -20,13 +20,13 @@ export async function saveDraft(
|
|||
await this.call({
|
||||
_: 'messages.saveDraft',
|
||||
peer,
|
||||
...draft
|
||||
...draft,
|
||||
})
|
||||
} else {
|
||||
await this.call({
|
||||
_: 'messages.saveDraft',
|
||||
peer,
|
||||
message: ''
|
||||
message: '',
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,8 +29,8 @@ export async function setChatDefaultPermissions(
|
|||
bannedRights: {
|
||||
_: 'chatBannedRights',
|
||||
untilDate: 0,
|
||||
...restrictions
|
||||
}
|
||||
...restrictions,
|
||||
},
|
||||
})
|
||||
|
||||
assertIsUpdatesGroup('messages.editChatDefaultBannedRights', res)
|
||||
|
|
|
@ -1,8 +1,5 @@
|
|||
import { TelegramClient } from '../../client'
|
||||
import {
|
||||
InputPeerLike,
|
||||
MtCuteInvalidPeerTypeError,
|
||||
} from '../../types'
|
||||
import { InputPeerLike, MtCuteInvalidPeerTypeError } from '../../types'
|
||||
import {
|
||||
isInputPeerChannel,
|
||||
isInputPeerChat,
|
||||
|
@ -30,13 +27,13 @@ export async function setChatTitle(
|
|||
res = await this.call({
|
||||
_: 'messages.editChatTitle',
|
||||
chatId: chat.chatId,
|
||||
title
|
||||
title,
|
||||
})
|
||||
} else if (isInputPeerChannel(chat)) {
|
||||
res = await this.call({
|
||||
_: 'channels.editTitle',
|
||||
channel: normalizeToInputChannel(chat),
|
||||
title
|
||||
title,
|
||||
})
|
||||
} else throw new MtCuteInvalidPeerTypeError(chatId, 'chat or channel')
|
||||
|
||||
|
|
|
@ -1,8 +1,5 @@
|
|||
import { TelegramClient } from '../../client'
|
||||
import {
|
||||
InputPeerLike,
|
||||
MtCuteInvalidPeerTypeError,
|
||||
} from '../../types'
|
||||
import { InputPeerLike, MtCuteInvalidPeerTypeError } from '../../types'
|
||||
import { normalizeToInputChannel } from '../../utils/peer-utils'
|
||||
|
||||
/**
|
||||
|
@ -20,12 +17,11 @@ export async function setChatUsername(
|
|||
username: string | null
|
||||
): Promise<void> {
|
||||
const chat = normalizeToInputChannel(await this.resolvePeer(chatId))
|
||||
if (!chat)
|
||||
throw new MtCuteInvalidPeerTypeError(chatId, 'channel')
|
||||
if (!chat) throw new MtCuteInvalidPeerTypeError(chatId, 'channel')
|
||||
|
||||
await this.call({
|
||||
_: 'channels.updateUsername',
|
||||
channel: chat,
|
||||
username: username || ''
|
||||
username: username || '',
|
||||
})
|
||||
}
|
||||
|
|
|
@ -23,7 +23,7 @@ export async function setSlowMode(
|
|||
const res = await this.call({
|
||||
_: 'channels.toggleSlowMode',
|
||||
channel: chat,
|
||||
seconds
|
||||
seconds,
|
||||
})
|
||||
this._handleUpdate(res)
|
||||
}
|
||||
|
|
|
@ -21,13 +21,13 @@ export async function unarchiveChats(
|
|||
folderPeers.push({
|
||||
_: 'inputFolderPeer',
|
||||
peer: await this.resolvePeer(chat),
|
||||
folderId: 0
|
||||
folderId: 0,
|
||||
})
|
||||
}
|
||||
|
||||
const res = await this.call({
|
||||
_: 'folders.editPeerFolders',
|
||||
folderPeers
|
||||
folderPeers,
|
||||
})
|
||||
this._handleUpdate(res)
|
||||
}
|
||||
|
|
|
@ -1,5 +1,10 @@
|
|||
import { TelegramClient } from '../../client'
|
||||
import { InputPeerLike, MtCuteInvalidPeerTypeError, MtCuteTypeAssertionError, User } from '../../types'
|
||||
import {
|
||||
InputPeerLike,
|
||||
MtCuteInvalidPeerTypeError,
|
||||
MtCuteTypeAssertionError,
|
||||
User,
|
||||
} from '../../types'
|
||||
import { normalizeToInputUser } from '../../utils/peer-utils'
|
||||
import { tl } from '@mtcute/tl'
|
||||
import { assertIsUpdatesGroup } from '../../utils/updates-utils'
|
||||
|
@ -46,7 +51,7 @@ export async function addContact(
|
|||
firstName: params.firstName,
|
||||
lastName: params.lastName ?? '',
|
||||
phone: params.phone ?? '',
|
||||
addPhonePrivacyException: !!params.sharePhone
|
||||
addPhonePrivacyException: !!params.sharePhone,
|
||||
})
|
||||
|
||||
assertIsUpdatesGroup('contacts.addContact', res)
|
||||
|
|
|
@ -1,6 +1,11 @@
|
|||
import { TelegramClient } from '../../client'
|
||||
import { MaybeArray } from '@mtcute/core'
|
||||
import { InputPeerLike, MtCuteInvalidPeerTypeError, MtCuteTypeAssertionError, User } from '../../types'
|
||||
import {
|
||||
InputPeerLike,
|
||||
MtCuteInvalidPeerTypeError,
|
||||
MtCuteTypeAssertionError,
|
||||
User,
|
||||
} from '../../types'
|
||||
import { normalizeToInputUser } from '../../utils/peer-utils'
|
||||
import { tl } from '@mtcute/tl'
|
||||
import { assertIsUpdatesGroup } from '../../utils/updates-utils'
|
||||
|
@ -41,7 +46,7 @@ export async function deleteContacts(
|
|||
const single = !Array.isArray(userIds)
|
||||
if (single) userIds = [userIds as InputPeerLike]
|
||||
|
||||
const inputPeers = await this.resolvePeerMany(
|
||||
const inputPeers = await this.resolvePeerMany(
|
||||
userIds as InputPeerLike[],
|
||||
normalizeToInputUser
|
||||
)
|
||||
|
@ -54,7 +59,7 @@ export async function deleteContacts(
|
|||
|
||||
const res = await this.call({
|
||||
_: 'contacts.deleteContacts',
|
||||
id: inputPeers
|
||||
id: inputPeers,
|
||||
})
|
||||
|
||||
assertIsUpdatesGroup('contacts.deleteContacts', res)
|
||||
|
@ -63,7 +68,7 @@ export async function deleteContacts(
|
|||
|
||||
this._handleUpdate(res)
|
||||
|
||||
const users = res.users.map(user => new User(this, user))
|
||||
const users = res.users.map((user) => new User(this, user))
|
||||
|
||||
return single ? users[0] : users
|
||||
}
|
||||
|
|
|
@ -7,12 +7,10 @@ import { tl } from '@mtcute/tl'
|
|||
* Get list of contacts from your Telegram contacts list.
|
||||
* @internal
|
||||
*/
|
||||
export async function getContacts(
|
||||
this: TelegramClient
|
||||
): Promise<User[]> {
|
||||
export async function getContacts(this: TelegramClient): Promise<User[]> {
|
||||
const res = await this.call({
|
||||
_: 'contacts.getContacts',
|
||||
hash: 0
|
||||
hash: 0,
|
||||
})
|
||||
assertTypeIs('getContacts', res, 'contacts.contacts')
|
||||
|
||||
|
|
|
@ -36,13 +36,13 @@ export async function createFolder(
|
|||
includePeers: [],
|
||||
excludePeers: [],
|
||||
...folder,
|
||||
id
|
||||
id,
|
||||
}
|
||||
|
||||
await this.call({
|
||||
_: 'messages.updateDialogFilter',
|
||||
id,
|
||||
filter
|
||||
filter,
|
||||
})
|
||||
|
||||
return filter
|
||||
|
|
|
@ -21,21 +21,22 @@ export async function editFolder(
|
|||
): Promise<tl.RawDialogFilter> {
|
||||
if (typeof folder === 'number' || typeof folder === 'string') {
|
||||
const old = await this.getFolders()
|
||||
const found = old.find(it => it.id === folder || it.title === folder)
|
||||
if (!found) throw new MtCuteArgumentError(`Could not find a folder ${folder}`)
|
||||
const found = old.find((it) => it.id === folder || it.title === folder)
|
||||
if (!found)
|
||||
throw new MtCuteArgumentError(`Could not find a folder ${folder}`)
|
||||
|
||||
folder = found
|
||||
}
|
||||
|
||||
const filter: tl.RawDialogFilter = {
|
||||
...folder,
|
||||
...modification
|
||||
...modification,
|
||||
}
|
||||
|
||||
await this.call({
|
||||
_: 'messages.updateDialogFilter',
|
||||
id: folder.id,
|
||||
filter
|
||||
filter,
|
||||
})
|
||||
|
||||
return filter
|
||||
|
|
|
@ -25,11 +25,13 @@ export async function findFolder(
|
|||
|
||||
const folders = await this.getFolders()
|
||||
|
||||
return folders.find((it) => {
|
||||
if (params.id && it.id !== params.id) return false
|
||||
if (params.title && it.title !== params.title) return false
|
||||
if (params.emoji && it.emoticon !== params.emoji) return false
|
||||
return (
|
||||
folders.find((it) => {
|
||||
if (params.id && it.id !== params.id) return false
|
||||
if (params.title && it.title !== params.title) return false
|
||||
if (params.emoji && it.emoticon !== params.emoji) return false
|
||||
|
||||
return true
|
||||
}) ?? null
|
||||
return true
|
||||
}) ?? null
|
||||
)
|
||||
}
|
||||
|
|
|
@ -132,7 +132,9 @@ export async function* getDialogs(
|
|||
(it) => it.id === params!.folder || it.title === params!.folder
|
||||
)
|
||||
if (!found)
|
||||
throw new MtCuteArgumentError(`Could not find folder ${params.folder}`)
|
||||
throw new MtCuteArgumentError(
|
||||
`Could not find folder ${params.folder}`
|
||||
)
|
||||
|
||||
filters = found
|
||||
} else {
|
||||
|
@ -143,7 +145,7 @@ export async function* getDialogs(
|
|||
if (filters) {
|
||||
filters = {
|
||||
...filters,
|
||||
...params.filter
|
||||
...params.filter,
|
||||
}
|
||||
} else {
|
||||
filters = {
|
||||
|
@ -153,7 +155,7 @@ export async function* getDialogs(
|
|||
pinnedPeers: [],
|
||||
includePeers: [],
|
||||
excludePeers: [],
|
||||
...params.filter
|
||||
...params.filter,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -164,11 +166,13 @@ export async function* getDialogs(
|
|||
_: 'messages.getPeerDialogs',
|
||||
peers: filters.pinnedPeers.map((peer) => ({
|
||||
_: 'inputDialogPeer',
|
||||
peer
|
||||
}))
|
||||
peer,
|
||||
})),
|
||||
})
|
||||
|
||||
res.dialogs.forEach((dialog: tl.Mutable<tl.TypeDialog>) => dialog.pinned = true)
|
||||
res.dialogs.forEach(
|
||||
(dialog: tl.Mutable<tl.TypeDialog>) => (dialog.pinned = true)
|
||||
)
|
||||
|
||||
return res
|
||||
}
|
||||
|
@ -243,11 +247,11 @@ export async function* getDialogs(
|
|||
}
|
||||
|
||||
const filterFolder = filters
|
||||
// if pinned is `only`, this wouldn't be reached
|
||||
// if pinned is `exclude`, we want to exclude them
|
||||
// if pinned is `include`, we already yielded them, so we also want to exclude them
|
||||
// if pinned is `keep`, we want to keep them
|
||||
? Dialog.filterFolder(filters, pinned !== 'keep')
|
||||
? // if pinned is `only`, this wouldn't be reached
|
||||
// if pinned is `exclude`, we want to exclude them
|
||||
// if pinned is `include`, we already yielded them, so we also want to exclude them
|
||||
// if pinned is `keep`, we want to keep them
|
||||
Dialog.filterFolder(filters, pinned !== 'keep')
|
||||
: undefined
|
||||
|
||||
const folderId =
|
||||
|
|
|
@ -5,8 +5,10 @@ import { tl } from '@mtcute/tl'
|
|||
* Get list of folders.
|
||||
* @internal
|
||||
*/
|
||||
export async function getFolders(this: TelegramClient): Promise<tl.RawDialogFilter[]> {
|
||||
export async function getFolders(
|
||||
this: TelegramClient
|
||||
): Promise<tl.RawDialogFilter[]> {
|
||||
return this.call({
|
||||
_: 'messages.getDialogFilters'
|
||||
_: 'messages.getDialogFilters',
|
||||
})
|
||||
}
|
||||
|
|
|
@ -8,7 +8,11 @@ import {
|
|||
FileDownloadParameters,
|
||||
FileLocation,
|
||||
} from '../../types'
|
||||
import { fileIdToInputFileLocation, fileIdToInputWebFileLocation, parseFileId } from '@mtcute/file-id'
|
||||
import {
|
||||
fileIdToInputFileLocation,
|
||||
fileIdToInputWebFileLocation,
|
||||
parseFileId,
|
||||
} from '@mtcute/file-id'
|
||||
|
||||
/**
|
||||
* Download a file and return it as an iterable, which yields file contents
|
||||
|
@ -84,14 +88,19 @@ export async function* downloadAsIterable(
|
|||
}
|
||||
|
||||
const requestCurrent = async (): Promise<Buffer> => {
|
||||
let result: tl.RpcCallReturn['upload.getFile'] | tl.RpcCallReturn['upload.getWebFile']
|
||||
let result:
|
||||
| tl.RpcCallReturn['upload.getFile']
|
||||
| tl.RpcCallReturn['upload.getWebFile']
|
||||
try {
|
||||
result = await this.call({
|
||||
_: isWeb ? 'upload.getWebFile' : 'upload.getFile',
|
||||
location: location as any,
|
||||
offset,
|
||||
limit: chunkSize
|
||||
}, { connection })
|
||||
result = await this.call(
|
||||
{
|
||||
_: isWeb ? 'upload.getWebFile' : 'upload.getFile',
|
||||
location: location as any,
|
||||
offset,
|
||||
limit: chunkSize,
|
||||
},
|
||||
{ connection }
|
||||
)
|
||||
} catch (e) {
|
||||
if (e.constructor === FileMigrateError) {
|
||||
connection = this._downloadConnections[e.newDc]
|
||||
|
@ -116,7 +125,11 @@ export async function* downloadAsIterable(
|
|||
)
|
||||
}
|
||||
|
||||
if (result._ === 'upload.webFile' && result.size && limit === Infinity) {
|
||||
if (
|
||||
result._ === 'upload.webFile' &&
|
||||
result.size &&
|
||||
limit === Infinity
|
||||
) {
|
||||
limit = result.size
|
||||
}
|
||||
|
||||
|
|
|
@ -11,16 +11,20 @@ export async function _normalizeFileToDocument(
|
|||
file: InputFileLike | tl.TypeInputDocument,
|
||||
params: {
|
||||
progressCallback?: (uploaded: number, total: number) => void
|
||||
},
|
||||
}
|
||||
): Promise<tl.TypeInputDocument> {
|
||||
if (typeof file === 'object' && tl.isAnyInputDocument(file)) {
|
||||
return file
|
||||
}
|
||||
|
||||
const media = await this._normalizeInputMedia({
|
||||
type: 'document',
|
||||
file,
|
||||
}, params, true)
|
||||
const media = await this._normalizeInputMedia(
|
||||
{
|
||||
type: 'document',
|
||||
file,
|
||||
},
|
||||
params,
|
||||
true
|
||||
)
|
||||
|
||||
assertTypeIs('createStickerSet', media, 'inputMediaDocument')
|
||||
assertTypeIs('createStickerSet', media.id, 'inputDocument')
|
||||
|
|
|
@ -24,7 +24,7 @@ const debug = require('debug')('mtcute:upload')
|
|||
|
||||
const OVERRIDE_MIME: Record<string, string> = {
|
||||
// tg doesn't interpret `audio/opus` files as voice messages for some reason
|
||||
'audio/opus': 'audio/ogg'
|
||||
'audio/opus': 'audio/ogg',
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -36,7 +36,7 @@ export async function createInviteLink(
|
|||
_: 'messages.exportChatInvite',
|
||||
peer: await this.resolvePeer(chatId),
|
||||
expireDate: normalizeDate(params.expires),
|
||||
usageLimit: params.usageLimit
|
||||
usageLimit: params.usageLimit,
|
||||
})
|
||||
|
||||
return new ChatInviteLink(this, res)
|
||||
|
|
|
@ -40,7 +40,7 @@ export async function editInviteLink(
|
|||
peer: await this.resolvePeer(chatId),
|
||||
link,
|
||||
expireDate: normalizeDate(params.expires),
|
||||
usageLimit: params.usageLimit
|
||||
usageLimit: params.usageLimit,
|
||||
})
|
||||
|
||||
const { users } = createUsersChatsIndex(res)
|
||||
|
|
|
@ -18,7 +18,7 @@ export async function exportInviteLink(
|
|||
const res = await this.call({
|
||||
_: 'messages.exportChatInvite',
|
||||
peer: await this.resolvePeer(chatId),
|
||||
legacyRevokePermanent: true
|
||||
legacyRevokePermanent: true,
|
||||
})
|
||||
|
||||
return new ChatInviteLink(this, res)
|
||||
|
|
|
@ -27,14 +27,16 @@ export async function* getInviteLinkMembers(
|
|||
|
||||
for (;;) {
|
||||
// for some reason ts needs annotation, idk
|
||||
const res: tl.RpcCallReturn['messages.getChatInviteImporters'] = await this.call({
|
||||
_: 'messages.getChatInviteImporters',
|
||||
limit: Math.min(100, limit - current),
|
||||
peer,
|
||||
link,
|
||||
offsetDate,
|
||||
offsetUser,
|
||||
})
|
||||
const res: tl.RpcCallReturn['messages.getChatInviteImporters'] = await this.call(
|
||||
{
|
||||
_: 'messages.getChatInviteImporters',
|
||||
limit: Math.min(100, limit - current),
|
||||
peer,
|
||||
link,
|
||||
offsetDate,
|
||||
offsetUser,
|
||||
}
|
||||
)
|
||||
|
||||
if (!res.importers.length) break
|
||||
|
||||
|
@ -45,7 +47,7 @@ export async function* getInviteLinkMembers(
|
|||
offsetUser = {
|
||||
_: 'inputUser',
|
||||
userId: last.userId,
|
||||
accessHash: (users[last.userId] as tl.RawUser).accessHash!
|
||||
accessHash: (users[last.userId] as tl.RawUser).accessHash!,
|
||||
}
|
||||
|
||||
for (const it of res.importers) {
|
||||
|
|
|
@ -17,7 +17,7 @@ export async function getInviteLink(
|
|||
const res = await this.call({
|
||||
_: 'messages.getExportedChatInvite',
|
||||
peer: await this.resolvePeer(chatId),
|
||||
link
|
||||
link,
|
||||
})
|
||||
|
||||
const { users } = createUsersChatsIndex(res)
|
||||
|
|
|
@ -22,12 +22,15 @@ export async function revokeInviteLink(
|
|||
_: 'messages.editExportedChatInvite',
|
||||
peer: await this.resolvePeer(chatId),
|
||||
link,
|
||||
revoked: true
|
||||
revoked: true,
|
||||
})
|
||||
|
||||
const { users } = createUsersChatsIndex(res)
|
||||
|
||||
const invite = res._ === 'messages.exportedChatInviteReplaced' ? res.newInvite : res.invite
|
||||
const invite =
|
||||
res._ === 'messages.exportedChatInviteReplaced'
|
||||
? res.newInvite
|
||||
: res.invite
|
||||
|
||||
return new ChatInviteLink(this, invite, users)
|
||||
}
|
||||
|
|
|
@ -16,14 +16,13 @@ export function _findMessageInUpdate(
|
|||
|
||||
for (const u of res.updates) {
|
||||
if (
|
||||
isEdit && (
|
||||
u._ === 'updateEditMessage' ||
|
||||
u._ === 'updateEditChannelMessage'
|
||||
) || !isEdit && (
|
||||
u._ === 'updateNewMessage' ||
|
||||
u._ === 'updateNewChannelMessage' ||
|
||||
u._ === 'updateNewScheduledMessage'
|
||||
)
|
||||
(isEdit &&
|
||||
(u._ === 'updateEditMessage' ||
|
||||
u._ === 'updateEditChannelMessage')) ||
|
||||
(!isEdit &&
|
||||
(u._ === 'updateNewMessage' ||
|
||||
u._ === 'updateNewChannelMessage' ||
|
||||
u._ === 'updateNewScheduledMessage'))
|
||||
) {
|
||||
const { users, chats } = createUsersChatsIndex(res)
|
||||
|
||||
|
|
|
@ -8,9 +8,7 @@ import {
|
|||
} from '../../types'
|
||||
import { MaybeArray } from '@mtcute/core'
|
||||
import { tl } from '@mtcute/tl'
|
||||
import {
|
||||
createUsersChatsIndex,
|
||||
} from '../../utils/peer-utils'
|
||||
import { createUsersChatsIndex } from '../../utils/peer-utils'
|
||||
import { normalizeDate, randomUlong } from '../../utils/misc-utils'
|
||||
import { assertIsUpdatesGroup } from '../../utils/updates-utils'
|
||||
|
||||
|
|
|
@ -29,8 +29,8 @@ export async function _getDiscussionMessage(
|
|||
{
|
||||
_: 'inputPeerChannel',
|
||||
channelId: chat.id,
|
||||
accessHash: chat.accessHash!
|
||||
accessHash: chat.accessHash!,
|
||||
},
|
||||
msg.id
|
||||
msg.id,
|
||||
]
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { TelegramClient } from '../../client'
|
||||
import { InputPeerLike, Message, MtCuteTypeAssertionError } from '../../types'
|
||||
import { createUsersChatsIndex, } from '../../utils/peer-utils'
|
||||
import { createUsersChatsIndex } from '../../utils/peer-utils'
|
||||
import { normalizeDate } from '../../utils/misc-utils'
|
||||
|
||||
/**
|
||||
|
|
|
@ -24,8 +24,7 @@ export async function getMessageGroup(
|
|||
const messages = await this.getMessages(chatId, ids)
|
||||
const groupedId = messages.find((it) => it.id === message)!.groupedId
|
||||
|
||||
if (!groupedId)
|
||||
throw new MtCuteArgumentError('This message is not grouped')
|
||||
if (!groupedId) throw new MtCuteArgumentError('This message is not grouped')
|
||||
|
||||
return messages.filter((it) => it.groupedId?.eq(groupedId))
|
||||
}
|
||||
|
|
|
@ -27,7 +27,7 @@ export async function _normalizeInline(
|
|||
if (!(id.dcId in this._connectionsForInline)) {
|
||||
this._connectionsForInline[
|
||||
id.dcId
|
||||
] = await this.createAdditionalConnection(id.dcId)
|
||||
] = await this.createAdditionalConnection(id.dcId)
|
||||
}
|
||||
connection = this._connectionsForInline[id.dcId]
|
||||
}
|
||||
|
|
|
@ -21,8 +21,7 @@ export async function _parseEntities(
|
|||
}
|
||||
// either explicitly disabled or no available parser
|
||||
if (!mode) return [text, []]
|
||||
|
||||
;([text, entities] = await this._parseModes[mode].parse(text))
|
||||
;[text, entities] = await this._parseModes[mode].parse(text)
|
||||
}
|
||||
|
||||
// replace mentionName entities with input ones
|
||||
|
|
|
@ -25,7 +25,7 @@ export async function pinMessage(
|
|||
peer: await this.resolvePeer(chatId),
|
||||
id: messageId,
|
||||
silent: !notify,
|
||||
pmOneside: !bothSides
|
||||
pmOneside: !bothSides,
|
||||
})
|
||||
|
||||
this._handleUpdate(res)
|
||||
|
|
|
@ -6,7 +6,11 @@ import {
|
|||
Message,
|
||||
ReplyMarkup,
|
||||
} from '../../types'
|
||||
import { normalizeDate, normalizeMessageId, randomUlong } from '../../utils/misc-utils'
|
||||
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'
|
||||
|
@ -95,7 +99,10 @@ export async function sendMediaGroup(
|
|||
|
||||
let replyTo = normalizeMessageId(params.replyTo)
|
||||
if (params.commentTo) {
|
||||
;[peer, replyTo] = await this._getDiscussionMessage(peer, normalizeMessageId(params.commentTo)!)
|
||||
;[peer, replyTo] = await this._getDiscussionMessage(
|
||||
peer,
|
||||
normalizeMessageId(params.commentTo)!
|
||||
)
|
||||
}
|
||||
|
||||
const multiMedia: tl.RawInputSingleMedia[] = []
|
||||
|
|
|
@ -6,7 +6,11 @@ import {
|
|||
Message,
|
||||
ReplyMarkup,
|
||||
} from '../../types'
|
||||
import { normalizeDate, normalizeMessageId, randomUlong } from '../../utils/misc-utils'
|
||||
import {
|
||||
normalizeDate,
|
||||
normalizeMessageId,
|
||||
randomUlong,
|
||||
} from '../../utils/misc-utils'
|
||||
|
||||
/**
|
||||
* Send a single media (a photo or a document-based media)
|
||||
|
@ -108,7 +112,10 @@ export async function sendMedia(
|
|||
|
||||
let replyTo = normalizeMessageId(params.replyTo)
|
||||
if (params.commentTo) {
|
||||
;[peer, replyTo] = await this._getDiscussionMessage(peer, normalizeMessageId(params.commentTo)!)
|
||||
;[peer, replyTo] = await this._getDiscussionMessage(
|
||||
peer,
|
||||
normalizeMessageId(params.commentTo)!
|
||||
)
|
||||
}
|
||||
|
||||
const res = await this.call({
|
||||
|
|
|
@ -1,7 +1,11 @@
|
|||
import { TelegramClient } from '../../client'
|
||||
import { tl } from '@mtcute/tl'
|
||||
import { inputPeerToPeer } from '../../utils/peer-utils'
|
||||
import { normalizeDate, normalizeMessageId, randomUlong } from '../../utils/misc-utils'
|
||||
import {
|
||||
normalizeDate,
|
||||
normalizeMessageId,
|
||||
randomUlong,
|
||||
} from '../../utils/misc-utils'
|
||||
import { InputPeerLike, Message, BotKeyboard, ReplyMarkup } from '../../types'
|
||||
|
||||
/**
|
||||
|
@ -91,7 +95,10 @@ export async function sendText(
|
|||
|
||||
let replyTo = normalizeMessageId(params.replyTo)
|
||||
if (params.commentTo) {
|
||||
;[peer, replyTo] = await this._getDiscussionMessage(peer, normalizeMessageId(params.commentTo)!)
|
||||
;[peer, replyTo] = await this._getDiscussionMessage(
|
||||
peer,
|
||||
normalizeMessageId(params.commentTo)!
|
||||
)
|
||||
}
|
||||
|
||||
const res = await this.call({
|
||||
|
|
|
@ -36,49 +36,49 @@ export async function sendTyping(
|
|||
switch (status) {
|
||||
case 'typing':
|
||||
status = { _: 'sendMessageTypingAction' }
|
||||
break;
|
||||
break
|
||||
case 'cancel':
|
||||
status = { _: 'sendMessageCancelAction' }
|
||||
break;
|
||||
break
|
||||
case 'record_video':
|
||||
status = { _: 'sendMessageRecordVideoAction' }
|
||||
break;
|
||||
break
|
||||
case 'upload_video':
|
||||
status = { _: 'sendMessageUploadVideoAction', progress }
|
||||
break;
|
||||
break
|
||||
case 'record_voice':
|
||||
status = { _: 'sendMessageRecordAudioAction' }
|
||||
break;
|
||||
break
|
||||
case 'upload_voice':
|
||||
status = { _: 'sendMessageUploadAudioAction', progress }
|
||||
break;
|
||||
break
|
||||
case 'upload_photo':
|
||||
status = { _: 'sendMessageUploadPhotoAction', progress }
|
||||
break;
|
||||
break
|
||||
case 'upload_document':
|
||||
status = { _: 'sendMessageUploadDocumentAction', progress }
|
||||
break;
|
||||
break
|
||||
case 'geo':
|
||||
status = { _: 'sendMessageGeoLocationAction' }
|
||||
break;
|
||||
break
|
||||
case 'contact':
|
||||
status = { _: 'sendMessageChooseContactAction' }
|
||||
break;
|
||||
break
|
||||
case 'game':
|
||||
status = { _: 'sendMessageGamePlayAction' }
|
||||
break;
|
||||
break
|
||||
case 'record_round':
|
||||
status = { _: 'sendMessageRecordRoundAction' }
|
||||
break;
|
||||
break
|
||||
case 'upload_round':
|
||||
status = { _: 'sendMessageUploadRoundAction', progress }
|
||||
break;
|
||||
break
|
||||
case 'speak_call':
|
||||
status = { _: 'speakingInGroupCallAction' }
|
||||
break;
|
||||
break
|
||||
case 'history_import':
|
||||
status = { _: 'sendMessageHistoryImportAction', progress }
|
||||
break;
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -86,6 +86,6 @@ export async function sendTyping(
|
|||
_: 'messages.setTyping',
|
||||
peer: await this.resolvePeer(chatId),
|
||||
action: status,
|
||||
topMsgId: params?.threadId
|
||||
topMsgId: params?.threadId,
|
||||
})
|
||||
}
|
||||
|
|
|
@ -17,11 +17,13 @@ export async function unpinAllMessages(
|
|||
|
||||
const res = await this.call({
|
||||
_: 'messages.unpinAllMessages',
|
||||
peer
|
||||
peer,
|
||||
})
|
||||
|
||||
if (isInputPeerChannel(peer)) {
|
||||
this._handleUpdate(createDummyUpdate(res.pts, res.ptsCount, peer.channelId))
|
||||
this._handleUpdate(
|
||||
createDummyUpdate(res.pts, res.ptsCount, peer.channelId)
|
||||
)
|
||||
} else {
|
||||
this._handleUpdate(createDummyUpdate(res.pts, res.ptsCount))
|
||||
}
|
||||
|
|
|
@ -14,13 +14,13 @@ import { InputPeerLike } from '../../types'
|
|||
export async function unpinMessage(
|
||||
this: TelegramClient,
|
||||
chatId: InputPeerLike,
|
||||
messageId: number,
|
||||
messageId: number
|
||||
): Promise<void> {
|
||||
const res = await this.call({
|
||||
_: 'messages.updatePinnedMessage',
|
||||
peer: await this.resolvePeer(chatId),
|
||||
id: messageId,
|
||||
unpin: true
|
||||
unpin: true,
|
||||
})
|
||||
|
||||
this._handleUpdate(res)
|
||||
|
|
|
@ -12,8 +12,11 @@ export async function initTakeoutSession(
|
|||
this: TelegramClient,
|
||||
params: Omit<tl.account.RawInitTakeoutSessionRequest, '_'>
|
||||
): Promise<TakeoutSession> {
|
||||
return new TakeoutSession(this, await this.call({
|
||||
_: 'account.initTakeoutSession',
|
||||
...params
|
||||
}))
|
||||
return new TakeoutSession(
|
||||
this,
|
||||
await this.call({
|
||||
_: 'account.initTakeoutSession',
|
||||
...params,
|
||||
})
|
||||
)
|
||||
}
|
||||
|
|
|
@ -29,7 +29,11 @@ export async function changeCloudPassword(
|
|||
)
|
||||
|
||||
const oldSrp = await computeSrpParams(this._crypto, pwd, currentPassword)
|
||||
const newHash = await computeNewPasswordHash(this._crypto, algo, newPassword)
|
||||
const newHash = await computeNewPasswordHash(
|
||||
this._crypto,
|
||||
algo,
|
||||
newPassword
|
||||
)
|
||||
|
||||
await this.call({
|
||||
_: 'account.updatePasswordSettings',
|
||||
|
@ -38,7 +42,7 @@ export async function changeCloudPassword(
|
|||
_: 'account.passwordInputSettings',
|
||||
newAlgo: algo,
|
||||
newPasswordHash: newHash,
|
||||
hint
|
||||
}
|
||||
hint,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
|
|
@ -43,7 +43,7 @@ export async function enableCloudPassword(
|
|||
newAlgo: algo,
|
||||
newPasswordHash: newHash,
|
||||
hint,
|
||||
email
|
||||
}
|
||||
email,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@ export async function verifyPasswordEmail(
|
|||
): Promise<void> {
|
||||
await this.call({
|
||||
_: 'account.confirmPasswordEmail',
|
||||
code
|
||||
code,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -23,7 +23,7 @@ export async function verifyPasswordEmail(
|
|||
*/
|
||||
export async function resendPasswordEmail(this: TelegramClient): Promise<void> {
|
||||
await this.call({
|
||||
_: 'account.resendPasswordEmail'
|
||||
_: 'account.resendPasswordEmail',
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -34,6 +34,6 @@ export async function resendPasswordEmail(this: TelegramClient): Promise<void> {
|
|||
*/
|
||||
export async function cancelPasswordEmail(this: TelegramClient): Promise<void> {
|
||||
await this.call({
|
||||
_: 'account.cancelPasswordEmail'
|
||||
_: 'account.cancelPasswordEmail',
|
||||
})
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ import { computeSrpParams } from '@mtcute/core'
|
|||
*/
|
||||
export async function removeCloudPassword(
|
||||
this: TelegramClient,
|
||||
password: string,
|
||||
password: string
|
||||
): Promise<void> {
|
||||
const pwd = await this.call({ _: 'account.getPassword' })
|
||||
if (!pwd.hasPassword)
|
||||
|
@ -25,7 +25,7 @@ export async function removeCloudPassword(
|
|||
_: 'account.passwordInputSettings',
|
||||
newAlgo: { _: 'passwordKdfAlgoUnknown' },
|
||||
newPasswordHash: Buffer.alloc(0),
|
||||
hint: ''
|
||||
}
|
||||
hint: '',
|
||||
},
|
||||
})
|
||||
}
|
||||
|
|
|
@ -33,12 +33,12 @@ export async function addStickerToSet(
|
|||
* @param total Total file size
|
||||
*/
|
||||
progressCallback?: (uploaded: number, total: number) => void
|
||||
},
|
||||
}
|
||||
): Promise<StickerSet> {
|
||||
if (typeof id === 'string') {
|
||||
id = {
|
||||
_: 'inputStickerSetShortName',
|
||||
shortName: id
|
||||
shortName: id,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -47,18 +47,21 @@ export async function addStickerToSet(
|
|||
stickerset: id,
|
||||
sticker: {
|
||||
_: 'inputStickerSetItem',
|
||||
document: await this._normalizeFileToDocument(sticker.file, params ?? {}),
|
||||
document: await this._normalizeFileToDocument(
|
||||
sticker.file,
|
||||
params ?? {}
|
||||
),
|
||||
emoji: sticker.emojis,
|
||||
maskCoords: sticker.maskPosition
|
||||
? {
|
||||
_: 'maskCoords',
|
||||
n: MASK_POS[sticker.maskPosition.point],
|
||||
x: sticker.maskPosition.x,
|
||||
y: sticker.maskPosition.y,
|
||||
zoom: sticker.maskPosition.scale,
|
||||
}
|
||||
_: 'maskCoords',
|
||||
n: MASK_POS[sticker.maskPosition.point],
|
||||
x: sticker.maskPosition.x,
|
||||
y: sticker.maskPosition.y,
|
||||
zoom: sticker.maskPosition.scale,
|
||||
}
|
||||
: undefined,
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
return new StickerSet(this, res)
|
||||
|
|
|
@ -25,7 +25,7 @@ export async function deleteStickerFromSet(
|
|||
|
||||
const res = await this.call({
|
||||
_: 'stickers.removeStickerFromSet',
|
||||
sticker
|
||||
sticker,
|
||||
})
|
||||
|
||||
return new StickerSet(this, res)
|
||||
|
|
|
@ -17,7 +17,7 @@ export async function getInstalledStickers(
|
|||
): Promise<StickerSet[]> {
|
||||
const res = await this.call({
|
||||
_: 'messages.getAllStickers',
|
||||
hash: 0
|
||||
hash: 0,
|
||||
})
|
||||
|
||||
assertTypeIs('getInstalledStickers', res, 'messages.allStickers')
|
||||
|
|
|
@ -14,16 +14,19 @@ export async function getStickerSet(
|
|||
): Promise<StickerSet> {
|
||||
let input: tl.TypeInputStickerSet
|
||||
if (typeof id === 'string') {
|
||||
input = id === 'emoji' ? {
|
||||
_: 'inputStickerSetAnimatedEmoji'
|
||||
} : {
|
||||
_: 'inputStickerSetShortName',
|
||||
shortName: id
|
||||
}
|
||||
input =
|
||||
id === 'emoji'
|
||||
? {
|
||||
_: 'inputStickerSetAnimatedEmoji',
|
||||
}
|
||||
: {
|
||||
_: 'inputStickerSetShortName',
|
||||
shortName: id,
|
||||
}
|
||||
} else if ('dice' in id) {
|
||||
input = {
|
||||
_: 'inputStickerSetDice',
|
||||
emoticon: id.dice
|
||||
emoticon: id.dice,
|
||||
}
|
||||
} else {
|
||||
input = id
|
||||
|
@ -31,7 +34,7 @@ export async function getStickerSet(
|
|||
|
||||
const res = await this.call({
|
||||
_: 'messages.getStickerSet',
|
||||
stickerset: input
|
||||
stickerset: input,
|
||||
})
|
||||
|
||||
return new StickerSet(this, res)
|
||||
|
|
|
@ -30,7 +30,7 @@ export async function moveStickerInSet(
|
|||
const res = await this.call({
|
||||
_: 'stickers.changeStickerPosition',
|
||||
sticker,
|
||||
position
|
||||
position,
|
||||
})
|
||||
|
||||
return new StickerSet(this, res)
|
||||
|
|
|
@ -23,19 +23,19 @@ export async function setStickerSetThumb(
|
|||
* @param total Total file size
|
||||
*/
|
||||
progressCallback?: (uploaded: number, total: number) => void
|
||||
},
|
||||
}
|
||||
): Promise<StickerSet> {
|
||||
if (typeof id === 'string') {
|
||||
id = {
|
||||
_: 'inputStickerSetShortName',
|
||||
shortName: id
|
||||
shortName: id,
|
||||
}
|
||||
}
|
||||
|
||||
const res = await this.call({
|
||||
_: 'stickers.setStickerSetThumb',
|
||||
stickerset: id,
|
||||
thumb: await this._normalizeFileToDocument(thumb, params ?? {})
|
||||
thumb: await this._normalizeFileToDocument(thumb, params ?? {}),
|
||||
})
|
||||
|
||||
return new StickerSet(this, res)
|
||||
|
|
|
@ -106,7 +106,10 @@ export async function _loadStorage(this: TelegramClient): Promise<void> {
|
|||
/**
|
||||
* @internal
|
||||
*/
|
||||
export async function _saveStorage(this: TelegramClient, afterImport = false): Promise<void> {
|
||||
export async function _saveStorage(
|
||||
this: TelegramClient,
|
||||
afterImport = false
|
||||
): Promise<void> {
|
||||
// save updates state to the session
|
||||
|
||||
if (afterImport) {
|
||||
|
@ -312,7 +315,10 @@ async function _fetchPeersForShort(
|
|||
case 'updateNewChannelMessage':
|
||||
case 'updateEditMessage':
|
||||
case 'updateEditChannelMessage': {
|
||||
const msg = upd._ === 'message' || upd._ === 'messageService' ? upd : upd.message
|
||||
const msg =
|
||||
upd._ === 'message' || upd._ === 'messageService'
|
||||
? upd
|
||||
: upd.message
|
||||
if (msg._ === 'messageEmpty') return null
|
||||
|
||||
// ref: https://github.com/tdlib/td/blob/e1ebf743988edfcf4400cd5d33a664ff941dc13e/td/telegram/UpdatesManager.cpp#L412
|
||||
|
@ -357,13 +363,19 @@ async function _fetchPeersForShort(
|
|||
}
|
||||
break
|
||||
case 'messageActionChatJoinedByLink':
|
||||
if (!(await fetchPeer(msg.action.inviterId))) return null
|
||||
if (!(await fetchPeer(msg.action.inviterId)))
|
||||
return null
|
||||
break
|
||||
case 'messageActionChatDeleteUser':
|
||||
if (!(await fetchPeer(msg.action.userId))) return null
|
||||
break
|
||||
case 'messageActionChatMigrateTo':
|
||||
if (!(await fetchPeer(MAX_CHANNEL_ID - msg.action.channelId))) return null
|
||||
if (
|
||||
!(await fetchPeer(
|
||||
MAX_CHANNEL_ID - msg.action.channelId
|
||||
))
|
||||
)
|
||||
return null
|
||||
break
|
||||
case 'messageActionChannelMigrateFrom':
|
||||
if (!(await fetchPeer(-msg.action.chatId))) return null
|
||||
|
@ -465,7 +477,12 @@ async function _loadDifference(
|
|||
if (nextLocalPts) {
|
||||
if (nextLocalPts > pts) continue
|
||||
if (nextLocalPts < pts) {
|
||||
await _loadChannelDifference.call(this, cid, noDispatch, pts)
|
||||
await _loadChannelDifference.call(
|
||||
this,
|
||||
cid,
|
||||
noDispatch,
|
||||
pts
|
||||
)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
@ -735,7 +752,10 @@ export function _handleUpdate(
|
|||
this._config = await this.call({ _: 'help.getConfig' })
|
||||
} else {
|
||||
if (!noDispatch) {
|
||||
const peers = await _fetchPeersForShort.call(this, upd)
|
||||
const peers = await _fetchPeersForShort.call(
|
||||
this,
|
||||
upd
|
||||
)
|
||||
if (!peers) {
|
||||
// some peer is not cached.
|
||||
// need to re-fetch the thing, and cache them on the way
|
||||
|
|
|
@ -25,6 +25,6 @@ export async function deleteProfilePhotos(
|
|||
|
||||
await this.call({
|
||||
_: 'photos.deletePhotos',
|
||||
id: photos
|
||||
id: photos,
|
||||
})
|
||||
}
|
||||
|
|
|
@ -40,7 +40,7 @@ export async function getProfilePhotos(
|
|||
userId: peer,
|
||||
offset: params.offset ?? 0,
|
||||
limit: params.limit ?? 100,
|
||||
maxId: bigInt.zero
|
||||
maxId: bigInt.zero,
|
||||
})
|
||||
|
||||
return res.photos.map((it) => new Photo(this, it as tl.RawPhoto))
|
||||
|
|
|
@ -62,7 +62,7 @@ export async function* iterProfilePhotos(
|
|||
userId: peer,
|
||||
limit: Math.min(limit, total - current),
|
||||
offset,
|
||||
maxId
|
||||
maxId,
|
||||
})
|
||||
|
||||
if (!res.photos.length) break
|
||||
|
|
|
@ -19,9 +19,7 @@ export async function resolvePeerMany<
|
|||
>(
|
||||
this: TelegramClient,
|
||||
peerIds: InputPeerLike[],
|
||||
normalizer: (
|
||||
obj: tl.TypeInputPeer
|
||||
) => T | null
|
||||
normalizer: (obj: tl.TypeInputPeer) => T | null
|
||||
): Promise<T[]>
|
||||
|
||||
/**
|
||||
|
|
|
@ -75,7 +75,10 @@ export async function resolvePeer(
|
|||
accessHash: found.accessHash!,
|
||||
}
|
||||
} else {
|
||||
const id = res.peer._ === 'peerChannel' ? res.peer.channelId : res.peer.chatId
|
||||
const id =
|
||||
res.peer._ === 'peerChannel'
|
||||
? res.peer.channelId
|
||||
: res.peer.chatId
|
||||
|
||||
const found = res.chats.find((it) => it.id === id)
|
||||
if (found)
|
||||
|
@ -85,13 +88,13 @@ export async function resolvePeer(
|
|||
return {
|
||||
_: 'inputPeerChannel',
|
||||
channelId: found.id,
|
||||
accessHash: found.accessHash!
|
||||
accessHash: found.accessHash!,
|
||||
}
|
||||
case 'chat':
|
||||
case 'chatForbidden':
|
||||
return {
|
||||
_: 'inputPeerChat',
|
||||
chatId: found.id
|
||||
chatId: found.id,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -145,7 +148,7 @@ export async function resolvePeer(
|
|||
|
||||
return {
|
||||
_: 'inputPeerChat',
|
||||
chatId: -peerId
|
||||
chatId: -peerId,
|
||||
}
|
||||
// break
|
||||
}
|
||||
|
@ -163,7 +166,10 @@ export async function resolvePeer(
|
|||
})
|
||||
|
||||
const found = res.chats.find((it) => it.id === id)
|
||||
if (found && (found._ === 'channel' || found._ === 'channelForbidden'))
|
||||
if (
|
||||
found &&
|
||||
(found._ === 'channel' || found._ === 'channelForbidden')
|
||||
)
|
||||
return {
|
||||
_: 'inputPeerChannel',
|
||||
channelId: found.id,
|
||||
|
|
|
@ -12,6 +12,6 @@ export async function setOffline(
|
|||
): Promise<void> {
|
||||
await this.call({
|
||||
_: 'account.updateStatus',
|
||||
offline
|
||||
offline,
|
||||
})
|
||||
}
|
||||
|
|
|
@ -20,8 +20,11 @@ export async function setProfilePhoto(
|
|||
): Promise<Photo> {
|
||||
const res = await this.call({
|
||||
_: 'photos.uploadProfilePhoto',
|
||||
[type === 'photo' ? 'file' : 'video']: await this._normalizeInputFile(media, {}),
|
||||
videoStartTs: previewSec
|
||||
[type === 'photo' ? 'file' : 'video']: await this._normalizeInputFile(
|
||||
media,
|
||||
{}
|
||||
),
|
||||
videoStartTs: previewSec,
|
||||
})
|
||||
|
||||
return new Photo(this, res.photo as tl.RawPhoto)
|
||||
|
|
|
@ -32,7 +32,7 @@ export async function updateProfile(
|
|||
_: 'account.updateProfile',
|
||||
firstName: params.firstName,
|
||||
lastName: params.lastName,
|
||||
about: params.bio
|
||||
about: params.bio,
|
||||
})
|
||||
|
||||
return new User(this, res)
|
||||
|
|
|
@ -18,7 +18,7 @@ export async function updateUsername(
|
|||
|
||||
const res = await this.call({
|
||||
_: 'account.updateUsername',
|
||||
username
|
||||
username,
|
||||
})
|
||||
|
||||
return new User(this, res)
|
||||
|
|
|
@ -12,7 +12,11 @@ export class GameHighScore {
|
|||
|
||||
readonly _users: UsersIndex
|
||||
|
||||
constructor (client: TelegramClient, raw: tl.RawHighScore, users: UsersIndex) {
|
||||
constructor(
|
||||
client: TelegramClient,
|
||||
raw: tl.RawHighScore,
|
||||
users: UsersIndex
|
||||
) {
|
||||
this.client = client
|
||||
this.raw = raw
|
||||
this._users = users
|
||||
|
|
|
@ -160,7 +160,10 @@ export namespace BotInlineMessage {
|
|||
export function geo(
|
||||
latitude: number,
|
||||
longitude: number,
|
||||
params: Omit<InputInlineMessageGeo, 'type' | 'latitude' | 'longitude'> = {}
|
||||
params: Omit<
|
||||
InputInlineMessageGeo,
|
||||
'type' | 'latitude' | 'longitude'
|
||||
> = {}
|
||||
): InputInlineMessageGeo {
|
||||
const ret = params as tl.Mutable<InputInlineMessageGeo>
|
||||
ret.type = 'geo'
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { tl } from '@mtcute/tl'
|
||||
import { BotInlineMessage, InputInlineMessage, InputInlineMessageGame } from './input-inline-message'
|
||||
import { BotInlineMessage, InputInlineMessage } from './input-inline-message'
|
||||
import { TelegramClient } from '../../../client'
|
||||
import { fileIdToInputDocument, fileIdToInputPhoto } from '@mtcute/file-id'
|
||||
import { extractFileName } from '../../../utils/file-utils'
|
||||
|
@ -711,7 +711,12 @@ export namespace BotInline {
|
|||
obj: InputInlineResult,
|
||||
fallback?: string
|
||||
): tl.RawInputWebDocument | undefined => {
|
||||
if (obj.type !== 'voice' && obj.type !== 'audio' && obj.type !== 'sticker' && obj.type !== 'game') {
|
||||
if (
|
||||
obj.type !== 'voice' &&
|
||||
obj.type !== 'audio' &&
|
||||
obj.type !== 'sticker' &&
|
||||
obj.type !== 'game'
|
||||
) {
|
||||
if (!obj.thumb || typeof obj.thumb === 'string') {
|
||||
if (!obj.thumb && !fallback) {
|
||||
return undefined
|
||||
|
@ -805,11 +810,13 @@ export namespace BotInline {
|
|||
parseMode
|
||||
)
|
||||
if (sendMessage._ !== 'inputBotInlineMessageGame') {
|
||||
throw new MtCuteArgumentError('game inline result must contain a game inline message')
|
||||
throw new MtCuteArgumentError(
|
||||
'game inline result must contain a game inline message'
|
||||
)
|
||||
}
|
||||
} else {
|
||||
sendMessage = {
|
||||
_: 'inputBotInlineMessageGame'
|
||||
_: 'inputBotInlineMessageGame',
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -817,7 +824,7 @@ export namespace BotInline {
|
|||
_: 'inputBotInlineResultGame',
|
||||
id: obj.id,
|
||||
shortName: obj.shortName,
|
||||
sendMessage
|
||||
sendMessage,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -830,7 +837,9 @@ export namespace BotInline {
|
|||
)
|
||||
} else {
|
||||
if (obj.type === 'venue')
|
||||
throw new MtCuteArgumentError('message bust be supplied for venue inline result')
|
||||
throw new MtCuteArgumentError(
|
||||
'message bust be supplied for venue inline result'
|
||||
)
|
||||
|
||||
if (
|
||||
obj.type === 'video' &&
|
||||
|
@ -856,8 +865,7 @@ export namespace BotInline {
|
|||
phoneNumber: obj.phone,
|
||||
firstName: obj.firstName,
|
||||
lastName: obj.lastName ?? '',
|
||||
vcard: ''
|
||||
|
||||
vcard: '',
|
||||
}
|
||||
} else {
|
||||
sendMessage = {
|
||||
|
@ -872,12 +880,18 @@ export namespace BotInline {
|
|||
| tl.TypeInputDocument
|
||||
| tl.TypeInputPhoto
|
||||
| undefined = undefined
|
||||
if (obj.type !== 'geo' && obj.type !== 'venue' && obj.type !== 'contact') {
|
||||
if (
|
||||
obj.type !== 'geo' &&
|
||||
obj.type !== 'venue' &&
|
||||
obj.type !== 'contact'
|
||||
) {
|
||||
if (typeof obj.media === 'string') {
|
||||
// file id or url
|
||||
if (obj.media.match(/^https?:\/\//)) {
|
||||
if (obj.type === 'sticker')
|
||||
throw new MtCuteArgumentError('sticker inline result cannot contain a URL')
|
||||
throw new MtCuteArgumentError(
|
||||
'sticker inline result cannot contain a URL'
|
||||
)
|
||||
|
||||
let mime: string
|
||||
if (obj.type === 'video') mime = 'video/mp4'
|
||||
|
@ -959,7 +973,9 @@ export namespace BotInline {
|
|||
// but whatever.
|
||||
// ref: https://github.com/tdlib/td/blob/master/td/telegram/InlineQueriesManager.cpp
|
||||
if (obj.type === 'contact') {
|
||||
title = obj.lastName?.length ? `${obj.firstName} ${obj.lastName}` : obj.firstName
|
||||
title = obj.lastName?.length
|
||||
? `${obj.firstName} ${obj.lastName}`
|
||||
: obj.firstName
|
||||
} else if (obj.type !== 'sticker') {
|
||||
title = obj.title
|
||||
}
|
||||
|
@ -972,7 +988,11 @@ export namespace BotInline {
|
|||
description = obj.address
|
||||
} else if (obj.type === 'contact') {
|
||||
description = obj.phone
|
||||
} else if (obj.type !== 'gif' && obj.type !== 'voice' && obj.type !== 'sticker') {
|
||||
} else if (
|
||||
obj.type !== 'gif' &&
|
||||
obj.type !== 'voice' &&
|
||||
obj.type !== 'sticker'
|
||||
) {
|
||||
description = obj.description
|
||||
}
|
||||
|
||||
|
|
|
@ -302,10 +302,10 @@ export namespace BotKeyboard {
|
|||
text,
|
||||
url,
|
||||
bot: params.bot ?? {
|
||||
_: 'inputUserSelf'
|
||||
_: 'inputUserSelf',
|
||||
},
|
||||
fwdText: params.fwdText,
|
||||
requestWriteAccess: params.requestWriteAccess
|
||||
requestWriteAccess: params.requestWriteAccess,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -29,7 +29,10 @@ export class FileLocation {
|
|||
| tl.TypeInputFileLocation
|
||||
| tl.TypeInputWebFileLocation
|
||||
| Buffer
|
||||
| (() => tl.TypeInputFileLocation | tl.TypeInputWebFileLocation | Buffer)
|
||||
| (() =>
|
||||
| tl.TypeInputFileLocation
|
||||
| tl.TypeInputWebFileLocation
|
||||
| Buffer)
|
||||
|
||||
/**
|
||||
* File size in bytes, when available
|
||||
|
@ -47,7 +50,10 @@ export class FileLocation {
|
|||
| tl.TypeInputFileLocation
|
||||
| tl.TypeInputWebFileLocation
|
||||
| Buffer
|
||||
| (() => tl.TypeInputFileLocation | tl.TypeInputWebFileLocation | Buffer),
|
||||
| (() =>
|
||||
| tl.TypeInputFileLocation
|
||||
| tl.TypeInputWebFileLocation
|
||||
| Buffer),
|
||||
fileSize?: number,
|
||||
dcId?: number
|
||||
) {
|
||||
|
|
|
@ -25,7 +25,7 @@ export interface UploadedFile {
|
|||
}
|
||||
|
||||
/** @internal */
|
||||
export function isUploadedFile(obj: any): obj is UploadedFile {
|
||||
export function isUploadedFile(obj: object): obj is UploadedFile {
|
||||
return (
|
||||
obj &&
|
||||
typeof obj === 'object' &&
|
||||
|
|
|
@ -52,7 +52,11 @@ export interface FileDownloadParameters {
|
|||
* File location which should be downloaded.
|
||||
* You can also provide TDLib and Bot API compatible File ID
|
||||
*/
|
||||
location: tl.TypeInputFileLocation | tl.TypeInputWebFileLocation | FileLocation | string
|
||||
location:
|
||||
| tl.TypeInputFileLocation
|
||||
| tl.TypeInputWebFileLocation
|
||||
| FileLocation
|
||||
| string
|
||||
|
||||
/**
|
||||
* Total file size, if known.
|
||||
|
|
|
@ -171,7 +171,7 @@ export class Dice {
|
|||
get inputMedia(): tl.TypeInputMedia {
|
||||
return {
|
||||
_: 'inputMediaDice',
|
||||
emoticon: this.obj.emoticon
|
||||
emoticon: this.obj.emoticon,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,7 +3,6 @@ import { tl } from '@mtcute/tl'
|
|||
import { Thumbnail } from './thumbnail'
|
||||
import { TelegramClient } from '../../client'
|
||||
import { makeInspectable } from '../utils'
|
||||
import { InputMediaLike } from './input-media'
|
||||
import { tdFileId as td, toFileId, toUniqueFileId } from '@mtcute/file-id'
|
||||
|
||||
/**
|
||||
|
@ -105,7 +104,7 @@ export class RawDocument extends FileLocation {
|
|||
_: 'inputDocument',
|
||||
id: this.doc.id,
|
||||
accessHash: this.doc.accessHash,
|
||||
fileReference: this.doc.fileReference
|
||||
fileReference: this.doc.fileReference,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -117,7 +116,7 @@ export class RawDocument extends FileLocation {
|
|||
get inputMedia(): tl.TypeInputMedia {
|
||||
return {
|
||||
_: 'inputMediaDocument',
|
||||
id: this.inputDocument
|
||||
id: this.inputDocument,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -91,8 +91,8 @@ export class Game {
|
|||
id: {
|
||||
_: 'inputGameID',
|
||||
id: this.game.id,
|
||||
accessHash: this.game.accessHash
|
||||
}
|
||||
accessHash: this.game.accessHash,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,6 @@ import { InputFileLike } from '../files'
|
|||
import { tl } from '@mtcute/tl'
|
||||
import { Venue } from './venue'
|
||||
import { MaybeArray } from '@mtcute/core'
|
||||
import { InputInlineResultGame } from '../bots'
|
||||
|
||||
interface BaseInputMedia {
|
||||
/**
|
||||
|
@ -728,7 +727,10 @@ export namespace InputMedia {
|
|||
export function geoLive(
|
||||
latitude: number,
|
||||
longitude: number,
|
||||
params: OmitTypeAndFile<InputMediaGeoLive, 'latitude' | 'longitude'> = {}
|
||||
params: OmitTypeAndFile<
|
||||
InputMediaGeoLive,
|
||||
'latitude' | 'longitude'
|
||||
> = {}
|
||||
): InputMediaGeoLive {
|
||||
const ret = params as tl.Mutable<InputMediaGeoLive>
|
||||
ret.type = 'geo_live'
|
||||
|
|
|
@ -11,7 +11,7 @@ export class Invoice {
|
|||
readonly client: TelegramClient
|
||||
readonly raw: tl.RawMessageMediaInvoice
|
||||
|
||||
constructor (client: TelegramClient, raw: tl.RawMessageMediaInvoice) {
|
||||
constructor(client: TelegramClient, raw: tl.RawMessageMediaInvoice) {
|
||||
this.client = client
|
||||
this.raw = raw
|
||||
}
|
||||
|
|
|
@ -40,42 +40,44 @@ export class Location {
|
|||
* Create {@link FileLocation} containing
|
||||
* server-generated image with the map preview
|
||||
*/
|
||||
preview(params: {
|
||||
/**
|
||||
* Map width in pixels before applying scale (16-1024)
|
||||
*
|
||||
* Defaults to `128`
|
||||
*/
|
||||
width?: number
|
||||
preview(
|
||||
params: {
|
||||
/**
|
||||
* Map width in pixels before applying scale (16-1024)
|
||||
*
|
||||
* Defaults to `128`
|
||||
*/
|
||||
width?: number
|
||||
|
||||
/**
|
||||
* Map height in pixels before applying scale (16-1024)
|
||||
*
|
||||
* Defaults to `128`
|
||||
*/
|
||||
height?: number
|
||||
/**
|
||||
* Map height in pixels before applying scale (16-1024)
|
||||
*
|
||||
* Defaults to `128`
|
||||
*/
|
||||
height?: number
|
||||
|
||||
/**
|
||||
* Map zoom level (13-20)
|
||||
*
|
||||
* Defaults to `15`
|
||||
*/
|
||||
zoom?: number
|
||||
/**
|
||||
* Map zoom level (13-20)
|
||||
*
|
||||
* Defaults to `15`
|
||||
*/
|
||||
zoom?: number
|
||||
|
||||
/**
|
||||
* Map scale (1-3)
|
||||
*
|
||||
* Defaults to `1`
|
||||
*/
|
||||
scale?: number
|
||||
} = {}): FileLocation {
|
||||
/**
|
||||
* Map scale (1-3)
|
||||
*
|
||||
* Defaults to `1`
|
||||
*/
|
||||
scale?: number
|
||||
} = {}
|
||||
): FileLocation {
|
||||
return new FileLocation(this.client, {
|
||||
_: 'inputWebFileGeoPointLocation',
|
||||
geoPoint: {
|
||||
_: 'inputGeoPoint',
|
||||
lat: this.geo.lat,
|
||||
long: this.geo.long,
|
||||
accuracyRadius: this.geo.accuracyRadius
|
||||
accuracyRadius: this.geo.accuracyRadius,
|
||||
},
|
||||
accessHash: this.geo.accessHash,
|
||||
w: params.width ?? 128,
|
||||
|
@ -97,8 +99,8 @@ export class Location {
|
|||
_: 'inputGeoPoint',
|
||||
lat: this.geo.lat,
|
||||
long: this.geo.long,
|
||||
accuracyRadius: this.geo.accuracyRadius
|
||||
}
|
||||
accuracyRadius: this.geo.accuracyRadius,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -141,11 +143,11 @@ export class LiveLocation extends Location {
|
|||
_: 'inputGeoPoint',
|
||||
lat: this.geo.lat,
|
||||
long: this.geo.long,
|
||||
accuracyRadius: this.geo.accuracyRadius
|
||||
accuracyRadius: this.geo.accuracyRadius,
|
||||
},
|
||||
heading: this.live.heading,
|
||||
period: this.live.period,
|
||||
proximityNotificationRadius: this.live.proximityNotificationRadius
|
||||
proximityNotificationRadius: this.live.proximityNotificationRadius,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -4,7 +4,6 @@ import { TelegramClient } from '../../client'
|
|||
import { MtCuteArgumentError } from '../errors'
|
||||
import { Thumbnail } from './thumbnail'
|
||||
import { makeInspectable } from '../utils'
|
||||
import { InputMediaLike } from './input-media'
|
||||
|
||||
/**
|
||||
* A photo
|
||||
|
|
|
@ -86,7 +86,7 @@ export class Poll {
|
|||
data: ans.option,
|
||||
voters: res.voters,
|
||||
chosen: !!res.chosen,
|
||||
correct: !!res.correct
|
||||
correct: !!res.correct,
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
|
@ -94,7 +94,7 @@ export class Poll {
|
|||
data: ans.option,
|
||||
voters: 0,
|
||||
chosen: false,
|
||||
correct: false
|
||||
correct: false,
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -210,7 +210,7 @@ export class Poll {
|
|||
question: this.raw.question,
|
||||
answers: this.raw.answers,
|
||||
closePeriod: this.raw.closePeriod,
|
||||
closeDate: this.raw.closeDate
|
||||
closeDate: this.raw.closeDate,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -125,7 +125,7 @@ export class Sticker extends RawDocument {
|
|||
point: MASK_POS[raw.n],
|
||||
x: raw.x,
|
||||
y: raw.y,
|
||||
scale: raw.zoom
|
||||
scale: raw.zoom,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -3,7 +3,6 @@ import { Location } from './location'
|
|||
import { assertTypeIs } from '../../utils/type-assertion'
|
||||
import { makeInspectable } from '../utils'
|
||||
import { TelegramClient } from '../../client'
|
||||
import bigInt from 'big-integer'
|
||||
|
||||
export namespace Venue {
|
||||
export interface VenueSource {
|
||||
|
@ -33,7 +32,7 @@ export class Venue {
|
|||
readonly client: TelegramClient
|
||||
readonly raw: tl.RawMessageMediaVenue
|
||||
|
||||
constructor (client: TelegramClient, raw: tl.RawMessageMediaVenue) {
|
||||
constructor(client: TelegramClient, raw: tl.RawMessageMediaVenue) {
|
||||
this.client = client
|
||||
this.raw = raw
|
||||
}
|
||||
|
@ -106,7 +105,7 @@ export class Venue {
|
|||
address: this.raw.address,
|
||||
provider: this.raw.provider,
|
||||
venueId: this.raw.venueId,
|
||||
venueType: this.raw.venueType
|
||||
venueType: this.raw.venueType,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
import { tl } from '@mtcute/tl'
|
||||
import { Photo } from '../media'
|
||||
import { _callDiscardReasonFromTl, CallDiscardReason } from '../calls/discard-reason'
|
||||
import {
|
||||
_callDiscardReasonFromTl,
|
||||
CallDiscardReason,
|
||||
} from '../calls/discard-reason'
|
||||
import { Message } from './message'
|
||||
|
||||
export namespace MessageAction {
|
||||
|
@ -279,7 +282,10 @@ export type MessageAction =
|
|||
| null
|
||||
|
||||
/** @internal */
|
||||
export function _messageActionFromTl(this: Message, act: tl.TypeMessageAction): MessageAction {
|
||||
export function _messageActionFromTl(
|
||||
this: Message,
|
||||
act: tl.TypeMessageAction
|
||||
): MessageAction {
|
||||
switch (act._) {
|
||||
case 'messageActionChatCreate':
|
||||
return {
|
||||
|
@ -329,10 +335,7 @@ export function _messageActionFromTl(this: Message, act: tl.TypeMessageAction):
|
|||
case 'messageActionChatEditPhoto':
|
||||
return {
|
||||
type: 'photo_changed',
|
||||
photo: new Photo(
|
||||
this.client,
|
||||
act.photo as tl.RawPhoto
|
||||
),
|
||||
photo: new Photo(this.client, act.photo as tl.RawPhoto),
|
||||
}
|
||||
case 'messageActionChatDeletePhoto':
|
||||
return {
|
||||
|
@ -390,12 +393,12 @@ export function _messageActionFromTl(this: Message, act: tl.TypeMessageAction):
|
|||
}
|
||||
case 'messageActionScreenshotTaken':
|
||||
return {
|
||||
type: 'screenshot_taken'
|
||||
type: 'screenshot_taken',
|
||||
}
|
||||
case 'messageActionBotAllowed':
|
||||
return {
|
||||
type: 'bot_allowed',
|
||||
domain: act.domain
|
||||
domain: act.domain,
|
||||
}
|
||||
case 'messageActionGeoProximityReached':
|
||||
if (act.fromId._ !== 'peerUser' || act.toId._ !== 'peerUser') {
|
||||
|
@ -405,7 +408,7 @@ export function _messageActionFromTl(this: Message, act: tl.TypeMessageAction):
|
|||
type: 'geo_proximity',
|
||||
targetId: act.toId.userId,
|
||||
userId: act.fromId.userId,
|
||||
distance: act.distance
|
||||
distance: act.distance,
|
||||
}
|
||||
}
|
||||
case 'messageActionGroupCall':
|
||||
|
@ -413,24 +416,24 @@ export function _messageActionFromTl(this: Message, act: tl.TypeMessageAction):
|
|||
return {
|
||||
type: 'group_call_ended',
|
||||
call: act.call,
|
||||
duration: act.duration
|
||||
duration: act.duration,
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
type: 'group_call_started',
|
||||
call: act.call
|
||||
call: act.call,
|
||||
}
|
||||
}
|
||||
case 'messageActionInviteToGroupCall':
|
||||
return {
|
||||
type: 'group_call_invite',
|
||||
call: act.call,
|
||||
userIds: act.users
|
||||
userIds: act.users,
|
||||
}
|
||||
case 'messageActionSetMessagesTTL':
|
||||
return {
|
||||
type: 'set_ttl',
|
||||
period: act.period
|
||||
period: act.period,
|
||||
}
|
||||
default:
|
||||
return null
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import { tl } from '@mtcute/tl'
|
||||
import { User } from '../peers/user'
|
||||
import { makeInspectable } from '../utils'
|
||||
|
||||
const entityToType: Partial<
|
||||
|
|
|
@ -40,7 +40,10 @@ export type MessageMedia =
|
|||
// todo: successful_payment, connected_website
|
||||
|
||||
/** @internal */
|
||||
export function _messageMediaFromTl(this: Message, m: tl.TypeMessageMedia): MessageMedia {
|
||||
export function _messageMediaFromTl(
|
||||
this: Message,
|
||||
m: tl.TypeMessageMedia
|
||||
): MessageMedia {
|
||||
switch (m._) {
|
||||
case 'messageMediaPhoto':
|
||||
if (!(m.photo?._ === 'photo')) return null
|
||||
|
|
|
@ -33,16 +33,22 @@ export const SearchFilters = {
|
|||
Empty: { _: 'inputMessagesFilterEmpty' } as tl.TypeMessagesFilter,
|
||||
Photo: { _: 'inputMessagesFilterPhotos' } as tl.TypeMessagesFilter,
|
||||
Video: { _: 'inputMessagesFilterVideo' } as tl.TypeMessagesFilter,
|
||||
PhotoAndVideo: { _: 'inputMessagesFilterPhotoVideo' } as tl.TypeMessagesFilter,
|
||||
PhotoAndVideo: {
|
||||
_: 'inputMessagesFilterPhotoVideo',
|
||||
} as tl.TypeMessagesFilter,
|
||||
Document: { _: 'inputMessagesFilterDocument' } as tl.TypeMessagesFilter,
|
||||
Url: { _: 'inputMessagesFilterUrl' } as tl.TypeMessagesFilter,
|
||||
Gif: { _: 'inputMessagesFilterGif' } as tl.TypeMessagesFilter,
|
||||
Voice: { _: 'inputMessagesFilterVoice' } as tl.TypeMessagesFilter,
|
||||
Audio: { _: 'inputMessagesFilterMusic' } as tl.TypeMessagesFilter,
|
||||
ChatPhotoChange: { _: 'inputMessagesFilterChatPhotos' } as tl.TypeMessagesFilter,
|
||||
ChatPhotoChange: {
|
||||
_: 'inputMessagesFilterChatPhotos',
|
||||
} as tl.TypeMessagesFilter,
|
||||
Call: { _: 'inputMessagesFilterPhoneCalls' } as tl.TypeMessagesFilter,
|
||||
Round: { _: 'inputMessagesFilterRoundVideo' } as tl.TypeMessagesFilter,
|
||||
RoundAndVoice: { _: 'inputMessagesFilterRoundVoice' } as tl.TypeMessagesFilter,
|
||||
RoundAndVoice: {
|
||||
_: 'inputMessagesFilterRoundVoice',
|
||||
} as tl.TypeMessagesFilter,
|
||||
MyMention: { _: 'inputMessagesFilterMyMentions' } as tl.TypeMessagesFilter,
|
||||
Location: { _: 'inputMessagesFilterGeo' } as tl.TypeMessagesFilter,
|
||||
Contact: { _: 'inputMessagesFilterContacts' } as tl.TypeMessagesFilter,
|
||||
|
|
|
@ -107,7 +107,7 @@ export class StickerSet {
|
|||
return {
|
||||
_: 'inputStickerSetID',
|
||||
id: this.brief.id,
|
||||
accessHash: this.brief.accessHash
|
||||
accessHash: this.brief.accessHash,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -156,7 +156,7 @@ export class StickerSet {
|
|||
const info: tl.Mutable<StickerSet.StickerInfo> = {
|
||||
alt: sticker.emoji,
|
||||
emoji: '', // populated later
|
||||
sticker
|
||||
sticker,
|
||||
}
|
||||
this._stickers!.push(info)
|
||||
index[doc.id.toString()] = info
|
||||
|
@ -183,7 +183,9 @@ export class StickerSet {
|
|||
* In case this object does not contain info about stickers (i.e. {@link isFull} = false)
|
||||
*/
|
||||
getStickersByEmoji(emoji: string): StickerSet.StickerInfo[] {
|
||||
return this.stickers.filter(it => it.alt === emoji || it.emoji.indexOf(emoji) != -1)
|
||||
return this.stickers.filter(
|
||||
(it) => it.alt === emoji || it.emoji.indexOf(emoji) != -1
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -217,13 +219,14 @@ export class StickerSet {
|
|||
if (idx < 0) idx = this.full!.documents.length + idx
|
||||
const doc = this.full!.documents[idx] as tl.RawDocument
|
||||
|
||||
if (!doc) throw new RangeError(`Sticker set does not have sticker ${idx}`)
|
||||
if (!doc)
|
||||
throw new RangeError(`Sticker set does not have sticker ${idx}`)
|
||||
|
||||
return {
|
||||
_: 'inputDocument',
|
||||
id: doc.id,
|
||||
accessHash: doc.accessHash,
|
||||
fileReference: doc.fileReference
|
||||
fileReference: doc.fileReference,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -240,7 +243,9 @@ export class StickerSet {
|
|||
* Sticker File ID. In case this is a full sticker set object,
|
||||
* you can also pass index (even negative), and that sticker will be removed
|
||||
*/
|
||||
async deleteSticker(sticker: number | Parameters<TelegramClient['deleteStickerFromSet']>[0]): Promise<StickerSet> {
|
||||
async deleteSticker(
|
||||
sticker: number | Parameters<TelegramClient['deleteStickerFromSet']>[0]
|
||||
): Promise<StickerSet> {
|
||||
if (typeof sticker === 'number') {
|
||||
sticker = this._getInputDocument(sticker)
|
||||
}
|
||||
|
@ -262,7 +267,10 @@ export class StickerSet {
|
|||
* you can also pass index (even negative), and that sticker will be removed
|
||||
* @param position New sticker position
|
||||
*/
|
||||
async moveSticker(sticker: number | Parameters<TelegramClient['moveStickerInSet']>[0], position: number): Promise<StickerSet> {
|
||||
async moveSticker(
|
||||
sticker: number | Parameters<TelegramClient['moveStickerInSet']>[0],
|
||||
position: number
|
||||
): Promise<StickerSet> {
|
||||
if (typeof sticker === 'number') {
|
||||
sticker = this._getInputDocument(sticker)
|
||||
}
|
||||
|
@ -284,7 +292,9 @@ export class StickerSet {
|
|||
* you can also pass index (even negative), and that sticker
|
||||
* will be used as a thumb
|
||||
*/
|
||||
async setThumb(thumb: number | Parameters<TelegramClient['setStickerSetThumb']>[1]): Promise<StickerSet> {
|
||||
async setThumb(
|
||||
thumb: number | Parameters<TelegramClient['setStickerSetThumb']>[1]
|
||||
): Promise<StickerSet> {
|
||||
if (typeof thumb === 'number') {
|
||||
thumb = this._getInputDocument(thumb)
|
||||
}
|
||||
|
|
|
@ -351,35 +351,55 @@ function _actionFromTl(
|
|||
return {
|
||||
type: 'photo_changed',
|
||||
old: new Photo(this.client, e.prevPhoto as tl.RawPhoto),
|
||||
new: new Photo(this.client, e.newPhoto as tl.RawPhoto)
|
||||
new: new Photo(this.client, e.newPhoto as tl.RawPhoto),
|
||||
}
|
||||
case 'channelAdminLogEventActionToggleInvites':
|
||||
return {
|
||||
type: 'invites_toggled',
|
||||
old: !e.newValue,
|
||||
new: e.newValue
|
||||
new: e.newValue,
|
||||
}
|
||||
case 'channelAdminLogEventActionToggleSignatures':
|
||||
return {
|
||||
type: 'signatures_toggled',
|
||||
old: !e.newValue,
|
||||
new: e.newValue
|
||||
new: e.newValue,
|
||||
}
|
||||
case 'channelAdminLogEventActionUpdatePinned':
|
||||
return {
|
||||
type: 'msg_pinned',
|
||||
message: new Message(this.client, e.message, this._users, this._chats)
|
||||
message: new Message(
|
||||
this.client,
|
||||
e.message,
|
||||
this._users,
|
||||
this._chats
|
||||
),
|
||||
}
|
||||
case 'channelAdminLogEventActionEditMessage':
|
||||
return {
|
||||
type: 'msg_edited',
|
||||
old: new Message(this.client, e.prevMessage, this._users, this._chats),
|
||||
new: new Message(this.client, e.newMessage, this._users, this._chats)
|
||||
old: new Message(
|
||||
this.client,
|
||||
e.prevMessage,
|
||||
this._users,
|
||||
this._chats
|
||||
),
|
||||
new: new Message(
|
||||
this.client,
|
||||
e.newMessage,
|
||||
this._users,
|
||||
this._chats
|
||||
),
|
||||
}
|
||||
case 'channelAdminLogEventActionDeleteMessage':
|
||||
return {
|
||||
type: 'msg_deleted',
|
||||
message: new Message(this.client, e.message, this._users, this._chats)
|
||||
message: new Message(
|
||||
this.client,
|
||||
e.message,
|
||||
this._users,
|
||||
this._chats
|
||||
),
|
||||
}
|
||||
case 'channelAdminLogEventActionParticipantLeave':
|
||||
return { type: 'user_left' }
|
||||
|
@ -391,65 +411,84 @@ function _actionFromTl(
|
|||
case 'channelAdminLogEventActionParticipantToggleBan':
|
||||
return {
|
||||
type: 'user_perms_changed',
|
||||
old: new ChatMember(this.client, e.prevParticipant, this._users),
|
||||
new: new ChatMember(this.client, e.newParticipant, this._users)
|
||||
old: new ChatMember(
|
||||
this.client,
|
||||
e.prevParticipant,
|
||||
this._users
|
||||
),
|
||||
new: new ChatMember(this.client, e.newParticipant, this._users),
|
||||
}
|
||||
case 'channelAdminLogEventActionParticipantToggleAdmin':
|
||||
return {
|
||||
type: 'user_admin_perms_changed',
|
||||
old: new ChatMember(this.client, e.prevParticipant, this._users),
|
||||
new: new ChatMember(this.client, e.newParticipant, this._users)
|
||||
old: new ChatMember(
|
||||
this.client,
|
||||
e.prevParticipant,
|
||||
this._users
|
||||
),
|
||||
new: new ChatMember(this.client, e.newParticipant, this._users),
|
||||
}
|
||||
case 'channelAdminLogEventActionChangeStickerSet':
|
||||
return {
|
||||
type: 'stickerset_changed',
|
||||
old: e.prevStickerset,
|
||||
new: e.newStickerset
|
||||
new: e.newStickerset,
|
||||
}
|
||||
case 'channelAdminLogEventActionTogglePreHistoryHidden':
|
||||
return {
|
||||
type: 'history_toggled',
|
||||
old: !e.newValue,
|
||||
new: e.newValue
|
||||
new: e.newValue,
|
||||
}
|
||||
case 'channelAdminLogEventActionDefaultBannedRights':
|
||||
return {
|
||||
type: 'def_perms_changed',
|
||||
old: new ChatPermissions(e.prevBannedRights),
|
||||
new: new ChatPermissions(e.newBannedRights)
|
||||
new: new ChatPermissions(e.newBannedRights),
|
||||
}
|
||||
case 'channelAdminLogEventActionStopPoll':
|
||||
return {
|
||||
type: 'poll_stopped',
|
||||
message: new Message(this.client, e.message, this._users, this._chats)
|
||||
message: new Message(
|
||||
this.client,
|
||||
e.message,
|
||||
this._users,
|
||||
this._chats
|
||||
),
|
||||
}
|
||||
case 'channelAdminLogEventActionChangeLinkedChat':
|
||||
return {
|
||||
type: 'linked_chat_changed',
|
||||
old: e.prevValue,
|
||||
new: e.newValue
|
||||
new: e.newValue,
|
||||
}
|
||||
case 'channelAdminLogEventActionChangeLocation':
|
||||
return {
|
||||
type: 'location_changed',
|
||||
old: e.prevValue._ === 'channelLocationEmpty' ? null : new ChatLocation(this.client, e.prevValue),
|
||||
new: e.newValue._ === 'channelLocationEmpty' ? null : new ChatLocation(this.client, e.newValue),
|
||||
old:
|
||||
e.prevValue._ === 'channelLocationEmpty'
|
||||
? null
|
||||
: new ChatLocation(this.client, e.prevValue),
|
||||
new:
|
||||
e.newValue._ === 'channelLocationEmpty'
|
||||
? null
|
||||
: new ChatLocation(this.client, e.newValue),
|
||||
}
|
||||
case 'channelAdminLogEventActionToggleSlowMode':
|
||||
return {
|
||||
type: 'slow_mode_changed',
|
||||
old: e.prevValue,
|
||||
new: e.newValue
|
||||
new: e.newValue,
|
||||
}
|
||||
case 'channelAdminLogEventActionStartGroupCall':
|
||||
return {
|
||||
type: 'call_started',
|
||||
call: e.call
|
||||
call: e.call,
|
||||
}
|
||||
case 'channelAdminLogEventActionDiscardGroupCall':
|
||||
return {
|
||||
type: 'call_ended',
|
||||
call: e.call
|
||||
call: e.call,
|
||||
}
|
||||
case 'channelAdminLogEventActionParticipantMute':
|
||||
case 'channelAdminLogEventActionParticipantUnmute':
|
||||
|
@ -459,34 +498,34 @@ function _actionFromTl(
|
|||
case 'channelAdminLogEventActionToggleGroupCallSetting':
|
||||
return {
|
||||
type: 'call_setting_changed',
|
||||
joinMuted: e.joinMuted
|
||||
joinMuted: e.joinMuted,
|
||||
}
|
||||
case 'channelAdminLogEventActionParticipantJoinByInvite':
|
||||
return {
|
||||
type: 'user_joined_invite',
|
||||
link: new ChatInviteLink(this.client, e.invite, this._users)
|
||||
link: new ChatInviteLink(this.client, e.invite, this._users),
|
||||
}
|
||||
case 'channelAdminLogEventActionExportedInviteDelete':
|
||||
return {
|
||||
type: 'invite_deleted',
|
||||
link: new ChatInviteLink(this.client, e.invite, this._users)
|
||||
link: new ChatInviteLink(this.client, e.invite, this._users),
|
||||
}
|
||||
case 'channelAdminLogEventActionExportedInviteRevoke':
|
||||
return {
|
||||
type: 'invite_revoked',
|
||||
link: new ChatInviteLink(this.client, e.invite, this._users)
|
||||
link: new ChatInviteLink(this.client, e.invite, this._users),
|
||||
}
|
||||
case 'channelAdminLogEventActionExportedInviteEdit':
|
||||
return {
|
||||
type: 'invite_edited',
|
||||
old: new ChatInviteLink(this.client, e.prevInvite, this._users),
|
||||
new: new ChatInviteLink(this.client, e.newInvite, this._users)
|
||||
new: new ChatInviteLink(this.client, e.newInvite, this._users),
|
||||
}
|
||||
case 'channelAdminLogEventActionChangeHistoryTTL':
|
||||
return {
|
||||
type: 'ttl_changed',
|
||||
old: e.prevValue,
|
||||
new: e.newValue
|
||||
new: e.newValue,
|
||||
}
|
||||
default:
|
||||
return null
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue