mirror of
https://github.com/discordeno/discordeno.git
synced 2026-06-04 09:50:07 +00:00
Fix sending files through a rest proxy (#2593)
* Fix rest proxy not working with files * Fix some credits * Add tests * Fix test * Remove some usage of any * Fix mime matching * Fix formatting issues
This commit is contained in:
@@ -1,7 +1,8 @@
|
||||
import { RestManager } from "./restManager.ts";
|
||||
import { FileContent } from "../types/discordeno.ts";
|
||||
import { decode } from "../util/base64.ts";
|
||||
import { USER_AGENT } from "../util/constants.ts";
|
||||
import { RequestMethod } from "./rest.ts";
|
||||
import { RestManager } from "./restManager.ts";
|
||||
|
||||
/** Creates the request body and headers that are necessary to send a request. Will handle different types of methods and everything necessary for discord. */
|
||||
export function createRequestBody(rest: RestManager, options: CreateRequestBodyOptions) {
|
||||
@@ -31,29 +32,19 @@ export function createRequestBody(rest: RestManager, options: CreateRequestBodyO
|
||||
|
||||
// IF A FILE/ATTACHMENT IS PRESENT WE NEED SPECIAL HANDLING
|
||||
if (options.body?.file) {
|
||||
if (!Array.isArray(options.body.file)) {
|
||||
options.body.file = [options.body.file];
|
||||
}
|
||||
const files = findFiles(options.body.file);
|
||||
|
||||
const form = new FormData();
|
||||
|
||||
// WHEN CREATING A STICKER, DISCORD WANTS FORM DATA ONLY
|
||||
if (options.url?.endsWith("/stickers") && options.method === "POST") {
|
||||
form.append(
|
||||
`file`,
|
||||
(options.body.file as FileContent[])[0].blob,
|
||||
(options.body.file as FileContent[])[0].name,
|
||||
);
|
||||
form.append(`file`, files[0].blob, files[0].name);
|
||||
form.append(`name`, options.body.name as string);
|
||||
form.append(`description`, options.body.description as string);
|
||||
form.append(`tags`, options.body.tags as string);
|
||||
} else {
|
||||
for (let i = 0; i < (options.body.file as FileContent[]).length; i++) {
|
||||
form.append(
|
||||
`file${i}`,
|
||||
(options.body.file as FileContent[])[i].blob,
|
||||
(options.body.file as FileContent[])[i].name,
|
||||
);
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
form.append(`file${i}`, files[i].blob, files[i].name);
|
||||
}
|
||||
|
||||
form.append("payload_json", JSON.stringify({ ...options.body, file: undefined }));
|
||||
@@ -78,3 +69,39 @@ export interface CreateRequestBodyOptions {
|
||||
unauthorized?: boolean;
|
||||
url?: string;
|
||||
}
|
||||
|
||||
function findFiles(file: unknown): FileContent[] {
|
||||
if (!file) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const files: unknown[] = Array.isArray(file) ? file : [file];
|
||||
return files.filter(coerceToFileContent);
|
||||
}
|
||||
|
||||
function coerceToFileContent(value: unknown): value is FileContent {
|
||||
if (!value || typeof value !== "object") {
|
||||
return false;
|
||||
}
|
||||
|
||||
const file = value as Record<string, unknown>;
|
||||
if (typeof file.name !== "string") {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (typeof file.blob) {
|
||||
case "string": {
|
||||
const match = file.blob.match(/^data:(?<mimeType>[a-zA-Z0-9\/]*);base64,(?<content>.*)$/);
|
||||
if (match?.groups === undefined) {
|
||||
return false;
|
||||
}
|
||||
const { mimeType, content } = match.groups;
|
||||
file.blob = new Blob([decode(content)], { type: mimeType });
|
||||
return true;
|
||||
}
|
||||
case "object":
|
||||
return file.blob instanceof Blob;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { FileContent } from "../mod.ts";
|
||||
import { API_VERSION, BASE_URL, baseEndpoints } from "../util/constants.ts";
|
||||
import { encode } from "../util/urlToBase64.ts";
|
||||
import { encode } from "../util/base64.ts";
|
||||
import { RequestMethod, RestRequestRejection, RestRequestResponse } from "./rest.ts";
|
||||
import { RestManager } from "./restManager.ts";
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Collection, formatImageURL, hasProperty, iconBigintToHash, iconHashToBigInt, validateLength } from "../mod.ts";
|
||||
import { decode, encode } from "../util/base64.ts";
|
||||
import { bigintToSnowflake, snowflakeToBigint } from "../util/bigint.ts";
|
||||
import { removeTokenPrefix } from "../util/token.ts";
|
||||
import { assertEquals, assertExists, assertNotEquals } from "./deps.ts";
|
||||
@@ -407,3 +408,34 @@ Deno.test({
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
Deno.test({
|
||||
name: "[utils] encode some bytes to base64",
|
||||
ignore: Deno.env.get("TEST_ENV") === "INTEGRATION",
|
||||
async fn(t) {
|
||||
assertEquals(encode(new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])), "AQIDBAUGBwgJCg==");
|
||||
},
|
||||
});
|
||||
|
||||
Deno.test({
|
||||
name: "[utils] decode some base64 to bytes",
|
||||
ignore: Deno.env.get("TEST_ENV") === "INTEGRATION",
|
||||
async fn(t) {
|
||||
assertEquals(decode("AQIDBAUGBwgJCg=="), new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]));
|
||||
},
|
||||
});
|
||||
|
||||
Deno.test({
|
||||
name: "[utils] encode/decode base64 roundtrip should work",
|
||||
ignore: Deno.env.get("TEST_ENV") === "INTEGRATION",
|
||||
async fn(t) {
|
||||
for (let i = 0; i < 10; i++) {
|
||||
const bytes = [];
|
||||
for (let i = 0; i < 10000; i++) {
|
||||
bytes.push(Math.floor(Math.random() * 256));
|
||||
}
|
||||
const data = new Uint8Array(bytes);
|
||||
assertEquals(decode(encode(data)), data);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
274
util/base64.ts
Normal file
274
util/base64.ts
Normal file
@@ -0,0 +1,274 @@
|
||||
/**
|
||||
* CREDIT: https://gist.github.com/enepomnyaschih/72c423f727d395eeaa09697058238727
|
||||
* Encodes a given Uint8Array, ArrayBuffer or string into RFC4648 base64 representation
|
||||
* @param data
|
||||
*/
|
||||
export function encode(data: ArrayBuffer | string): string {
|
||||
const uint8 = typeof data === "string"
|
||||
? new TextEncoder().encode(data)
|
||||
: data instanceof Uint8Array
|
||||
? data
|
||||
: new Uint8Array(data);
|
||||
let result = "",
|
||||
i;
|
||||
const l = uint8.length;
|
||||
for (i = 2; i < l; i += 3) {
|
||||
result += base64abc[uint8[i - 2] >> 2];
|
||||
result += base64abc[((uint8[i - 2] & 0x03) << 4) | (uint8[i - 1] >> 4)];
|
||||
result += base64abc[((uint8[i - 1] & 0x0f) << 2) | (uint8[i] >> 6)];
|
||||
result += base64abc[uint8[i] & 0x3f];
|
||||
}
|
||||
if (i === l + 1) {
|
||||
// 1 octet yet to write
|
||||
result += base64abc[uint8[i - 2] >> 2];
|
||||
result += base64abc[(uint8[i - 2] & 0x03) << 4];
|
||||
result += "==";
|
||||
}
|
||||
if (i === l) {
|
||||
// 2 octets yet to write
|
||||
result += base64abc[uint8[i - 2] >> 2];
|
||||
result += base64abc[((uint8[i - 2] & 0x03) << 4) | (uint8[i - 1] >> 4)];
|
||||
result += base64abc[(uint8[i - 1] & 0x0f) << 2];
|
||||
result += "=";
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* CREDIT: https://gist.github.com/enepomnyaschih/72c423f727d395eeaa09697058238727
|
||||
* Decodes RFC4648 base64 string into an Uint8Array
|
||||
* @param data
|
||||
*/
|
||||
export function decode(data: string): Uint8Array {
|
||||
if (data.length % 4 !== 0) {
|
||||
throw new Error("Unable to parse base64 string.");
|
||||
}
|
||||
const index = data.indexOf("=");
|
||||
if (index !== -1 && index < data.length - 2) {
|
||||
throw new Error("Unable to parse base64 string.");
|
||||
}
|
||||
let missingOctets = data.endsWith("==") ? 2 : data.endsWith("=") ? 1 : 0,
|
||||
n = data.length,
|
||||
result = new Uint8Array(3 * (n / 4)),
|
||||
buffer;
|
||||
for (let i = 0, j = 0; i < n; i += 4, j += 3) {
|
||||
buffer = getBase64Code(data.charCodeAt(i)) << 18 |
|
||||
getBase64Code(data.charCodeAt(i + 1)) << 12 |
|
||||
getBase64Code(data.charCodeAt(i + 2)) << 6 |
|
||||
getBase64Code(data.charCodeAt(i + 3));
|
||||
result[j] = buffer >> 16;
|
||||
result[j + 1] = (buffer >> 8) & 0xFF;
|
||||
result[j + 2] = buffer & 0xFF;
|
||||
}
|
||||
return result.subarray(0, result.length - missingOctets);
|
||||
}
|
||||
|
||||
/**
|
||||
* CREDIT: https://gist.github.com/enepomnyaschih/72c423f727d395eeaa09697058238727
|
||||
* @param charCode
|
||||
*/
|
||||
function getBase64Code(charCode: number): number {
|
||||
if (charCode >= base64codes.length) {
|
||||
throw new Error("Unable to parse base64 string.");
|
||||
}
|
||||
const code = base64codes[charCode];
|
||||
if (code === 255) {
|
||||
throw new Error("Unable to parse base64 string.");
|
||||
}
|
||||
return code;
|
||||
}
|
||||
|
||||
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
|
||||
const base64abc = [
|
||||
"A",
|
||||
"B",
|
||||
"C",
|
||||
"D",
|
||||
"E",
|
||||
"F",
|
||||
"G",
|
||||
"H",
|
||||
"I",
|
||||
"J",
|
||||
"K",
|
||||
"L",
|
||||
"M",
|
||||
"N",
|
||||
"O",
|
||||
"P",
|
||||
"Q",
|
||||
"R",
|
||||
"S",
|
||||
"T",
|
||||
"U",
|
||||
"V",
|
||||
"W",
|
||||
"X",
|
||||
"Y",
|
||||
"Z",
|
||||
"a",
|
||||
"b",
|
||||
"c",
|
||||
"d",
|
||||
"e",
|
||||
"f",
|
||||
"g",
|
||||
"h",
|
||||
"i",
|
||||
"j",
|
||||
"k",
|
||||
"l",
|
||||
"m",
|
||||
"n",
|
||||
"o",
|
||||
"p",
|
||||
"q",
|
||||
"r",
|
||||
"s",
|
||||
"t",
|
||||
"u",
|
||||
"v",
|
||||
"w",
|
||||
"x",
|
||||
"y",
|
||||
"z",
|
||||
"0",
|
||||
"1",
|
||||
"2",
|
||||
"3",
|
||||
"4",
|
||||
"5",
|
||||
"6",
|
||||
"7",
|
||||
"8",
|
||||
"9",
|
||||
"+",
|
||||
"/",
|
||||
];
|
||||
|
||||
// CREDIT: https://gist.github.com/enepomnyaschih/72c423f727d395eeaa09697058238727
|
||||
const base64codes = [
|
||||
255,
|
||||
255,
|
||||
255,
|
||||
255,
|
||||
255,
|
||||
255,
|
||||
255,
|
||||
255,
|
||||
255,
|
||||
255,
|
||||
255,
|
||||
255,
|
||||
255,
|
||||
255,
|
||||
255,
|
||||
255,
|
||||
255,
|
||||
255,
|
||||
255,
|
||||
255,
|
||||
255,
|
||||
255,
|
||||
255,
|
||||
255,
|
||||
255,
|
||||
255,
|
||||
255,
|
||||
255,
|
||||
255,
|
||||
255,
|
||||
255,
|
||||
255,
|
||||
255,
|
||||
255,
|
||||
255,
|
||||
255,
|
||||
255,
|
||||
255,
|
||||
255,
|
||||
255,
|
||||
255,
|
||||
255,
|
||||
255,
|
||||
62,
|
||||
255,
|
||||
255,
|
||||
255,
|
||||
63,
|
||||
52,
|
||||
53,
|
||||
54,
|
||||
55,
|
||||
56,
|
||||
57,
|
||||
58,
|
||||
59,
|
||||
60,
|
||||
61,
|
||||
255,
|
||||
255,
|
||||
255,
|
||||
0,
|
||||
255,
|
||||
255,
|
||||
255,
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
6,
|
||||
7,
|
||||
8,
|
||||
9,
|
||||
10,
|
||||
11,
|
||||
12,
|
||||
13,
|
||||
14,
|
||||
15,
|
||||
16,
|
||||
17,
|
||||
18,
|
||||
19,
|
||||
20,
|
||||
21,
|
||||
22,
|
||||
23,
|
||||
24,
|
||||
25,
|
||||
255,
|
||||
255,
|
||||
255,
|
||||
255,
|
||||
255,
|
||||
255,
|
||||
26,
|
||||
27,
|
||||
28,
|
||||
29,
|
||||
30,
|
||||
31,
|
||||
32,
|
||||
33,
|
||||
34,
|
||||
35,
|
||||
36,
|
||||
37,
|
||||
38,
|
||||
39,
|
||||
40,
|
||||
41,
|
||||
42,
|
||||
43,
|
||||
44,
|
||||
45,
|
||||
46,
|
||||
47,
|
||||
48,
|
||||
49,
|
||||
50,
|
||||
51,
|
||||
];
|
||||
@@ -1,3 +1,5 @@
|
||||
import { encode } from "./base64.ts";
|
||||
|
||||
/** Converts a url to base 64. Useful for example, uploading/creating server emojis. */
|
||||
export async function urlToBase64(url: string) {
|
||||
const buffer = await fetch(url).then((res) => res.arrayBuffer());
|
||||
@@ -5,107 +7,3 @@ export async function urlToBase64(url: string) {
|
||||
const type = url.substring(url.lastIndexOf(".") + 1);
|
||||
return `data:image/${type};base64,${imageStr}`;
|
||||
}
|
||||
|
||||
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
|
||||
const base64abc = [
|
||||
"A",
|
||||
"B",
|
||||
"C",
|
||||
"D",
|
||||
"E",
|
||||
"F",
|
||||
"G",
|
||||
"H",
|
||||
"I",
|
||||
"J",
|
||||
"K",
|
||||
"L",
|
||||
"M",
|
||||
"N",
|
||||
"O",
|
||||
"P",
|
||||
"Q",
|
||||
"R",
|
||||
"S",
|
||||
"T",
|
||||
"U",
|
||||
"V",
|
||||
"W",
|
||||
"X",
|
||||
"Y",
|
||||
"Z",
|
||||
"a",
|
||||
"b",
|
||||
"c",
|
||||
"d",
|
||||
"e",
|
||||
"f",
|
||||
"g",
|
||||
"h",
|
||||
"i",
|
||||
"j",
|
||||
"k",
|
||||
"l",
|
||||
"m",
|
||||
"n",
|
||||
"o",
|
||||
"p",
|
||||
"q",
|
||||
"r",
|
||||
"s",
|
||||
"t",
|
||||
"u",
|
||||
"v",
|
||||
"w",
|
||||
"x",
|
||||
"y",
|
||||
"z",
|
||||
"0",
|
||||
"1",
|
||||
"2",
|
||||
"3",
|
||||
"4",
|
||||
"5",
|
||||
"6",
|
||||
"7",
|
||||
"8",
|
||||
"9",
|
||||
"+",
|
||||
"/",
|
||||
];
|
||||
|
||||
/**
|
||||
* CREDIT: https://gist.github.com/enepomnyaschih/72c423f727d395eeaa09697058238727
|
||||
* Encodes a given Uint8Array, ArrayBuffer or string into RFC4648 base64 representation
|
||||
* @param data
|
||||
*/
|
||||
export function encode(data: ArrayBuffer | string): string {
|
||||
const uint8 = typeof data === "string"
|
||||
? new TextEncoder().encode(data)
|
||||
: data instanceof Uint8Array
|
||||
? data
|
||||
: new Uint8Array(data);
|
||||
let result = "",
|
||||
i;
|
||||
const l = uint8.length;
|
||||
for (i = 2; i < l; i += 3) {
|
||||
result += base64abc[uint8[i - 2] >> 2];
|
||||
result += base64abc[((uint8[i - 2] & 0x03) << 4) | (uint8[i - 1] >> 4)];
|
||||
result += base64abc[((uint8[i - 1] & 0x0f) << 2) | (uint8[i] >> 6)];
|
||||
result += base64abc[uint8[i] & 0x3f];
|
||||
}
|
||||
if (i === l + 1) {
|
||||
// 1 octet yet to write
|
||||
result += base64abc[uint8[i - 2] >> 2];
|
||||
result += base64abc[(uint8[i - 2] & 0x03) << 4];
|
||||
result += "==";
|
||||
}
|
||||
if (i === l) {
|
||||
// 2 octets yet to write
|
||||
result += base64abc[uint8[i - 2] >> 2];
|
||||
result += base64abc[((uint8[i - 2] & 0x03) << 4) | (uint8[i - 1] >> 4)];
|
||||
result += base64abc[(uint8[i - 1] & 0x0f) << 2];
|
||||
result += "=";
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user