From 079d65b38de73e4381064b2210139b13c963e8c6 Mon Sep 17 00:00:00 2001 From: teidesu Date: Thu, 10 Jun 2021 02:31:48 +0300 Subject: [PATCH] build: preparing for publish, day 6 i am slowly descending to madness bugs fixed, stuff exported, and maybe something else --- package.json | 1 + packages/client/src/client.ts | 15 ++--- packages/client/src/index.ts | 1 + packages/client/src/methods/_imports.ts | 1 + .../src/methods/chats/get-chat-members.ts | 13 +++- .../client/src/methods/files/upload-file.ts | 39 ++++++++++++ .../src/methods/messages/get-history.ts | 4 +- .../src/methods/messages/get-messages.ts | 4 +- .../src/methods/messages/search-global.ts | 6 +- .../src/methods/messages/search-messages.ts | 6 +- .../src/methods/messages/send-media-group.ts | 17 +++++- packages/client/src/types/files/utils.ts | 8 +++ packages/client/src/types/index.ts | 2 +- packages/client/src/types/messages/message.ts | 59 ++----------------- packages/client/src/types/utils.ts | 2 + packages/client/src/utils/peer-utils.ts | 2 + packages/client/src/utils/stream-utils.ts | 12 +++- packages/client/src/utils/updates-utils.ts | 8 ++- packages/file-id/src/convert.ts | 19 ++++-- yarn.lock | 21 ++++++- 20 files changed, 152 insertions(+), 88 deletions(-) diff --git a/package.json b/package.json index dca80d36..d05cab7c 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/packages/client/src/client.ts b/packages/client/src/client.ts index 56763ff9..6a92d563 100644 --- a/packages/client/src/client.ts +++ b/packages/client/src/client.ts @@ -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 + ): Promise> /** * 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 @@ -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 /** @@ -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 /** @@ -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. diff --git a/packages/client/src/index.ts b/packages/client/src/index.ts index 5bc0a2a3..3821566d 100644 --- a/packages/client/src/index.ts +++ b/packages/client/src/index.ts @@ -10,3 +10,4 @@ export * from './parser' export * from './types' export * from './client' export * from './utils/peer-utils' +export { createDummyUpdate } from './utils/updates-utils' diff --git a/packages/client/src/methods/_imports.ts b/packages/client/src/methods/_imports.ts index c47dfcaa..38276a75 100644 --- a/packages/client/src/methods/_imports.ts +++ b/packages/client/src/methods/_imports.ts @@ -38,6 +38,7 @@ import { UsersIndex, ChatsIndex, GameHighScore, + ArrayWithTotal, } from '../types' // @copy diff --git a/packages/client/src/methods/chats/get-chat-members.ts b/packages/client/src/methods/chats/get-chat-members.ts index 91dd4301..89269574 100644 --- a/packages/client/src/methods/chats/get-chat-members.ts +++ b/packages/client/src/methods/chats/get-chat-members.ts @@ -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 { +): Promise> { 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 + + 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 + ret.total = res.count + return ret } throw new MtCuteInvalidPeerTypeError(chatId, 'chat or channel') diff --git a/packages/client/src/methods/files/upload-file.ts b/packages/client/src/methods/files/upload-file.ts index ffa2d795..6c35f02a 100644 --- a/packages/client/src/methods/files/upload-file.ts +++ b/packages/client/src/methods/files/upload-file.ts @@ -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 diff --git a/packages/client/src/methods/messages/get-history.ts b/packages/client/src/methods/messages/get-history.ts index 234c2f45..377cccd3 100644 --- a/packages/client/src/methods/messages/get-history.ts +++ b/packages/client/src/methods/messages/get-history.ts @@ -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() diff --git a/packages/client/src/methods/messages/get-messages.ts b/packages/client/src/methods/messages/get-messages.ts index fcf8f3ef..2b08a699 100644 --- a/packages/client/src/methods/messages/get-messages.ts +++ b/packages/client/src/methods/messages/get-messages.ts @@ -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 } diff --git a/packages/client/src/methods/messages/search-global.ts b/packages/client/src/methods/messages/search-global.ts index fafc4a51..17746dd3 100644 --- a/packages/client/src/methods/messages/search-global.ts +++ b/packages/client/src/methods/messages/search-global.ts @@ -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 diff --git a/packages/client/src/methods/messages/search-messages.ts b/packages/client/src/methods/messages/search-messages.ts index 04dc140e..1eac7fc6 100644 --- a/packages/client/src/methods/messages/search-messages.ts +++ b/packages/client/src/methods/messages/search-messages.ts @@ -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 diff --git a/packages/client/src/methods/messages/send-media-group.ts b/packages/client/src/methods/messages/send-media-group.ts index 1b56ee4d..9896cd0d 100644 --- a/packages/client/src/methods/messages/send-media-group.ts +++ b/packages/client/src/methods/messages/send-media-group.ts @@ -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), }) diff --git a/packages/client/src/types/files/utils.ts b/packages/client/src/types/files/utils.ts index 7d66680f..163f599f 100644 --- a/packages/client/src/types/files/utils.ts +++ b/packages/client/src/types/files/utils.ts @@ -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 | NodeJS.ReadableStream | null + } /** * Describes types that can be used as an input diff --git a/packages/client/src/types/index.ts b/packages/client/src/types/index.ts index 727ae5b4..cd84527f 100644 --- a/packages/client/src/types/index.ts +++ b/packages/client/src/types/index.ts @@ -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' diff --git a/packages/client/src/types/messages/message.ts b/packages/client/src/types/messages/message.ts index 08360a16..5bc3811e 100644 --- a/packages/client/src/types/messages/message.ts +++ b/packages/client/src/types/messages/message.ts @@ -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 - 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 { - if (this._emptyError) throw this._emptyError - if (!this._entities) { this._entities = [] if (this.raw._ === 'message' && this.raw.entities?.length) { diff --git a/packages/client/src/types/utils.ts b/packages/client/src/types/utils.ts index 5996472d..72402296 100644 --- a/packages/client/src/types/utils.ts +++ b/packages/client/src/types/utils.ts @@ -2,6 +2,8 @@ import { MaybeAsync } from '@mtcute/core' export type MaybeDynamic = MaybeAsync | (() => MaybeAsync) +export type ArrayWithTotal = T[] & { total: number } + let util: any | null = null try { util = require('util') diff --git a/packages/client/src/utils/peer-utils.ts b/packages/client/src/utils/peer-utils.ts index 0ff2bf9e..56410ce3 100644 --- a/packages/client/src/utils/peer-utils.ts +++ b/packages/client/src/utils/peer-utils.ts @@ -62,6 +62,8 @@ export function normalizeToInputUser( if (tl.isAnyInputUser(res)) return res switch (res._) { + case 'inputPeerSelf': + return { _: 'inputUserSelf' } case 'inputPeerUser': return { _: 'inputUser', diff --git a/packages/client/src/utils/stream-utils.ts b/packages/client/src/utils/stream-utils.ts index df18b084..59ee83ac 100644 --- a/packages/client/src/utils/stream-utils.ts +++ b/packages/client/src/utils/stream-utils.ts @@ -72,8 +72,18 @@ export function convertWebStreamToNodeReadable( export async function readStreamUntilEnd(stream: Readable): Promise { 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) diff --git a/packages/client/src/utils/updates-utils.ts b/packages/client/src/utils/updates-utils.ts index c1727c4d..e462cb05 100644 --- a/packages/client/src/utils/updates-utils.ts +++ b/packages/client/src/utils/updates-utils.ts @@ -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, diff --git a/packages/file-id/src/convert.ts b/packages/file-id/src/convert.ts index b03518c1..b05fe1e4 100644 --- a/packages/file-id/src/convert.ts +++ b/packages/file-id/src/convert.ts @@ -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) - throw new td.InvalidFileIdError( - 'Expected document to have file reference' - ) + 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, } diff --git a/yarn.lock b/yarn.lock index 81a0de78..681a91a0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -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"