chat.js 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132
  1. // Copyright (c) 2025 gugdun
  2. // All rights reserved. Unauthorized use, copying, or distribution is strictly prohibited.
  3. const path = require("path");
  4. const express = require("express");
  5. const ejs = require("ejs");
  6. const db = require("../db");
  7. const multer = require("multer");
  8. const sharp = require("sharp");
  9. const createDOMPurify = require("dompurify");
  10. const { JSDOM } = require("jsdom");
  11. const { marked } = require("marked");
  12. const package = require("../../package.json");
  13. const { encrypt, decrypt } = require("../util/crypto");
  14. const views = path.join(__dirname, "..", "views");
  15. const router = express.Router();
  16. const storage = multer.memoryStorage();
  17. const upload = multer({ storage: storage });
  18. router.get("/chat/:chat_id", async (req, res) => {
  19. if (req.user) {
  20. try {
  21. const user = await db.one("SELECT id FROM users WHERE username = $1", [ req.params.chat_id ]);
  22. const chat = await db.one("SELECT id FROM chats WHERE (user1_id = $1 AND user2_id = $2) OR (user1_id = $2 AND user2_id = $1)", [ req.user.id, user?.id ]);
  23. const messages = await db.any("SELECT username, attachment_id, text, timestamp FROM messages JOIN users ON users.id = user_id WHERE chat_id = $1 ORDER BY messages.timestamp DESC LIMIT 20", [ chat?.id ]);
  24. const moreMessages = await db.any("SELECT 1 FROM messages WHERE chat_id = $1 AND timestamp < $2", [ chat?.id, messages[messages.length - 1]?.timestamp ]);
  25. res.render("layout", {
  26. child: await ejs.renderFile(path.join(views, "chat.ejs"), {
  27. version: package.version,
  28. username: req.user.username,
  29. title: req.params.chat_id,
  30. chatId: chat?.id,
  31. empty: messages.length < 1,
  32. messages: messages.map((message) => {
  33. return {
  34. username: message.username,
  35. text: decrypt(message.text),
  36. datetime: message.timestamp,
  37. attachment: message.attachment_id
  38. };
  39. }),
  40. hasMoreMessages: moreMessages.length > 0,
  41. firstTimestamp: messages[messages.length - 1]?.timestamp || new Date().toISOString()
  42. })
  43. });
  44. } catch (err) {
  45. console.log(err);
  46. res.render("layout", {
  47. child: await ejs.renderFile(path.join(views, "chat.ejs"), {
  48. version: package.version,
  49. username: null,
  50. title: req.params.chat_id,
  51. chatId: null,
  52. empty: true,
  53. messages: [],
  54. hasMoreMessages: false,
  55. firstTimestamp: new Date().toISOString()
  56. })
  57. });
  58. }
  59. } else {
  60. res.redirect("/login");
  61. }
  62. });
  63. router.post("/chat/:chat_id", upload.single("attachment"), async (req, res) => {
  64. if (req.user) {
  65. try {
  66. const user = await db.one("SELECT id FROM users WHERE username = $1", [ req.params.chat_id ]);
  67. const chat = await db.one("SELECT id FROM chats WHERE (user1_id = $1 AND user2_id = $2) OR (user1_id = $2 AND user2_id = $1)", [ req.user.id, user?.id ]);
  68. const datetime = new Date().toISOString();
  69. let attachment = null;
  70. if (req.file) {
  71. const attachmentBuffer = await sharp(req.file.buffer).autoOrient().jpeg().toBuffer();
  72. attachment = await db.one("INSERT INTO attachments (type, data) VALUES ($1, $2) RETURNING id", [ "image/jpeg", attachmentBuffer ]);
  73. const thumbnailBuffer = await sharp(req.file.buffer).autoOrient().resize(256, 256, { fit: "inside" }).jpeg().toBuffer();
  74. await db.none("INSERT INTO thumbnails (attachment_id, type, data) VALUES ($1, $2, $3)", [ attachment?.id, "image/jpeg", thumbnailBuffer ]);
  75. }
  76. const window = new JSDOM("").window;
  77. const DOMPurify = createDOMPurify(window);
  78. await db.none("INSERT INTO messages (chat_id, user_id, attachment_id, text, timestamp) VALUES ($1, $2, $3, $4, $5)", [
  79. chat?.id,
  80. req.user.id,
  81. attachment?.id ?? null,
  82. encrypt(DOMPurify.sanitize(marked.parse(req.body.text))),
  83. datetime
  84. ]);
  85. res.json({ success: true });
  86. } catch (err) {
  87. console.log(err);
  88. res.json({ success: false });
  89. }
  90. } else {
  91. res.redirect("/login");
  92. }
  93. });
  94. router.post("/chat/:chat_id/messages", async (req, res) => {
  95. if (req.user) {
  96. try {
  97. const user = await db.one("SELECT id FROM users WHERE username = $1", [req.params.chat_id]);
  98. const chat = await db.one("SELECT id FROM chats WHERE (user1_id = $1 AND user2_id = $2) OR (user1_id = $2 AND user2_id = $1)", [ req.user.id, user?.id ]);
  99. const firstTimestamp = req.body.timestamp;
  100. const messages = await db.any("SELECT username, attachment_id, text, timestamp FROM messages JOIN users ON users.id = user_id WHERE chat_id = $1 AND timestamp < $2 ORDER BY messages.timestamp DESC LIMIT 20", [ chat?.id, firstTimestamp ]);
  101. const moreMessages = await db.any("SELECT 1 FROM messages WHERE chat_id = $1 AND timestamp < $2", [ chat?.id, messages[messages.length - 1]?.timestamp ]);
  102. res.json({
  103. messages: messages.map((message) => {
  104. return {
  105. username: message.username,
  106. text: decrypt(message.text),
  107. datetime: message.timestamp,
  108. attachment: message.attachment_id
  109. }
  110. }),
  111. hasMoreMessages: moreMessages.length > 0,
  112. timestamp: messages[messages.length - 1]?.timestamp || firstTimestamp
  113. });
  114. } catch (err) {
  115. console.log(err);
  116. res.json({
  117. messages: [],
  118. hasMoreMessages: false,
  119. timestamp: req.body.timestamp
  120. });
  121. }
  122. } else {
  123. res.redirect("/login");
  124. }
  125. });
  126. module.exports = router;