feat: Using new attachment API

This commit is contained in:
March 7th 2022-11-27 19:45:58 +07:00
parent d97cf71661
commit c70dc733e2
8 changed files with 105 additions and 5 deletions

View File

@ -1036,6 +1036,9 @@ class Client extends BaseClient {
if (options && options.password && typeof options.password !== 'string') { if (options && options.password && typeof options.password !== 'string') {
throw new TypeError('CLIENT_INVALID_OPTION', 'password', 'a string'); throw new TypeError('CLIENT_INVALID_OPTION', 'password', 'a string');
} }
if (options && options.usingNewAttachmentAPI && typeof options.usingNewAttachmentAPI !== 'boolean') {
throw new TypeError('CLIENT_INVALID_OPTION', 'usingNewAttachmentAPI', 'a boolean');
}
if (options && options.interactionTimeout && typeof options.interactionTimeout !== 'number') { if (options && options.interactionTimeout && typeof options.interactionTimeout !== 'number') {
throw new TypeError('CLIENT_INVALID_OPTION', 'interactionTimeout', 'a number'); throw new TypeError('CLIENT_INVALID_OPTION', 'interactionTimeout', 'a number');
} }

View File

@ -3,7 +3,7 @@
const ThreadManager = require('./ThreadManager'); const ThreadManager = require('./ThreadManager');
const { TypeError } = require('../errors'); const { TypeError } = require('../errors');
const MessagePayload = require('../structures/MessagePayload'); const MessagePayload = require('../structures/MessagePayload');
const { resolveAutoArchiveMaxLimit } = require('../util/Util'); const { resolveAutoArchiveMaxLimit, getAttachments, uploadFile } = require('../util/Util');
/** /**
* Manages API methods for threads in forum channels and stores their cache. * Manages API methods for threads in forum channels and stores their cache.
@ -67,7 +67,27 @@ class GuildForumThreadManager extends ThreadManager {
messagePayload = MessagePayload.create(this, message).resolveData(); messagePayload = MessagePayload.create(this, message).resolveData();
} }
const { data: body, files } = await messagePayload.resolveFiles(); let { data: body, files } = await messagePayload.resolveFiles();
if (typeof message == 'object' && typeof message.usingNewAttachmentAPI !== 'boolean') {
message.usingNewAttachmentAPI = this.client.options.usingNewAttachmentAPI;
}
if (message?.usingNewAttachmentAPI === true) {
const attachments = await getAttachments(this.client, this.channel.id, ...files);
const requestPromises = attachments.map(async attachment => {
await uploadFile(files[attachment.id].file, attachment.upload_url);
return {
id: attachment.id,
filename: files[attachment.id].name,
uploaded_filename: attachment.upload_filename,
};
});
const attachmentsData = await Promise.all(requestPromises);
attachmentsData.sort((a, b) => parseInt(a.id) - parseInt(b.id));
body.attachments = attachmentsData;
files = [];
}
if (autoArchiveDuration === 'MAX') autoArchiveDuration = resolveAutoArchiveMaxLimit(this.channel.guild); if (autoArchiveDuration === 'MAX') autoArchiveDuration = resolveAutoArchiveMaxLimit(this.channel.guild);

View File

@ -130,7 +130,28 @@ class MessageManager extends CachedManager {
} else { } else {
messagePayload = await MessagePayload.create(message instanceof Message ? message : this, options).resolveData(); messagePayload = await MessagePayload.create(message instanceof Message ? message : this, options).resolveData();
} }
const { data, files } = await messagePayload.resolveFiles(); let { data, files } = await messagePayload.resolveFiles();
if (typeof options == 'object' && typeof options.usingNewAttachmentAPI !== 'boolean') {
options.usingNewAttachmentAPI = this.client.options.usingNewAttachmentAPI;
}
if (options?.usingNewAttachmentAPI === true) {
const attachments = await Util.getAttachments(this.client, this.channel.id, ...files);
const requestPromises = attachments.map(async attachment => {
await Util.uploadFile(files[attachment.id].file, attachment.upload_url);
return {
id: attachment.id,
filename: files[attachment.id].name,
uploaded_filename: attachment.upload_filename,
};
});
const attachmentsData = await Promise.all(requestPromises);
attachmentsData.sort((a, b) => parseInt(a.id) - parseInt(b.id));
data.attachments = attachmentsData;
files = [];
}
const d = await this.client.api.channels[this.channel.id].messages[messageId].patch({ data, files }); const d = await this.client.api.channels[this.channel.id].messages[messageId].patch({ data, files });
const existing = this.cache.get(messageId); const existing = this.cache.get(messageId);

View File

@ -51,6 +51,15 @@ class MessagePayload {
this.files = null; this.files = null;
} }
/**
* Whether or not using new API to upload files
* @type {boolean}
* @readonly
*/
get usingNewAttachmentAPI() {
return Boolean(this.options?.usingNewAttachmentAPI);
}
/** /**
* Whether or not the target is a {@link Webhook} or a {@link WebhookClient} * Whether or not the target is a {@link Webhook} or a {@link WebhookClient}
* @type {boolean} * @type {boolean}

View File

@ -9,7 +9,7 @@ const { Collection } = require('@discordjs/collection');
const { InteractionTypes, MaxBulkDeletableMessageAge } = require('../../util/Constants'); const { InteractionTypes, MaxBulkDeletableMessageAge } = require('../../util/Constants');
const { TypeError, Error } = require('../../errors'); const { TypeError, Error } = require('../../errors');
const InteractionCollector = require('../InteractionCollector'); const InteractionCollector = require('../InteractionCollector');
const { lazy } = require('../../util/Util'); const { lazy, getAttachments, uploadFile } = require('../../util/Util');
const Message = lazy(() => require('../Message').Message); const Message = lazy(() => require('../Message').Message);
const { s } = require('@sapphire/shapeshift'); const { s } = require('@sapphire/shapeshift');
const validateName = stringName => const validateName = stringName =>
@ -84,6 +84,7 @@ class TextBasedChannel {
* @property {MessageActionRow[]|MessageActionRowOptions[]} [components] * @property {MessageActionRow[]|MessageActionRowOptions[]} [components]
* Action rows containing interactive components for the message (buttons, select menus) * Action rows containing interactive components for the message (buttons, select menus)
* @property {MessageAttachment[]} [attachments] Attachments to send in the message * @property {MessageAttachment[]} [attachments] Attachments to send in the message
* @property {boolean} [usingNewAttachmentAPI] Whether to use the new attachment API (`channels/:id/attachments`)
*/ */
/** /**
@ -190,7 +191,28 @@ class TextBasedChannel {
messagePayload = await MessagePayload.create(this, options).resolveData(); messagePayload = await MessagePayload.create(this, options).resolveData();
} }
const { data, files } = await messagePayload.resolveFiles(); let { data, files } = await messagePayload.resolveFiles();
if (typeof options == 'object' && typeof options.usingNewAttachmentAPI !== 'boolean') {
options.usingNewAttachmentAPI = this.client.options.usingNewAttachmentAPI;
}
if (options?.usingNewAttachmentAPI === true) {
const attachments = await getAttachments(this.client, this.id, ...files);
const requestPromises = attachments.map(async attachment => {
await uploadFile(files[attachment.id].file, attachment.upload_url);
return {
id: attachment.id,
filename: files[attachment.id].name,
uploaded_filename: attachment.upload_filename,
};
});
const attachmentsData = await Promise.all(requestPromises);
attachmentsData.sort((a, b) => parseInt(a.id) - parseInt(b.id));
data.attachments = attachmentsData;
files = [];
}
const d = await this.client.api.channels[this.id].messages.post({ data, files }); const d = await this.client.api.channels[this.id].messages.post({ data, files });
return this.messages.cache.get(d.id) ?? this.messages._add(d); return this.messages.cache.get(d.id) ?? this.messages._add(d);

View File

@ -42,6 +42,7 @@ const { randomUA } = require('../util/Constants');
* @property {string} [captchaService=null] Captcha service to use for solving captcha {@link captchaServices} * @property {string} [captchaService=null] Captcha service to use for solving captcha {@link captchaServices}
* @property {string} [captchaKey=null] Captcha service key * @property {string} [captchaKey=null] Captcha service key
* @property {string} [password=null] Your Discord account password * @property {string} [password=null] Your Discord account password
* @property {boolean} [usingNewAttachmentAPI=true] Use new attachment API
* @property {string} [interactionTimeout=15000] The amount of time in milliseconds to wait for an interaction response, before rejecting * @property {string} [interactionTimeout=15000] The amount of time in milliseconds to wait for an interaction response, before rejecting
* @property {boolean} [autoRedeemNitro=false] Automaticlly redeems nitro codes <NOTE: there is no cooldown on the auto redeem> * @property {boolean} [autoRedeemNitro=false] Automaticlly redeems nitro codes <NOTE: there is no cooldown on the auto redeem>
* @property {string} [proxy] Proxy to use for the WebSocket + REST connection (proxy-agent uri type) {@link https://www.npmjs.com/package/proxy-agent}. * @property {string} [proxy] Proxy to use for the WebSocket + REST connection (proxy-agent uri type) {@link https://www.npmjs.com/package/proxy-agent}.
@ -157,6 +158,7 @@ class Options extends null {
DMSync: false, DMSync: false,
patchVoice: false, patchVoice: false,
password: null, password: null,
usingNewAttachmentAPI: true,
interactionTimeout: 15_000, interactionTimeout: 15_000,
waitGuildTimeout: 15_000, waitGuildTimeout: 15_000,
messageCreateEventGuildTimeout: 100, messageCreateEventGuildTimeout: 100,

View File

@ -3,6 +3,7 @@
const { parse } = require('node:path'); const { parse } = require('node:path');
const process = require('node:process'); const process = require('node:process');
const { Collection } = require('@discordjs/collection'); const { Collection } = require('@discordjs/collection');
const axios = require('axios');
const fetch = require('node-fetch'); const fetch = require('node-fetch');
const { Colors, Endpoints } = require('./Constants'); const { Colors, Endpoints } = require('./Constants');
const Options = require('./Options'); const Options = require('./Options');
@ -728,6 +729,25 @@ class Util extends null {
emoji_name: defaultReaction.name, emoji_name: defaultReaction.name,
}; };
} }
static async getAttachments(client, channelId, ...files) {
files = files.flat(2).map((file, i) => ({
filename: file.name ?? file.attachment?.name ?? file.attachment?.filename ?? 'file.jpg',
// 8MB = 8388608 bytes
file_size: Math.floor((8_388_608 / 10) * Math.random()),
id: `${i}`,
}));
const { attachments } = await client.api.channels[channelId].attachments.post({
data: {
files,
},
});
return attachments;
}
static uploadFile(data, url) {
return axios.put(url, data);
}
} }
module.exports = Util; module.exports = Util;

3
typings/index.d.ts vendored
View File

@ -2220,6 +2220,7 @@ export class MessageMentions {
export class MessagePayload { export class MessagePayload {
public constructor(target: MessageTarget, options: MessageOptions | WebhookMessageOptions); public constructor(target: MessageTarget, options: MessageOptions | WebhookMessageOptions);
public data: RawMessagePayloadData | null; public data: RawMessagePayloadData | null;
public readonly usingNewAttachmentAPI: boolean;
public readonly isUser: boolean; public readonly isUser: boolean;
public readonly isWebhook: boolean; public readonly isWebhook: boolean;
public readonly isMessage: boolean; public readonly isMessage: boolean;
@ -4736,6 +4737,7 @@ export interface ClientOptions {
captchaService?: string; captchaService?: string;
captchaKey?: string; captchaKey?: string;
interactionTimeout?: number; interactionTimeout?: number;
usingNewAttachmentAPI?: boolean;
} }
// end copy // end copy
@ -6265,6 +6267,7 @@ export interface MessageOptions {
stickers?: StickerResolvable[]; stickers?: StickerResolvable[];
attachments?: MessageAttachment[]; attachments?: MessageAttachment[];
flags?: BitFieldResolvable<'SUPPRESS_EMBEDS', number>; flags?: BitFieldResolvable<'SUPPRESS_EMBEDS', number>;
usingNewAttachmentAPI?: boolean;
} }
export type MessageReactionResolvable = export type MessageReactionResolvable =