mirror of
https://github.com/discordjs/discord.js.git
synced 2026-03-16 03:23:29 +01:00
refactor(rest): add content-type(s) to uploads (#8290)
This commit is contained in:
@@ -255,8 +255,8 @@ class MessagePayload {
|
||||
name = fileLike.name ?? findName(attachment);
|
||||
}
|
||||
|
||||
const data = await DataResolver.resolveFile(attachment);
|
||||
return { data, name };
|
||||
const { data, contentType } = await DataResolver.resolveFile(attachment);
|
||||
return { data, name, contentType };
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -100,32 +100,37 @@ class DataResolver extends null {
|
||||
* @see {@link https://nodejs.org/api/stream.html}
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} ResolvedFile
|
||||
* @property {Buffer} data Buffer containing the file data
|
||||
* @property {string} [contentType] Content type of the file
|
||||
*/
|
||||
|
||||
/**
|
||||
* Resolves a BufferResolvable to a Buffer.
|
||||
* @param {BufferResolvable|Stream} resource The buffer or stream resolvable to resolve
|
||||
* @returns {Promise<Buffer>}
|
||||
* @returns {Promise<ResolvedFile>}
|
||||
*/
|
||||
static async resolveFile(resource) {
|
||||
if (!resource) return null;
|
||||
if (Buffer.isBuffer(resource)) return resource;
|
||||
if (Buffer.isBuffer(resource)) return { data: resource };
|
||||
|
||||
if (typeof resource[Symbol.asyncIterator] === 'function') {
|
||||
const buffers = [];
|
||||
for await (const data of resource) buffers.push(data);
|
||||
return Buffer.concat(buffers);
|
||||
return { data: Buffer.concat(buffers) };
|
||||
}
|
||||
|
||||
if (typeof resource === 'string') {
|
||||
if (/^https?:\/\//.test(resource)) {
|
||||
const res = await fetch(resource);
|
||||
return Buffer.from(await res.arrayBuffer());
|
||||
return { data: Buffer.from(await res.arrayBuffer()), contentType: res.headers.get('content-type') };
|
||||
}
|
||||
|
||||
const file = path.resolve(resource);
|
||||
|
||||
const stats = await fs.stat(file);
|
||||
if (!stats.isFile()) throw new DiscordError(ErrorCodes.FileNotFound, file);
|
||||
return fs.readFile(file);
|
||||
return { data: await fs.readFile(file) };
|
||||
}
|
||||
|
||||
throw new TypeError(ErrorCodes.ReqResourceType);
|
||||
|
||||
7
packages/discord.js/typings/index.d.ts
vendored
7
packages/discord.js/typings/index.d.ts
vendored
@@ -1039,11 +1039,16 @@ export class ContextMenuCommandInteraction<Cached extends CacheType = CacheType>
|
||||
private resolveContextMenuOptions(data: APIApplicationCommandInteractionData): CommandInteractionOption<Cached>[];
|
||||
}
|
||||
|
||||
export interface ResolvedFile {
|
||||
data: Buffer;
|
||||
contentType?: string;
|
||||
}
|
||||
|
||||
export class DataResolver extends null {
|
||||
private constructor();
|
||||
public static resolveBase64(data: Base64Resolvable): string;
|
||||
public static resolveCode(data: string, regex: RegExp): string;
|
||||
public static resolveFile(resource: BufferResolvable | Stream): Promise<Buffer>;
|
||||
public static resolveFile(resource: BufferResolvable | Stream): Promise<ResolvedFile>;
|
||||
public static resolveImage(resource: BufferResolvable | Base64Resolvable): Promise<string | null>;
|
||||
public static resolveInviteCode(data: InviteResolvable): string;
|
||||
public static resolveGuildTemplateCode(data: GuildTemplateResolvable): string;
|
||||
|
||||
@@ -55,6 +55,7 @@
|
||||
"@sapphire/async-queue": "^1.3.2",
|
||||
"@sapphire/snowflake": "^3.2.2",
|
||||
"discord-api-types": "^0.36.1",
|
||||
"file-type": "^17.1.2",
|
||||
"tslib": "^2.4.0",
|
||||
"undici": "^5.6.0"
|
||||
},
|
||||
|
||||
@@ -9,6 +9,12 @@ import { SequentialHandler } from './handlers/SequentialHandler';
|
||||
import { DefaultRestOptions, DefaultUserAgent, RESTEvents } from './utils/constants';
|
||||
import { resolveBody } from './utils/utils';
|
||||
|
||||
// Make this a lazy dynamic import as file-type is a pure ESM package
|
||||
const getFileType = (): Promise<typeof import('file-type')> => {
|
||||
let cached: Promise<typeof import('file-type')>;
|
||||
return (cached ??= import('file-type'));
|
||||
};
|
||||
|
||||
/**
|
||||
* Represents a file to be added to the request
|
||||
*/
|
||||
@@ -27,6 +33,10 @@ export interface RawFile {
|
||||
* The actual data for the file
|
||||
*/
|
||||
data: string | number | boolean | Buffer;
|
||||
/**
|
||||
* Content-Type of the file
|
||||
*/
|
||||
contentType?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -401,11 +411,14 @@ export class RequestManager extends EventEmitter {
|
||||
// FormData.append only accepts a string or Blob.
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/Blob/Blob#parameters
|
||||
// The Blob constructor accepts TypedArray/ArrayBuffer, strings, and Blobs.
|
||||
if (Buffer.isBuffer(file.data) || typeof file.data === 'string') {
|
||||
formData.append(fileKey, new Blob([file.data]), file.name);
|
||||
if (Buffer.isBuffer(file.data)) {
|
||||
// Try to infer the content type from the buffer if one isn't passed
|
||||
const { fileTypeFromBuffer } = await getFileType();
|
||||
const contentType = file.contentType ?? (await fileTypeFromBuffer(file.data))?.mime;
|
||||
formData.append(fileKey, new Blob([file.data], { type: contentType }), file.name);
|
||||
} else {
|
||||
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
||||
formData.append(fileKey, new Blob([`${file.data}`]), file.name);
|
||||
formData.append(fileKey, new Blob([`${file.data}`], { type: file.contentType }), file.name);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user