From 10994b66122a370ed190bb285afa6eca790c0be9 Mon Sep 17 00:00:00 2001 From: leca Date: Thu, 8 Aug 2024 14:42:20 +0300 Subject: [PATCH] implemented messages --- messages.json | 11 +++++-- scheme.psql | 7 +++-- src/db.js | 47 ++++++++++++++++++++++-------- src/index.js | 81 +++++++++++++++++++++++++++++++++++++++------------ src/utils.js | 30 ++++++++++++++++++- 5 files changed, 140 insertions(+), 36 deletions(-) diff --git a/messages.json b/messages.json index 5abc236..d0dcd5d 100644 --- a/messages.json +++ b/messages.json @@ -5,7 +5,12 @@ "menu": "Pick an action:\n1) Start watching profiles\n2) Check for mutual likes\n3) Check for messages", "newlikes": "You have new mutual like(s)! Go into menu (🏠️) to see!", "showalike": "This person and you mutually liked each other!", - "mxid": "Go drop them a line! Their MXID is: " + "mxid": "Go drop them a line! Their MXID is: ", + "message": "Write one message to this person or upload one picture/video to send them!", + "newmessage": "You've got new message(s)! Go to a menu (🏠️) to see!", + "showmessage": " sent you this: ", + "nonewmessages": "You have no new messages!", + "messagesent": "You message was sent!" }, "setup": { "country": "Write a name of country you are living in. Prefer local language. For example, 'Россия', 'United States' or 'Україна'.", @@ -28,6 +33,8 @@ "twosexes": "There are only two sexes! Please, choose your biological one.", "didntunderstand": "I cannot understand your answer. Please, read my question again and answer!", "notimplemented": "This feature is not yet implemented! Please, keep track of my updates at https://git.foxarmy.org/leca/heart2heart. Bother your admins to update, if released! ;)", - "noprofiles": "There are no profiles left for you! I'm sorry. Please, come back later!" + "noprofiles": "There are no profiles left for you! I'm sorry. Please, come back later!", + "alreadymessaged": "You have already messaged that person. Your message was not sent!" + } } \ No newline at end of file diff --git a/scheme.psql b/scheme.psql index 097abaf..44e88a0 100644 --- a/scheme.psql +++ b/scheme.psql @@ -31,5 +31,8 @@ CREATE TABLE IF NOT EXISTS messages ( sender VARCHAR(64), -- link to room_id recipient VARCHAR(64), -- link to room_id type CHAR, -- 't' for text, 'p' for picture and 'v' for video - content VARCHAR(128) -- will contain a url if media and text if the message is just a text -); \ No newline at end of file + content VARCHAR(128), -- will contain a url if media and text if the message is just a text + read BOOLEAN DEFAULT FALSE +); + +CREATE UNIQUE INDEX IF NOT EXISTS unique_messages ON messages(sender, recipient); diff --git a/src/db.js b/src/db.js index 856e77f..a98a679 100644 --- a/src/db.js +++ b/src/db.js @@ -1,5 +1,6 @@ import pg from 'pg'; import fs from 'fs'; +import { convertMsgType } from './utils.js'; const { Client } = pg; @@ -56,24 +57,24 @@ const selectProfilesForUser = async (roomId) => { //Selecting profiles other than user's and with difference in age +-2. let user; if (myInterest === 'b') // both, no matter what sex - user = (await db.query(`SELECT - room_id, name, age, sex, description, country, city FROM users - WHERE - age::numeric <@ ANY(ARRAY[numrange($1 - 2, $1 + 2)]) + user = (await db.query(`SELECT + room_id, name, age, sex, description, country, city FROM users + WHERE + age::numeric <@ ANY(ARRAY[numrange($1 - 2, $1 + 2)]) AND room_id != $2 AND (interest = $3 OR interest = 'b') - ORDER BY RANDOM() + ORDER BY RANDOM() LIMIT 1`, [userAge, roomId, mySex]) ).rows[0]; else { user = (await db.query(`SELECT - room_id, name, age, sex, description, country, city FROM users - WHERE - age::numeric <@ ANY(ARRAY[numrange($1 - 2, $1 + 2)]) - AND room_id != $2 - AND sex = $3 + room_id, name, age, sex, description, country, city FROM users + WHERE + age::numeric <@ ANY(ARRAY[numrange($1 - 2, $1 + 2)]) + AND room_id != $2 + AND sex = $3 AND (interest = $4 OR interest = 'b') - ORDER BY RANDOM() + ORDER BY RANDOM() LIMIT 1`, [userAge, roomId, myInterest, mySex]) ).rows[0]; } @@ -137,6 +138,24 @@ const getAllLikesForUser = async (roomId) => { const markLikeAsRead = async (roomId, recipient,) => { await db.query("UPDATE likes SET read = TRUE WHERE sender = $1 AND recipient = $2", [roomId, recipient]); }; + +const uploadMediaAsMessage = async (roomId, type, mxc) => { + await db.query("INSERT INTO media(owner, type, purpose, url) VALUES ($1, $2, 'm', $3)", [roomId, convertMsgType(type), mxc]); +}; + +const insertMessageIntoDB = async (roomId, recipient, type, content) => { + let rowAmount = (await db.query("INSERT INTO messages (sender, recipient, type, content) VALUES ($1, $2, $3, $4) ON CONFLICT(sender, recipient) DO NOTHING RETURNING *", [roomId, recipient, convertMsgType(type), content])).rowCount; + return rowAmount == 1; +}; + +const getUnreadMessages = async (roomId) => { + return (await db.query("SELECT (SELECT mx_id FROM users WHERE room_id = sender LIMIT 1), sender, type, content FROM messages WHERE recipient = $1 AND read = FALSE", [roomId])).rows +}; + +const markMessageAsRead = async (roomId, recipient) => { + return (await db.query("UPDATE messages SET read = TRUE WHERE sender = $1 AND recipient = $2", [roomId, recipient])); +} + export { eraseUser, appendUserPictures, @@ -153,5 +172,9 @@ export { getAllLikesForUser, markLikeAsRead, eraseUserLikes, - eraseUserMedia + eraseUserMedia, + uploadMediaAsMessage, + insertMessageIntoDB, + getUnreadMessages, + markMessageAsRead }; \ No newline at end of file diff --git a/src/index.js b/src/index.js index 0f585d5..7542541 100644 --- a/src/index.js +++ b/src/index.js @@ -9,10 +9,12 @@ import { import { StoreType } from "@matrix-org/matrix-sdk-crypto-nodejs"; import { + convertMsgType, logError, logInfo, readConfig, - readMessages + readMessages, + uploadMediaFromEvent } from './utils.js'; import { @@ -24,8 +26,12 @@ import { eraseUserMedia, getAmountOfUserPictures, getCurrentUserAction, + getUnreadMessages, getUserCurrentlyViewingProfile, - setUserState + insertMessageIntoDB, + markMessageAsRead, + setUserState, + uploadMediaAsMessage } from './db.js'; import { processRequest, showRandomProfileToUser, showNewLikes } from "./interactions.js"; @@ -52,13 +58,13 @@ client.on("room.message", async (roomId, event) => { let current_action = await getCurrentUserAction(roomId); let answer = event.content.body; + let msgtype = event.content.msgtype switch (current_action) { case "wait_start": if (answer !== "!start") return; await setUserState(roomId, "country"); await client.sendText(roomId, messages.setup.country); - break; case "country": await processRequest(client, roomId, current_action, answer, 'city'); @@ -115,16 +121,8 @@ client.on("room.message", async (roomId, event) => { await setUserState(roomId, 'view_profiles'); await showRandomProfileToUser(client, roomId); } else { - const message = new MessageEvent(event); - const fileEvent = new MessageEvent(message.raw); - const decrypted = await client.crypto.decryptMedia(fileEvent.content.file); - const mxc = await client.uploadContent(decrypted); - let type; - if (event.content.msgtype === "m.image") { - type = 'p'; - } else if (event.content.msgtype === "m.video") { - type = 'v'; - } + let mxc = await uploadMediaFromEvent(client, event); + let type = convertMsgType(msgtype); await appendUserPictures(roomId, mxc, type); let pictures_count = await getAmountOfUserPictures(roomId); if (pictures_count < maxAmountOfPhotoesPerUser) { @@ -148,17 +146,35 @@ client.on("room.message", async (roomId, event) => { await client.sendText(currently_viewing, messages.general.newlikes); } } else if (answer == '💌' || answer == '3') { - await client.sendText(roomId, messages.errors.notimplemented); + await setUserState(roomId, 'send_message'); + await client.sendText(roomId, messages.general.message); + return; } else if (answer == '🏠️' || answer == '4') { await client.sendText(roomId, messages.general.menu); - await setUserState(roomId, 'menu') - return + await setUserState(roomId, 'menu'); + return; } await showRandomProfileToUser(client, roomId); break; case 'send_message': - + let recipient = await getUserCurrentlyViewingProfile(roomId); + let content; + if (msgtype == "m.image" || msgtype == "m.video") { + let mxc = await uploadMediaFromEvent(client, event); + await uploadMediaAsMessage(roomId, msgtype, mxc); + content = mxc; + } else { + content = answer; + } + if (await insertMessageIntoDB(roomId, recipient, msgtype, content)) { + await client.sendText(recipient, messages.general.newmessage); + await client.sendText(roomId, messages.general.messagesent); + } else { + await client.sendText(roomId, messages.errors.alreadymessaged); + } + await setUserState(roomId, 'view_profiles'); + await showRandomProfileToUser(client, roomId); break; case 'menu': switch (answer) { @@ -171,11 +187,38 @@ client.on("room.message", async (roomId, event) => { await client.sendText(roomId, messages.general.menu); break; case '3': - await setUserState(roomId, 'send_message'); - await client.sendText(roomId, messages.errors.notimplemented); + let unreadMessages = await getUnreadMessages(roomId); + if (!unreadMessages || unreadMessages.length == 0) { + await client.sendText(roomId, messages.general.nonewmessages); + return; + } + await client.sendText(roomId, "Messages:"); + for (let message of unreadMessages) { + await client.sendText(roomId, message.mx_id + messages.general.showmessage); + if (message.type == "t") { + await client.sendText(roomId, message.content) + } else if (message.type == "p" || message.type == "v") { + let msgtype; + + if (message.type == "p") { + msgtype = "m.image" + } else if (message.type == "v") { + msgtype = "m.video"; + } + + await client.sendMessage(roomId, { + msgtype: msgtype, + body: "Message", + url: message.content + }); + } + await markMessageAsRead(message.sender, roomId); + } + await client.sendText(roomId, messages.general.menu); break; default: await client.sendText(roomId, messages.errors.didntunderstand); + await client.sendText(roomId, messages.general.menu); break; } break; diff --git a/src/utils.js b/src/utils.js index d0980ff..5b10489 100644 --- a/src/utils.js +++ b/src/utils.js @@ -1,4 +1,11 @@ import fs from 'fs'; +import { + EncryptionAlgorithm, + MatrixClient, + MessageEvent, + RustSdkCryptoStorageProvider, + SimpleFsStorageProvider, +} from "matrix-bot-sdk"; const configPath = './config.json'; const messagesPath = './messages.json'; @@ -41,4 +48,25 @@ const readMessages = () => { return JSON.parse(fs.readFileSync(messagesPath)); }; -export {readMessages, readConfig, logError, logInfo}; \ No newline at end of file +const uploadMediaFromEvent = async (client, event) => { + const message = new MessageEvent(event); + const fileEvent = new MessageEvent(message.raw); + const decrypted = await client.crypto.decryptMedia(fileEvent.content.file); + const mxc = await client.uploadContent(decrypted); + return mxc; +}; + +const convertMsgType = (msgtype) => { + switch (msgtype) { + case "m.image": + return "p"; + case "m.video": + return "v"; + case "m.text": + return "t"; + default: + return msgtype; + } +}; + +export { readMessages, readConfig, logError, logInfo, uploadMediaFromEvent, convertMsgType }; \ No newline at end of file