Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(DataResolver): prefer streams over buffers #4075

Merged
merged 6 commits into from
Apr 17, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@
"unpkg": "./webpack/discord.min.js",
"dependencies": {
"@discordjs/collection": "^0.1.5",
"@discordjs/form-data": "^3.0.1",
"abort-controller": "^3.0.0",
"form-data": "^3.0.0",
"node-fetch": "^2.6.0",
"prism-media": "^1.2.0",
"setimmediate": "^1.0.5",
Expand Down
2 changes: 1 addition & 1 deletion src/rest/APIRequest.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
'use strict';

const https = require('https');
const FormData = require('@discordjs/form-data');
const AbortController = require('abort-controller');
const FormData = require('form-data');
const fetch = require('node-fetch');
const { browser, UserAgent } = require('../util/Constants');

Expand Down
48 changes: 27 additions & 21 deletions src/util/DataResolver.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

const fs = require('fs');
const path = require('path');
const stream = require('stream');
const fetch = require('node-fetch');
const { Error: DiscordError, TypeError } = require('../errors');
const { browser } = require('../util/Constants');
Expand Down Expand Up @@ -45,7 +46,7 @@ class DataResolver {
if (typeof image === 'string' && image.startsWith('data:')) {
return image;
}
const file = await this.resolveFile(image);
const file = await this.resolveFileAsBuffer(image);
return DataResolver.resolveBase64(file);
}

Expand Down Expand Up @@ -80,41 +81,46 @@ class DataResolver {
*/

/**
* Resolves a BufferResolvable to a Buffer.
* Resolves a BufferResolvable to a Buffer or a Stream.
* @param {BufferResolvable|Stream} resource The buffer or stream resolvable to resolve
* @returns {Promise<Buffer>}
* @returns {Promise<Buffer|Stream>}
*/
static resolveFile(resource) {
if (!browser && Buffer.isBuffer(resource)) return Promise.resolve(resource);
if (browser && resource instanceof ArrayBuffer) return Promise.resolve(Util.convertToBuffer(resource));
static async resolveFile(resource) {
if (!browser && Buffer.isBuffer(resource)) return resource;
if (browser && resource instanceof ArrayBuffer) return Util.convertToBuffer(resource);
if (resource instanceof stream.Readable) return resource;

if (typeof resource === 'string') {
if (/^https?:\/\//.test(resource)) {
return fetch(resource).then(res => (browser ? res.blob() : res.buffer()));
const res = await fetch(resource);
return browser ? res.blob() : res.body;
} else if (!browser) {
return new Promise((resolve, reject) => {
const file = browser ? resource : path.resolve(resource);
const file = path.resolve(resource);
fs.stat(file, (err, stats) => {
if (err) return reject(err);
if (!stats.isFile()) return reject(new DiscordError('FILE_NOT_FOUND', file));
fs.readFile(file, (err2, data) => {
if (err2) reject(err2);
else resolve(data);
});
return null;
return resolve(fs.createReadStream(file));
});
});
}
} else if (typeof resource.pipe === 'function') {
return new Promise((resolve, reject) => {
const buffers = [];
resource.once('error', reject);
resource.on('data', data => buffers.push(data));
resource.once('end', () => resolve(Buffer.concat(buffers)));
});
}

return Promise.reject(new TypeError('REQ_RESOURCE_TYPE'));
throw new TypeError('REQ_RESOURCE_TYPE');
}

/**
* Resolves a BufferResolvable to a Buffer.
* @param {BufferResolvable|Stream} resource The buffer or stream resolvable to resolve
* @returns {Promise<Buffer>}
*/
static async resolveFileAsBuffer(resource) {
const file = await this.resolveFile(resource);
if (Buffer.isBuffer(file)) return file;

const buffers = [];
for await (const data of file) buffers.push(data);
return Buffer.concat(buffers);
}
}

Expand Down
7 changes: 4 additions & 3 deletions typings/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ declare enum ChannelType {

declare module 'discord.js' {
import BaseCollection from '@discordjs/collection';
import { EventEmitter } from 'events';
import { Stream, Readable, Writable } from 'stream';
import { ChildProcess } from 'child_process';
import { EventEmitter } from 'events';
import { PathLike } from 'fs';
import { Readable, Stream, Writable } from 'stream';
import * as WebSocket from 'ws';

export const version: string;
Expand Down Expand Up @@ -537,7 +537,8 @@ declare module 'discord.js' {

export class DataResolver {
public static resolveBase64(data: Base64Resolvable): string;
public static resolveFile(resource: BufferResolvable | Stream): Promise<Buffer>;
public static resolveFile(resource: BufferResolvable | Stream): Promise<Buffer | Stream>;
public static resolveFileAsBuffer(resource: BufferResolvable | Stream): Promise<Buffer>;
public static resolveImage(resource: BufferResolvable | Base64Resolvable): Promise<string>;
public static resolveInviteCode(data: InviteResolvable): string;
}
Expand Down