build: preparing for publish, day 6

i am slowly descending to madness

bugs fixed, stuff exported, and maybe something else
This commit is contained in:
teidesu 2021-06-10 02:31:48 +03:00
parent c46f113f1f
commit 079d65b38d
20 changed files with 152 additions and 88 deletions

View file

@ -21,6 +21,7 @@
"@types/mocha": "^8.2.0",
"@types/node": "^14.14.22",
"@types/node-forge": "^0.9.7",
"@types/node-fetch": "^2.5.10",
"@types/pako": "^1.0.1",
"@types/ws": "^7.4.0",
"@typescript-eslint/eslint-plugin": "^4.15.0",

View file

@ -155,6 +155,7 @@ import { updateUsername } from './methods/users/update-username'
import { IMessageEntityParser } from './parser'
import { Readable } from 'stream'
import {
ArrayWithTotal,
Chat,
ChatEvent,
ChatInviteLink,
@ -523,7 +524,6 @@ export interface TelegramClient extends BaseTelegramClient {
* @param queryId Inline query ID
* @param results Results of the query
* @param params Additional parameters
*/
answerInlineQuery(
queryId: tl.Long,
@ -984,7 +984,7 @@ export interface TelegramClient extends BaseTelegramClient {
| 'contacts'
| 'mention'
}
): Promise<ChatMember[]>
): Promise<ArrayWithTotal<ChatMember>>
/**
* Get preview information about a private chat.
*
@ -1286,7 +1286,6 @@ export interface TelegramClient extends BaseTelegramClient {
*
* @param folder Parameters for the folder
* @returns Newly created folder
*/
createFolder(
folder: PartialExcept<tl.RawDialogFilter, 'title'>
@ -1443,7 +1442,6 @@ 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,7 +1450,6 @@ 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,
@ -1464,7 +1461,6 @@ export interface TelegramClient extends BaseTelegramClient {
* consecutive.
*
* @param params Download parameters
*/
downloadAsIterable(
params: FileDownloadParameters
@ -1474,7 +1470,6 @@ export interface TelegramClient extends BaseTelegramClient {
* streaming file contents.
*
* @param params File download parameters
*/
downloadAsStream(params: FileDownloadParameters): Readable
/**
@ -1983,7 +1978,6 @@ export interface TelegramClient extends BaseTelegramClient {
*
* @param chatId Chat ID
* @param message ID of one of the messages in the group
*/
getMessageGroup(chatId: InputPeerLike, message: number): Promise<Message[]>
/**
@ -2275,6 +2269,9 @@ export interface TelegramClient extends BaseTelegramClient {
/**
* Send a group of media.
*
* To add a caption to the group, add caption to the first
* media in the group and don't add caption for any other.
*
* @param chatId ID of the chat, its username, phone or `"me"` or `"self"`
* @param medias Medias contained in the message.
* @param params Additional sending parameters
@ -2282,7 +2279,7 @@ export interface TelegramClient extends BaseTelegramClient {
*/
sendMediaGroup(
chatId: InputPeerLike,
medias: InputMediaLike[],
medias: (InputMediaLike | string)[],
params?: {
/**
* Message to reply to. Either a message object or message ID.

View file

@ -10,3 +10,4 @@ export * from './parser'
export * from './types'
export * from './client'
export * from './utils/peer-utils'
export { createDummyUpdate } from './utils/updates-utils'

View file

@ -38,6 +38,7 @@ import {
UsersIndex,
ChatsIndex,
GameHighScore,
ArrayWithTotal,
} from '../types'
// @copy

View file

@ -12,6 +12,7 @@ import {
} from '../../utils/peer-utils'
import { assertTypeIs } from '../../utils/type-assertion'
import { tl } from '@mtcute/tl'
import { ArrayWithTotal } from '../../types'
/**
* Get a chunk of members of some chat.
@ -68,7 +69,7 @@ export async function getChatMembers(
| 'contacts'
| 'mention'
}
): Promise<ChatMember[]> {
): Promise<ArrayWithTotal<ChatMember>> {
if (!params) params = {}
const chat = await this.resolvePeer(chatId)
@ -95,7 +96,10 @@ export async function getChatMembers(
const { users } = createUsersChatsIndex(res)
return members.map((m) => new ChatMember(this, m, users))
const ret = members.map((m) => new ChatMember(this, m, users)) as ArrayWithTotal<ChatMember>
ret.total = ret.length
return ret
}
if (isInputPeerChannel(chat)) {
@ -146,7 +150,10 @@ export async function getChatMembers(
)
const { users } = createUsersChatsIndex(res)
return res.participants.map((i) => new ChatMember(this, i, users))
const ret = res.participants.map((i) => new ChatMember(this, i, users)) as ArrayWithTotal<ChatMember>
ret.total = res.count
return ret
}
throw new MtCuteInvalidPeerTypeError(chatId, 'chat or channel')

View file

@ -136,6 +136,45 @@ export async function uploadFile(
file = convertWebStreamToNodeReadable(file)
}
if (typeof file === 'object' && 'headers' in file && 'body' in file && 'url' in file) {
// fetch() response
const length = parseInt(file.headers.get('content-length') || '0')
if (!isNaN(length) && length) fileSize = length
fileMime = file.headers.get('content-type')?.split(';')[0]
const disposition = file.headers.get('content-disposition')
if (disposition) {
const idx = disposition.indexOf('filename=')
if (idx > -1) {
const raw = disposition.slice(idx + 9).split(';')[0]
fileName = JSON.parse(raw)
}
}
if (fileName === 'unnamed') {
// try to infer from url
const url = new URL(file.url)
const name = url.pathname.split('/').pop()
if (name && name.indexOf('.') > -1) {
fileName = name
}
}
if (!file.body)
throw new MtCuteArgumentError('Fetch response contains `null` body')
if (
typeof ReadableStream !== 'undefined' &&
file.body instanceof ReadableStream
) {
file = convertWebStreamToNodeReadable(file.body)
} else {
file = file.body
}
}
// override file name and mime (if any)
if (params.fileName) fileName = params.fileName

View file

@ -82,7 +82,9 @@ export async function getHistory(
const { users, chats } = createUsersChatsIndex(res)
const msgs = res.messages.map((msg) => new Message(this, msg, users, chats))
const msgs = res.messages
.filter((msg) => msg._ !== 'messageEmpty')
.map((msg) => new Message(this, msg, users, chats))
if (params.reverse) msgs.reverse()

View file

@ -85,7 +85,9 @@ export async function getMessages(
const { users, chats } = createUsersChatsIndex(res)
const ret = res.messages.map((msg) => new Message(this, msg, users, chats))
const ret = res.messages
.filter((msg) => msg._ !== 'messageEmpty')
.map((msg) => new Message(this, msg, users, chats))
return isSingle ? ret[0] : ret
}

View file

@ -79,9 +79,9 @@ export async function* searchGlobal(
const { users, chats } = createUsersChatsIndex(res)
const msgs = res.messages.map(
(msg) => new Message(this, msg, users, chats)
)
const msgs = res.messages
.filter((msg) => msg._ !== 'messageEmpty')
.map((msg) => new Message(this, msg, users, chats))
if (!msgs.length) break

View file

@ -100,9 +100,9 @@ export async function* searchMessages(
const { users, chats } = createUsersChatsIndex(res)
const msgs = res.messages.map(
(msg) => new Message(this, msg, users, chats)
)
const msgs = res.messages
.filter((msg) => msg._ !== 'messageEmpty')
.map((msg) => new Message(this, msg, users, chats))
if (!msgs.length) break

View file

@ -1,6 +1,6 @@
import { TelegramClient } from '../../client'
import {
BotKeyboard,
BotKeyboard, InputFileLike,
InputMediaLike,
InputPeerLike,
Message,
@ -18,6 +18,9 @@ import { createUsersChatsIndex } from '../../utils/peer-utils'
/**
* Send a group of media.
*
* To add a caption to the group, add caption to the first
* media in the group and don't add caption for any other.
*
* @param chatId ID of the chat, its username, phone or `"me"` or `"self"`
* @param medias Medias contained in the message.
* @param params Additional sending parameters
@ -27,7 +30,7 @@ import { createUsersChatsIndex } from '../../utils/peer-utils'
export async function sendMediaGroup(
this: TelegramClient,
chatId: InputPeerLike,
medias: InputMediaLike[],
medias: (InputMediaLike | string)[],
params?: {
/**
* Message to reply to. Either a message object or message ID.
@ -108,7 +111,15 @@ export async function sendMediaGroup(
const multiMedia: tl.RawInputSingleMedia[] = []
for (let i = 0; i < medias.length; i++) {
const media = medias[i]
let media = medias[i]
if (typeof media === 'string') {
media = {
type: 'auto',
file: media,
}
}
const inputMedia = await this._normalizeInputMedia(media, {
progressCallback: params.progressCallback?.bind(null, i),
})

View file

@ -14,6 +14,7 @@ import { tdFileId } from '@mtcute/file-id'
* - `ReadStream` (for NodeJS, from the `fs` module)
* - `ReadableStream` (from the Web API, base readable stream)
* - `Readable` (for NodeJS, base readable stream)
* - `Response` (from `window.fetch` or `node-fetch`)
*/
export type UploadFileLike =
| Buffer
@ -21,7 +22,14 @@ export type UploadFileLike =
| string
| ReadStream
| ReadableStream
| NodeJS.ReadableStream
| Readable
// fetch() response
| {
headers: any
url: string
body: ReadableStream<Uint8Array> | NodeJS.ReadableStream | null
}
/**
* Describes types that can be used as an input

View file

@ -7,5 +7,5 @@ export * from './peers'
export * from './misc'
export * from './errors'
export { MaybeDynamic } from './utils'
export { MaybeDynamic, ArrayWithTotal } from './utils'
export { MaybeAsync, PartialExcept, PartialOnly } from '@mtcute/core'

View file

@ -101,16 +101,6 @@ export class Message {
/**
* Raw TL object.
*
* > **Note**: In fact, `raw` can also be {@link tl.RawMessageEmpty}.
* > But since it is quite rare, for the simplicity sake
* > we don't bother thinking about it (and you shouldn't too).
* >
* > When the {@link Message} is in fact `messageEmpty`,
* > `.empty` will be true and trying to access properties
* > that are not available will result in {@link MtCuteEmptyError}.
* >
* > The only property that is available on an "empty" message is `.id`
*/
readonly raw: tl.RawMessage | tl.RawMessageService
@ -119,8 +109,6 @@ export class Message {
/** Map of chats in this message. Mainly for internal use */
readonly _chats: ChatsIndex
private _emptyError?: MtCuteEmptyError
constructor(
client: TelegramClient,
raw: tl.TypeMessage,
@ -128,33 +116,18 @@ export class Message {
chats: ChatsIndex,
isScheduled = false
) {
if (raw._ === 'messageEmpty')
throw new MtCuteTypeAssertionError('Message#ctor', 'not messageEmpty', 'messageEmpty')
this.client = client
this._users = users
this._chats = chats
// a bit of cheating in terms of types but whatever :shrug:
//
// using exclude instead of `typeof this.raw` because
// TypeMessage might have some other types added, and we'll detect
// that at compile time
this.raw = raw as Exclude<tl.TypeMessage, tl.RawMessageEmpty>
this.empty = raw._ === 'messageEmpty'
this.raw = raw
if (this.empty) {
this._emptyError = new MtCuteEmptyError()
}
this.isScheduled = isScheduled
}
/**
* Whether the message is empty.
*
* Note that if the message is empty,
* accessing any other property except `id` and `raw`
* will result in {@link MtCuteEmptyError}
*/
readonly empty: boolean
/**
* Whether the message is scheduled.
* If it is, then its {@link date} is set to future.
@ -172,8 +145,6 @@ export class Message {
* `null` for service messages and non-post messages.
*/
get views(): number | null {
if (this._emptyError) throw this._emptyError
return this.raw._ === 'message' ? this.raw.views ?? null : null
}
@ -184,8 +155,6 @@ export class Message {
* - Messages to yourself (i.e. *Saved Messages*) are incoming (`outgoing = false`)
*/
get isOutgoing(): boolean {
if (this._emptyError) throw this._emptyError
return this.raw.out!
}
@ -203,8 +172,6 @@ export class Message {
* `null` for service messages and non-grouped messages
*/
get groupedId(): tl.Long | null {
if (this._emptyError) throw this._emptyError
return this.raw._ === 'message' ? this.raw.groupedId ?? null : null
}
@ -224,8 +191,6 @@ export class Message {
* sender is the channel itself.
*/
get sender(): User | Chat {
if (this._emptyError) throw this._emptyError
if (this._sender === undefined) {
const from = this.raw.fromId
if (!from) {
@ -270,8 +235,6 @@ export class Message {
* Conversation the message belongs to
*/
get chat(): Chat {
if (this._emptyError) throw this._emptyError
if (this._chat === undefined) {
this._chat = Chat._parseFromMessage(
this.client,
@ -288,8 +251,6 @@ export class Message {
* Date the message was sent
*/
get date(): Date {
if (this._emptyError) throw this._emptyError
return new Date(this.raw.date * 1000)
}
@ -299,8 +260,6 @@ export class Message {
* If this message is a forward, contains info about it.
*/
get forward(): Message.MessageForwardInfo | null {
if (this._emptyError) throw this._emptyError
if (!this._forward) {
if (this.raw._ !== 'message' || !this.raw.fwdFrom) {
this._forward = null
@ -386,8 +345,6 @@ export class Message {
* replies to.
*/
get replyToMessageId(): number | null {
if (this._emptyError) throw this._emptyError
return this.raw.replyTo?.replyToMsgId ?? null
}
@ -395,8 +352,6 @@ export class Message {
* Whether this message contains mention of the current user
*/
get isMention(): boolean {
if (this._emptyError) throw this._emptyError
return this.raw.mentioned!
}
@ -406,8 +361,6 @@ export class Message {
* information about the bot which generated it
*/
get viaBot(): User | null {
if (this._emptyError) throw this._emptyError
if (this._viaBot === undefined) {
if (this.raw._ === 'messageService' || !this.raw.viaBotId) {
this._viaBot = null
@ -429,8 +382,6 @@ export class Message {
* (you should handle i18n yourself)
*/
get text(): string {
if (this._emptyError) throw this._emptyError
return this.raw._ === 'messageService' ? '' : this.raw.message
}
@ -439,8 +390,6 @@ export class Message {
* Message text/caption entities (may be empty)
*/
get entities(): ReadonlyArray<MessageEntity> {
if (this._emptyError) throw this._emptyError
if (!this._entities) {
this._entities = []
if (this.raw._ === 'message' && this.raw.entities?.length) {

View file

@ -2,6 +2,8 @@ import { MaybeAsync } from '@mtcute/core'
export type MaybeDynamic<T> = MaybeAsync<T> | (() => MaybeAsync<T>)
export type ArrayWithTotal<T> = T[] & { total: number }
let util: any | null = null
try {
util = require('util')

View file

@ -62,6 +62,8 @@ export function normalizeToInputUser(
if (tl.isAnyInputUser(res)) return res
switch (res._) {
case 'inputPeerSelf':
return { _: 'inputUserSelf' }
case 'inputPeerUser':
return {
_: 'inputUser',

View file

@ -72,8 +72,18 @@ export function convertWebStreamToNodeReadable(
export async function readStreamUntilEnd(stream: Readable): Promise<Buffer> {
const chunks = []
let length = 0
while (stream.readable) {
chunks.push(await stream.read())
const c = await stream.read()
if (c === null) break
length += c.length
if (length > 2097152000) {
throw new Error('File is too big')
}
chunks.push(c)
}
return Buffer.concat(chunks)

View file

@ -4,7 +4,13 @@ import { MtCuteTypeAssertionError } from '../types'
// dummy updates which are used for methods that return messages.affectedHistory.
// that is not an update, but it carries info about pts, and we need to handle it
/** @internal */
/**
* Create a dummy update from PTS and PTS count.
*
* @param pts PTS
* @param ptsCount PTS count
* @param channelId Channel ID (bare), if applicable
*/
export function createDummyUpdate(
pts: number,
ptsCount: number,

View file

@ -4,6 +4,8 @@ import { parseFileId } from './parse'
import { getBasicPeerType, markedPeerIdToBare } from '@mtcute/core'
import FileType = tdFileId.FileType
const EMPTY_BUFFER = Buffer.alloc(0)
type FileId = td.RawFullRemoteFileLocation
function dialogPhotoToInputPeer(
@ -182,14 +184,21 @@ export function fileIdToInputDocument(
)
throw new td.ConversionError('inputDocument')
if (!fileId.fileReference)
let fileRef = fileId.fileReference
if (!fileId.fileReference) {
if (fileId.type === FileType.Sticker) {
// older stickers' file IDs don't have file ref
fileRef = EMPTY_BUFFER
} else {
throw new td.InvalidFileIdError(
'Expected document to have file reference'
)
}
}
return {
_: 'inputDocument',
fileReference: fileId.fileReference,
fileReference: fileRef!,
id: fileId.location.id,
accessHash: fileId.location.accessHash,
}

View file

@ -1145,6 +1145,14 @@
resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-8.2.0.tgz#3eb56d13a1de1d347ecb1957c6860c911704bc44"
integrity sha512-/Sge3BymXo4lKc31C8OINJgXLaw+7vL1/L1pGiBNpGrBiT8FQiaFpSYV0uhTaG4y78vcMBTMFsWaHDvuD+xGzQ==
"@types/node-fetch@^2.5.10":
version "2.5.10"
resolved "http://localhost:4873/@types%2fnode-fetch/-/node-fetch-2.5.10.tgz#9b4d4a0425562f9fcea70b12cb3fcdd946ca8132"
integrity sha512-IpkX0AasN44hgEad0gEF/V6EgR5n69VEqPEgnmoM8GsIGro3PowbWs4tR6IhxUTyPLpOn+fiGG6nrQhcmoCuIQ==
dependencies:
"@types/node" "*"
form-data "^3.0.0"
"@types/node-forge@^0.9.7":
version "0.9.7"
resolved "https://registry.yarnpkg.com/@types/node-forge/-/node-forge-0.9.7.tgz#948f7b52d352a6c4ab22d3328c206f0870672db5"
@ -1953,9 +1961,9 @@ columnify@^1.5.4:
strip-ansi "^3.0.0"
wcwidth "^1.0.0"
combined-stream@^1.0.6, combined-stream@~1.0.6:
combined-stream@^1.0.6, combined-stream@^1.0.8, combined-stream@~1.0.6:
version "1.0.8"
resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f"
resolved "http://localhost:4873/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f"
integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==
dependencies:
delayed-stream "~1.0.0"
@ -2869,6 +2877,15 @@ forever-agent@~0.6.1:
resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91"
integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=
form-data@^3.0.0:
version "3.0.1"
resolved "http://localhost:4873/form-data/-/form-data-3.0.1.tgz#ebd53791b78356a99af9a300d4282c4d5eb9755f"
integrity sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==
dependencies:
asynckit "^0.4.0"
combined-stream "^1.0.8"
mime-types "^2.1.12"
form-data@~2.3.2:
version "2.3.3"
resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6"