diff --git a/packages/server/src/db.js b/packages/server/src/db.js index a5acd39..b047196 100644 --- a/packages/server/src/db.js +++ b/packages/server/src/db.js @@ -14,6 +14,7 @@ let pushSQL = to_name,\ to_nickname,\ is_group_message,\ + is_self_message,\ speaking_group_member_name,\ filename,\ display_filename\ @@ -28,6 +29,7 @@ let pushSQL = $toName,\ $toNickName,\ $isGroupMessage,\ + $isSelfMessage,\ $speakingGroupMemberName,\ $filename,\ $displayFilename\ @@ -59,6 +61,7 @@ async function initDB(dbFile) { is_group_message INTEGER, speaking_group_member_name TEXT, filename TEXT, + is_self_message, BOOLEAN, display_filename TEXT)` ); break; @@ -86,6 +89,7 @@ async function pushMessage(message) { $toName: message.toName, $toNickName: message.toNickName, $isGroupMessage: message.isGroupMessage, + $isSelfMessage: message.isSentBySelf, $speakingGroupMemberName: message.speakingGroupMemberName, $content: message.content, $filename: message.filename, diff --git a/packages/server/src/message.js b/packages/server/src/message.js index bc10f3d..ccb4820 100644 --- a/packages/server/src/message.js +++ b/packages/server/src/message.js @@ -184,6 +184,7 @@ async function processMessage(msg) { filename: m.filename, fileSize: m.fileSize, displayFilename: m.displayFilename, + isSentBySelf: m.isSentBySelf, }; const clientMsg = { messageID: m.composedID, diff --git a/packages/server/src/socket.js b/packages/server/src/socket.js index 30f3417..2a8c844 100644 --- a/packages/server/src/socket.js +++ b/packages/server/src/socket.js @@ -7,7 +7,8 @@ const formidable = require("formidable"); const dayjs = require("dayjs"); const { Server } = require("socket.io"); const { createServer } = require("http"); -const { bot, getDisplayName, cacheAvatars } = require("./bot.js"); +const db = require("./db.js"); +const { bot, getDisplayName, cacheAvatars, getNickName } = require("./bot.js"); const { config } = require("./config.js"); const { textSHA1, @@ -16,6 +17,8 @@ const { composeAvatarFileName, composeMediaFilePathFromID, getOriginalMsgID, + fileMD5, + composeMessageIDWithTimestamp, } = require("./util.js"); const DEFAULT_ROOM = "default_room"; @@ -57,20 +60,6 @@ io.use((socket, next) => { auth.platform } connected at ${dayjs().format("YYYY-MM-DDTHH:MM:ss")}`; bot.sendText(text, "filehelper"); - pushMessage({ - messageID: "notice_" + (Date.now() + Math.random()), - createTimestamp: Math.trunc(Date.now() / 1000), - type: "text", - fromID: "filehelper", - fromName: "你自己", - fromNickName: "你自己", - toID: bot.user.UserName, - toName: "你自己", - toNickName: bot.user.NickName, - isGroupMessage: false, - isSentBySelf: false, - content: text, - }); }); function deactivateSession(socket) { @@ -132,16 +121,29 @@ async function sendMessage(_, { type, target, data, filename }) { return "联系人不存在"; } let resp; + let uploadedFilepath; + let content = ""; + const isSentToGroup = bot.Contact.isRoomContact(targetContact); switch (type) { case "text": resp = await bot.sendText(data, target); + content = data; break; case "emotion": resp = await bot.sendEmoticon(data, target); + // FIXME: 如果是收藏的图片表情,应解析 + content = "[表情]"; break; case "img": case "video": case "file": { + if (type === "img") { + content = "[图片]"; + } else if (type === "video") { + content = "[视频]"; + } else { + content = "[文件]"; + } const defaultFilenameMap = { img: "pic.jpg", video: "video.mp4", @@ -187,15 +189,42 @@ async function sendMessage(_, { type, target, data, filename }) { } catch (e) { return e?.tips || e?.message || "消息发送失败"; } - // 上传后删除 - fs.rm(newFilename, () => {}); + uploadedFilepath = newFilename; break; } default: return "不支持的文件类型"; } + let mediaFilename; + if (uploadedFilepath) { + mediaFilename = + (await fileMD5(uploadedFilepath)) + path.extname(uploadedFilepath); + ensureMediaDirFromComposedID(); + fs.renameSync(); + } + const timestamp = dayjs().unix(); + const composedID = composeMessageIDWithTimestamp(timestamp, resp.MsgID); + const dbMsg = { + messageID: composedID, + createTimestamp: timestamp, + type, + messageType: null, + fromName: bot.user.NickName, + fromNickName: bot.user.NickName, + toName: getDisplayName(targetContact), + toNickName: getNickName(targetContact), + isGroupMessage: isSentToGroup, + isSentBySelf: true, + speakingGroupMemberName: isSentToGroup && bot.user.NickName, + content: content, + filename: mediaFilename, + fileSize: uploadedFilepath && fs.statSync(uploadedFilepath).size, + displayFilename: filename || null, + }; + db.pushMessage(dbMsg); return { messageID: resp.MsgID, + composedID, }; } async function revokeMessage(_, { composedID, target }) { @@ -271,7 +300,7 @@ router.get("/session/:sessionID/media/:composedID/:filename", (ctx, next) => { /** 上传文件 */ router.post("/upload_file", async (ctx, next) => { - if (!activeSessions.has(ctx.request.headers['sid'])) { + if (!activeSessions.has(ctx.request.headers["sid"])) { ctx.throw(401); } const filename = dayjs().format("YYYYMMDD_") + String(Math.random()).slice(2); diff --git a/packages/server/src/util.js b/packages/server/src/util.js index 64ce582..f56ff32 100644 --- a/packages/server/src/util.js +++ b/packages/server/src/util.js @@ -7,6 +7,9 @@ const { config } = require("./config"); function composeMessageID(msg) { return dayjs.unix(msg["CreateTime"]).format("YYYYMMDD") + msg["MsgId"]; } +function composeMessageIDWithTimestamp(timestamp, originalMsgID) { + return dayjs.unix(timestamp).format("YYYYMMDD") + originalMsgID; +} function getOriginalMsgID(composedID) { return composedID.slice(8); } @@ -38,6 +41,11 @@ function ensureMediaDir(msg) { fs.mkdirSync(dir, { recursive: true }); return dir; } +function ensureMediaDirFromComposedID(composedID) { + const dir = composeMediaFilePathFromID(composedID, ""); + fs.mkdirSync(dir, { recursive: true }); + return dir; +} function ensureAvatarDir() { const dir = path.join(config.cacheDir, "avatar"); fs.mkdirSync(dir, { recursive: true }); @@ -71,6 +79,7 @@ function textSHA1(text) { module.exports = { composeMessageID, + composeMessageIDWithTimestamp, getOriginalMsgID, composeFilename, composeMediaFilePath, @@ -78,6 +87,7 @@ module.exports = { composeCacheFilePath, composeAvatarFileName, ensureMediaDir, + ensureMediaDirFromComposedID, ensureAvatarDir, ensureUploadDir, fileMD5,