diff --git a/client_test/index.html b/client_test/index.html
index de2fe44b..0da8784a 100644
--- a/client_test/index.html
+++ b/client_test/index.html
@@ -13,12 +13,11 @@
window.GLOBAL_ENV = {
API_ENDPOINT: "/api",
WEBAPP_ENDPOINT: "",
- CDN_HOST: "cdn.discordapp.com",
+ CDN_HOST: "//localhost:3003",
ASSET_ENDPOINT: "",
MEDIA_PROXY_ENDPOINT: "https://media.discordapp.net",
WIDGET_ENDPOINT: "//discord.com/widget",
INVITE_HOST: "discord.gg",
-
GUILD_TEMPLATE_HOST: "discord.new",
GIFT_CODE_HOST: "discord.gift",
RELEASE_CHANNEL: "stable",
diff --git a/package-lock.json b/package-lock.json
index 35201da4..052f63c6 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -9,7 +9,7 @@
"version": "1.0.0",
"license": "ISC",
"dependencies": {
- "@fosscord/server-util": "^1.3.9",
+ "@fosscord/server-util": "^1.3.10",
"@types/jest": "^26.0.22",
"@types/json-schema": "^7.0.7",
"ajv": "^8.4.0",
@@ -24,11 +24,12 @@
"env-paths": "^2.2.1",
"express": "^4.17.1",
"express-validator": "^6.9.2",
+ "form-data": "^3.0.0",
"i18next": "^19.8.5",
"i18next-http-middleware": "^3.1.3",
"i18next-node-fs-backend": "^2.1.3",
"jsonwebtoken": "^8.5.1",
- "lambert-server": "^1.2.2",
+ "lambert-server": "^1.2.3",
"missing-native-js-functions": "^1.2.6",
"mongodb": "^3.6.5",
"mongoose": "^5.12.3",
@@ -520,9 +521,9 @@
}
},
"node_modules/@fosscord/server-util": {
- "version": "1.3.9",
- "resolved": "https://registry.npmjs.org/@fosscord/server-util/-/server-util-1.3.9.tgz",
- "integrity": "sha512-1oOcMMOBVJO3BodyKQaP3ukg9ok8qfCeIAHSCcloO02lAq45Y+EI7Y7i5a8dotYl7CP8Uv8ke9mGqI3Tojg0Fw==",
+ "version": "1.3.10",
+ "resolved": "https://registry.npmjs.org/@fosscord/server-util/-/server-util-1.3.10.tgz",
+ "integrity": "sha512-pu+XAoerl/WLFxoNxT1NV7Nj0QT+QigK5ghr1VCXkN5N/pUAJUyC72fJPYk+5Ug0CbJkPb0XNsRVJpuz8k0R2g==",
"dependencies": {
"@types/jsonwebtoken": "^8.5.0",
"@types/mongoose-autopopulate": "^0.10.1",
@@ -2123,8 +2124,7 @@
"node_modules/asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
- "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=",
- "dev": true
+ "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k="
},
"node_modules/atob": {
"version": "2.1.2",
@@ -3290,7 +3290,6 @@
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
- "dev": true,
"dependencies": {
"delayed-stream": "~1.0.0"
},
@@ -3848,7 +3847,6 @@
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
"integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=",
- "dev": true,
"engines": {
"node": ">=0.4.0"
}
@@ -4872,7 +4870,6 @@
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.0.tgz",
"integrity": "sha512-CKMFDglpbMi6PyN+brwB9Q/GOw0eAnsrEZDgcsH5Krhz5Od/haKHAX0NmQfha2zPPz0JpWzA7GJHGSnvCRLWsg==",
- "dev": true,
"dependencies": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
@@ -7928,9 +7925,9 @@
}
},
"node_modules/lambert-server": {
- "version": "1.2.2",
- "resolved": "https://registry.npmjs.org/lambert-server/-/lambert-server-1.2.2.tgz",
- "integrity": "sha512-rsQlFQZDYl3+feM25WNdV8cUf6yS5SYyelCFV8ohF6pMLqQJfXcbvsEYzGeF1pIIkjnWehxTK2J9kJJpNWeWFg==",
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/lambert-server/-/lambert-server-1.2.3.tgz",
+ "integrity": "sha512-tBcxVH5Hj6ts/hk11e5ABc1ihxH9aIrXJth/9ivkfeqWjZEEzGrxvEmtnPULwGGy+k6lvUoZw725LDgVxoYGKQ==",
"dependencies": {
"body-parser": "^1.19.0",
"express": "^4.17.1",
@@ -12860,9 +12857,9 @@
}
},
"@fosscord/server-util": {
- "version": "1.3.9",
- "resolved": "https://registry.npmjs.org/@fosscord/server-util/-/server-util-1.3.9.tgz",
- "integrity": "sha512-1oOcMMOBVJO3BodyKQaP3ukg9ok8qfCeIAHSCcloO02lAq45Y+EI7Y7i5a8dotYl7CP8Uv8ke9mGqI3Tojg0Fw==",
+ "version": "1.3.10",
+ "resolved": "https://registry.npmjs.org/@fosscord/server-util/-/server-util-1.3.10.tgz",
+ "integrity": "sha512-pu+XAoerl/WLFxoNxT1NV7Nj0QT+QigK5ghr1VCXkN5N/pUAJUyC72fJPYk+5Ug0CbJkPb0XNsRVJpuz8k0R2g==",
"requires": {
"@types/jsonwebtoken": "^8.5.0",
"@types/mongoose-autopopulate": "^0.10.1",
@@ -14196,8 +14193,7 @@
"asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
- "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=",
- "dev": true
+ "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k="
},
"atob": {
"version": "2.1.2",
@@ -15195,7 +15191,6 @@
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
- "dev": true,
"requires": {
"delayed-stream": "~1.0.0"
}
@@ -15691,8 +15686,7 @@
"delayed-stream": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
- "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=",
- "dev": true
+ "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk="
},
"delegates": {
"version": "1.0.0",
@@ -16533,7 +16527,6 @@
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.0.tgz",
"integrity": "sha512-CKMFDglpbMi6PyN+brwB9Q/GOw0eAnsrEZDgcsH5Krhz5Od/haKHAX0NmQfha2zPPz0JpWzA7GJHGSnvCRLWsg==",
- "dev": true,
"requires": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
@@ -18981,9 +18974,9 @@
}
},
"lambert-server": {
- "version": "1.2.2",
- "resolved": "https://registry.npmjs.org/lambert-server/-/lambert-server-1.2.2.tgz",
- "integrity": "sha512-rsQlFQZDYl3+feM25WNdV8cUf6yS5SYyelCFV8ohF6pMLqQJfXcbvsEYzGeF1pIIkjnWehxTK2J9kJJpNWeWFg==",
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/lambert-server/-/lambert-server-1.2.3.tgz",
+ "integrity": "sha512-tBcxVH5Hj6ts/hk11e5ABc1ihxH9aIrXJth/9ivkfeqWjZEEzGrxvEmtnPULwGGy+k6lvUoZw725LDgVxoYGKQ==",
"requires": {
"body-parser": "^1.19.0",
"express": "^4.17.1",
diff --git a/package.json b/package.json
index 0f651646..b3b2414b 100644
--- a/package.json
+++ b/package.json
@@ -29,7 +29,7 @@
},
"homepage": "https://github.com/fosscord/fosscord-api#readme",
"dependencies": {
- "@fosscord/server-util": "^1.3.9",
+ "@fosscord/server-util": "^1.3.10",
"@types/jest": "^26.0.22",
"@types/json-schema": "^7.0.7",
"ajv": "^8.4.0",
@@ -44,11 +44,12 @@
"env-paths": "^2.2.1",
"express": "^4.17.1",
"express-validator": "^6.9.2",
+ "form-data": "^3.0.0",
"i18next": "^19.8.5",
"i18next-http-middleware": "^3.1.3",
"i18next-node-fs-backend": "^2.1.3",
"jsonwebtoken": "^8.5.1",
- "lambert-server": "^1.2.2",
+ "lambert-server": "^1.2.3",
"missing-native-js-functions": "^1.2.6",
"mongodb": "^3.6.5",
"mongoose": "^5.12.3",
diff --git a/src/Server.ts b/src/Server.ts
index 79a8bba3..0ff4df8c 100644
--- a/src/Server.ts
+++ b/src/Server.ts
@@ -97,7 +97,7 @@ export class FosscordServer extends Server {
app.use("/api/v8", prefix);
this.app = app;
this.app.use(ErrorHandler);
- const indexHTML = await fs.readFile(path.join(__dirname, "..", "client_test", "index.html"));
+ const indexHTML = await fs.readFile(path.join(__dirname, "..", "client_test", "index.html"), { encoding: "utf8" });
this.app.use("/assets", express.static(path.join(__dirname, "..", "assets")));
@@ -143,7 +143,12 @@ export class FosscordServer extends Server {
this.app.get("*", (req, res) => {
res.set("Cache-Control", "public, max-age=" + 60 * 60 * 24);
res.set("content-type", "text/html");
- res.send(indexHTML);
+ res.send(
+ indexHTML.replace(
+ /CDN_HOST: ".+"/,
+ `CDN_HOST: "${(Config.get().cdn.endpoint || "http://localhost:3003").replace(/https?:/, "")}"`
+ )
+ );
});
return super.start();
}
diff --git a/src/routes/auth/register.ts b/src/routes/auth/register.ts
index e24485da..50bac43a 100644
--- a/src/routes/auth/register.ts
+++ b/src/routes/auth/register.ts
@@ -181,6 +181,7 @@ router.post(
premium: false,
premium_type: 0,
phone: null,
+ bio: "",
mfa_enabled: false,
verified: false,
disabled: false,
diff --git a/src/routes/channels/#channel_id/messages/#message_id/index.ts b/src/routes/channels/#channel_id/messages/#message_id/index.ts
index cfff07d1..aee517fc 100644
--- a/src/routes/channels/#channel_id/messages/#message_id/index.ts
+++ b/src/routes/channels/#channel_id/messages/#message_id/index.ts
@@ -45,6 +45,8 @@ router.patch("/", check(MessageCreateSchema), async (req, res) => {
return res.json(toObject(message));
});
+// TODO: delete attachments in message
+
router.delete("/", async (req, res) => {
const { message_id, channel_id } = req.params;
diff --git a/src/routes/channels/#channel_id/messages/index.ts b/src/routes/channels/#channel_id/messages/index.ts
index cdc46d14..4bf1516d 100644
--- a/src/routes/channels/#channel_id/messages/index.ts
+++ b/src/routes/channels/#channel_id/messages/index.ts
@@ -1,24 +1,13 @@
import { Router } from "express";
-import {
- ChannelModel,
- ChannelType,
- getPermission,
- Message,
- MessageCreateEvent,
- MessageDocument,
- MessageModel,
- Snowflake,
- toObject
-} from "@fosscord/server-util";
+import { Attachment, ChannelModel, ChannelType, getPermission, MessageDocument, MessageModel, toObject } from "@fosscord/server-util";
import { HTTPError } from "lambert-server";
import { MessageCreateSchema } from "../../../../schema/Message";
import { check, instanceOf, Length } from "../../../../util/instanceOf";
-import { PublicUserProjection } from "../../../../util/User";
import multer from "multer";
-import { emitEvent } from "../../../../util/Event";
import { Query } from "mongoose";
-import { PublicMemberProjection } from "../../../../util/Member";
import { sendMessage } from "../../../../util/Message";
+import { uploadFile } from "../../../../util/cdn";
+
const router: Router = Router();
export default router;
@@ -93,7 +82,14 @@ router.get("/", async (req, res) => {
});
// TODO: config max upload size
-const messageUpload = multer({ limits: { fieldSize: 1024 * 1024 * 1024 * 50 } }); // max upload 50 mb
+const messageUpload = multer({
+ limits: {
+ fileSize: 1024 * 1024 * 100,
+ fields: 10,
+ files: 1
+ },
+ storage: multer.memoryStorage()
+}); // max upload 50 mb
// TODO: dynamically change limit of MessageCreateSchema with config
// TODO: check: sum of all characters in an embed structure must not exceed 6000 characters
@@ -101,14 +97,31 @@ const messageUpload = multer({ limits: { fieldSize: 1024 * 1024 * 1024 * 50 } })
// https://discord.com/developers/docs/resources/channel#create-message
// TODO: text channel slowdown
// TODO: trim and replace message content and every embed field
+
// Send message
-router.post("/", check(MessageCreateSchema), async (req, res) => {
+router.post("/", check(MessageCreateSchema), messageUpload.single("file"), async (req, res) => {
const { channel_id } = req.params;
- const body = req.body as MessageCreateSchema;
+ var body = req.body as MessageCreateSchema;
+ const attachments: Attachment[] = [];
+
+ if (req.file) {
+ try {
+ const file = await uploadFile(`/attachments/${channel_id}`, req.file);
+ attachments.push({ ...file, proxy_url: file.url });
+ } catch (error) {
+ return res.status(400).json(error);
+ }
+ }
+
+ if (body.payload_json) {
+ body = JSON.parse(body.payload_json);
+ const errors = instanceOf(MessageCreateSchema, body, { req });
+ if (errors !== true) throw errors;
+ }
const embeds = [];
if (body.embed) embeds.push(body.embed);
- const data = await sendMessage({ ...body, type: 0, pinned: false, author_id: req.user_id, embeds, channel_id });
+ const data = await sendMessage({ ...body, type: 0, pinned: false, author_id: req.user_id, embeds, channel_id, attachments });
return res.send(data);
});
diff --git a/src/routes/users/#id/index.ts b/src/routes/users/#id/index.ts
index 185b2e5f..a2ad3ae6 100644
--- a/src/routes/users/#id/index.ts
+++ b/src/routes/users/#id/index.ts
@@ -6,10 +6,8 @@ const router: Router = Router();
router.get("/", async (req: Request, res: Response) => {
const { id } = req.params;
- const user = await getPublicUser(id);
- if (!user) throw new HTTPError("User not found", 404);
- res.json(user);
+ res.json(await getPublicUser(id));
});
export default router;
diff --git a/src/routes/users/@me/index.ts b/src/routes/users/@me/index.ts
index d139203d..4f17fbee 100644
--- a/src/routes/users/@me/index.ts
+++ b/src/routes/users/@me/index.ts
@@ -2,31 +2,35 @@ import { Router, Request, Response } from "express";
import { UserModel, toObject } from "@fosscord/server-util";
import { HTTPError } from "lambert-server";
import { getPublicUser } from "../../../util/User";
-import { UserModifySchema } from "../../../schema/User"
+import { UserModifySchema } from "../../../schema/User";
import { check } from "../../../util/instanceOf";
+import { uploadFile } from "../../../util/cdn";
const router: Router = Router();
router.get("/", async (req: Request, res: Response) => {
- const user = await UserModel.findOne({ id: req.user_id }).exec();
- if (!user) throw new HTTPError("User not found", 404);
-
- var publicUser = await getPublicUser(user.id);
-
- res.json(publicUser);
+ res.json(await getPublicUser(req.user_id));
});
router.patch("/", check(UserModifySchema), async (req: Request, res: Response) => {
const body = req.body as UserModifySchema;
- const user = await UserModel.findOne({ id: req.user_id }).exec();
- if (!user) throw new HTTPError("User not found", 404);
+ if (body.avatar) {
+ try {
+ const mimetype = body.avatar.split(":")[1].split(";")[0];
+ const buffer = Buffer.from(body.avatar.split(",")[1], "base64");
+
+ // @ts-ignore
+ const { id } = await uploadFile(`/avatars/${req.user_id}`, { buffer, mimetype, originalname: "avatar" });
+ body.avatar = id;
+ } catch (error) {
+ throw new HTTPError("Invalid avatar");
+ }
+ }
- var newuser = await UserModel.findOneAndUpdate({ id: req.user_id }, {
- ...body
- }).exec();
+ const user = await UserModel.findOneAndUpdate({ id: req.user_id }, body).exec();
- res.json(newuser);
+ res.json(toObject(user));
});
export default router;
diff --git a/src/schema/Message.ts b/src/schema/Message.ts
index e6aa42b3..b2e4b1f7 100644
--- a/src/schema/Message.ts
+++ b/src/schema/Message.ts
@@ -68,4 +68,5 @@ export interface MessageCreateSchema {
fail_if_not_exists: boolean;
};
payload_json?: string;
+ file?: any;
}
diff --git a/src/util/Message.ts b/src/util/Message.ts
index 27796997..9b928031 100644
--- a/src/util/Message.ts
+++ b/src/util/Message.ts
@@ -50,7 +50,7 @@ export async function handleMessage(opts: Partial<Message>) {
mention_channels_ids: [],
mention_role_ids: [],
mention_user_ids: [],
- attachments: [], // TODO: message attachments
+ attachments: opts.attachments || [], // TODO: message attachments
embeds: opts.embeds || [],
reactions: opts.reactions || [],
type: opts.type ?? 0
diff --git a/src/util/cdn.ts b/src/util/cdn.ts
new file mode 100644
index 00000000..a66e2215
--- /dev/null
+++ b/src/util/cdn.ts
@@ -0,0 +1,24 @@
+import { Config } from "@fosscord/server-util";
+import FormData from "form-data";
+import fetch from "node-fetch";
+
+export async function uploadFile(path: string, file: Express.Multer.File) {
+ const form = new FormData();
+ form.append("file", file.buffer, {
+ contentType: file.mimetype,
+ filename: file.originalname
+ });
+
+ const response = await fetch(`${Config.get().cdn.endpoint || "http://localhost:3003"}${path}`, {
+ headers: {
+ signature: Config.get().security.requestSignature,
+ ...form.getHeaders()
+ },
+ method: "POST",
+ body: form
+ });
+ const result = await response.json();
+
+ if (response.status !== 200) throw result;
+ return result;
+}
diff --git a/src/util/instanceOf.ts b/src/util/instanceOf.ts
index b67bde27..93a92805 100644
--- a/src/util/instanceOf.ts
+++ b/src/util/instanceOf.ts
@@ -74,10 +74,9 @@ export function instanceOf(
): Boolean {
if (!ref) ref = { obj: null, key: "" };
if (!path) path = "body";
+ if (!type) return true; // no type was specified
try {
- if (!type) return true; // no type was specified
-
if (value == null) {
if (optional) return true;
throw new FieldError("BASE_TYPE_REQUIRED", req.t("common:field.BASE_TYPE_REQUIRED"));
|