diff --git a/config.json b/config.json index aa26bac..2a76b04 100644 --- a/config.json +++ b/config.json @@ -1,5 +1,5 @@ { - "homeserverURL": "https://foxarmy.org/", - "token": "syt_bWVldHM_bwurBHyoTXGkyFHwwTbY_2IoMCg", + "homeserverURL": "https://matrix.org", + "token": "secret", "maxAmountOfPhotoesPerUser": 5 -} \ No newline at end of file +} diff --git a/docker-compose.yml b/docker-compose.yml index 8e3f9a8..88bf59b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -2,7 +2,9 @@ services: meets: build: . depends_on: - - postgresql + postgresql: + condition: service_healthy + restart: true env_file: meets.env volumes: - ./bot_data:/usr/src/app/bot_data @@ -12,6 +14,8 @@ services: postgresql: healthcheck: test: ["CMD", "pg_isready", "-U", "meets"] + interval: 2s + start_period: 2s image: "postgres:15" env_file: meets.env volumes: diff --git a/scheme.psql b/scheme.psql index 586aa32..097abaf 100644 --- a/scheme.psql +++ b/scheme.psql @@ -1,7 +1,6 @@ -CREATE TABLE IF NOT EXISTS users( +CREATE TABLE IF NOT EXISTS users ( mx_id VARCHAR(64), room_id VARCHAR(64) UNIQUE, - pictures_urls TEXT[] DEFAULT NULL, name VARCHAR(32) DEFAULT NULL, age SMALLINT DEFAULT NULL, sex CHAR, -- 'm' of 'f' @@ -10,7 +9,27 @@ CREATE TABLE IF NOT EXISTS users( country VARCHAR(64) DEFAULT NULL, city VARCHAR(64) DEFAULT NULL, current_action VARCHAR(16) DEFAULT NULL, - currently_viewing VARCHAR(64), --link to "room_id" - liked_profiles TEXT[], -- link to "room_id" - likes_from TEXT[] -- link to room_id + currently_viewing VARCHAR(64) --link to "room_id" +); + +CREATE TABLE IF NOT EXISTS likes ( + sender VARCHAR(64), -- link to room_id + recipient VARCHAR(64), -- link to room_id + read BOOLEAN DEFAULT FALSE +); + +CREATE UNIQUE INDEX IF NOT EXISTS unique_likes ON likes(sender, recipient) ; + +CREATE TABLE IF NOT EXISTS media ( + owner VARCHAR(64), -- link to room_id, + type CHAR, -- 'i' for image, 'v' for video + purpose CHAR, -- 'p' for media in profile, 'm' for media in message + url VARCHAR(64) -- mxc://...... +); + +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 diff --git a/src/db.js b/src/db.js index 750edf1..19d99ae 100644 --- a/src/db.js +++ b/src/db.js @@ -22,7 +22,7 @@ export const db = await getClient() const getAmountOfUserPictures = async (roomId) => { - return (await db.query("SELECT cardinality(pictures_urls) AS length FROM users WHERE room_id = $1", [roomId])).rows[0].length; + return (await db.query("SELECT COUNT(*) FROM media WHERE owner = $1 AND purpose = 'p'", [roomId])).rows[0].count } const getCurrentUserAction = async (roomId) => { @@ -33,19 +33,52 @@ const setUserState = async (roomId, state) => { await db.query("UPDATE users SET current_action = $1 WHERE room_id = $2", [state, roomId]); } -const appendUserPictures = async (roomId, mxc) => { - await db.query("UPDATE users SET pictures_urls = array_append(pictures_urls, $1) WHERE room_id = $2 RETURNING cardinality(pictures_urls) AS amount", [mxc, roomId]); +const appendUserPictures = async (roomId, mxc, type) => { + await db.query("INSERT INTO media (owner, type, purpose, url) VALUES ($1, $2, 'p', $3)", [roomId, type, mxc]) } const eraseUser = async (roomId) => { await db.query("DELETE FROM users WHERE room_id = $1", [roomId]); } -const selectProfilesForUser = async (roomId) => { +const eraseUserLikes = async (roomId) => { + await db.query("DELETE FROM likes WHERE sender = $1 OR recipient = $1", [roomId]); +} +const eraseUserMedia = async (roomId) => { + await db.query("DELETE FROM media WHERE owner = $1", [roomId]); +} + +const selectProfilesForUser = async (roomId) => { + let interest = (await db.query("SELECT interest FROM users WHERE room_id = $1", [roomId])).rows[0].interest let userAge = (await db.query("SELECT age FROM users WHERE room_id = $1", [roomId])).rows[0].age //Selecting profiles other than user's and with difference in age +-2. - return (await db.query("SELECT room_id, name, age, sex, description, country, city, pictures_urls FROM users WHERE age::numeric <@ ANY(ARRAY[numrange($1 - 2, $1 + 2)]) AND room_id != $2 ORDER BY RANDOM() LIMIT 1", [userAge, roomId])).rows[0] + let user + if (interest === '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)]) + AND room_id != $2 + ORDER BY RANDOM() + LIMIT 1`, [userAge, roomId]) + ).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 + ORDER BY RANDOM() + LIMIT 1`, [userAge, roomId, interest]) + ).rows[0] + } + if (!user) return null + let media = await getUserProfilePictures(user.room_id); + + user.media = media + return user } const setUserCurrentlyViewingProfile = async (roomId, anotherRoomId) => { @@ -58,27 +91,64 @@ const getUserCurrentlyViewingProfile = async (roomId) => { //Newlike is a room id of a user who was liked_profiles const appendUserLikes = async (roomId, newLike) => { - await db.query("UPDATE users SET liked_profiles = array_append(liked_profiles, $1) WHERE room_id = $2", [newLike, roomId]) - await db.query("UPDATE users SET likes_from = array_append(likes_from, $1) WHERE room_id = $2", [roomId, newLike]) + //We need to delete likes that was read from both, so we can add a new (the same) like. + await db.query(`DELETE FROM likes l1 + USING likes l2 + WHERE l1.sender = l2.recipient + AND l1.recipient = l2.sender + AND l1.read = TRUE + AND l2.read = TRUE`); + + await db.query("INSERT INTO likes (sender, recipient) VALUES ($1, $2) ON CONFLICT DO NOTHING", [roomId, newLike]); } const getUserLikes = async (roomId) => { - return (await db.query("SELECT likes FROM users WHERE room_id = $1", [roomId])).rows[0].likes + return (await db.query("SELECT recipient FROM likes WHERE sender = $1", [roomId])).rows[0].recipient } const checkForMutualLike = async (roomId1, roomId2) => { - return (await db.query("SELECT EXISTS (SELECT 1 FROM users u1 JOIN users u2 ON u1.room_id = ANY(u2.liked_profiles) AND u2.room_id = ANY(u1.liked_profiles) WHERE u1.room_id = $1 AND u2.room_id = $2 );", [roomId1, roomId2])).rows[0].exists + return (await db.query(`SELECT COUNT(*) > 0 AS value + FROM likes l1 + JOIN likes l2 ON l1.sender = l2.recipient AND l1.recipient = l2.sender + WHERE (l1.read = FALSE OR l2.read = FALSE) + AND l1.sender = $1 + AND l1.recipient = $2`, [roomId1, roomId2])).rows[0].value } const getProfileInfo = async (roomId) => { - return (await db.query("SELECT mx_id, room_id, name, age, sex, description, country, city, pictures_urls FROM users WHERE room_id = $1", [roomId])).rows[0]; + let user = (await db.query("SELECT mx_id, room_id, name, age, sex, description, country, city FROM users WHERE room_id = $1", [roomId])).rows[0]; + if (!user) return null + let media = await getUserProfilePictures(user.room_id); + user.media = media + return user +} + +const getUserProfilePictures = async (roomId) => { + return (await db.query("SELECT url, type FROM media WHERE owner = $1 AND purpose = 'p'", [roomId])).rows } const getAllLikesForUser = async (roomId) => { - return (await db.query("SELECT room_id FROM users WHERE $1 = ANY(liked_profiles)", [roomId])).rows + return (await db.query("SELECT sender FROM likes WHERE recipient = $1 AND read = FALSE", [roomId])).rows } -const removeLikeFromUser = async (roomId, liked) => { - await db.query("UPDATE users SET liked_profiles = ARRAY_REMOVE(liked_profiles, $1) WHERE room_id = $2", [roomId, liked]) +const markLikeAsRead = async (roomId, recipient,) => { + await db.query("UPDATE likes SET read = TRUE WHERE sender = $1 AND recipient = $2", [roomId, recipient]); } -export { eraseUser, appendUserPictures, setUserState, getCurrentUserAction, getAmountOfUserPictures, selectProfilesForUser, setUserCurrentlyViewingProfile, getUserCurrentlyViewingProfile, appendUserLikes, getUserLikes, checkForMutualLike, getProfileInfo, getAllLikesForUser, removeLikeFromUser } \ No newline at end of file +export { + eraseUser, + appendUserPictures, + setUserState, + getCurrentUserAction, + getAmountOfUserPictures, + selectProfilesForUser, + setUserCurrentlyViewingProfile, + getUserCurrentlyViewingProfile, + appendUserLikes, + getUserLikes, + checkForMutualLike, + getProfileInfo, + getAllLikesForUser, + markLikeAsRead, + eraseUserLikes, + eraseUserMedia +} \ No newline at end of file diff --git a/src/index.js b/src/index.js index cfcaed0..ca819bc 100644 --- a/src/index.js +++ b/src/index.js @@ -47,7 +47,7 @@ const client = new MatrixClient(homeserverUrl, accessToken, storage, crypto); client.on("room.message", async (roomId, event) => { try { - if (event.content?.msgtype !== 'm.text' && event.content?.msgtype !== 'm.image') return; + if (event.content?.msgtype !== 'm.text' && event.content?.msgtype !== 'm.image' && event.content?.msgtype !== 'm.video') return; if (event.sender === await client.getUserId()) return; let current_action = await getCurrentUserAction(roomId); @@ -98,22 +98,30 @@ client.on("room.message", async (roomId, event) => { await processRequest(client, roomId, current_action, answer, 'pictures'); break; case "pictures": - if (event.content?.msgtype !== 'm.image') { + if (event.content?.msgtype !== 'm.image' && event.content?.msgtype !== 'm.video') { await client.sendText(roomId, messages.setup.done); await setUserState(roomId, 'view_profiles') await showRandomProfileToUser(client, roomId); } else { let pictures_count = parseInt(await getAmountOfUserPictures(roomId)); - if (pictures_count > maxAmountOfPhotoesPerUser) { + if (pictures_count >= maxAmountOfPhotoesPerUser) { await client.sendText(roomId, messages.errors.toomuch); await setUserState(roomId, 'view_profiles') await showRandomProfileToUser(client, roomId); } else { const message = new MessageEvent(event); const fileEvent = new MessageEvent(message.raw); + console.log(event) + console.log(fileEvent) const decrypted = await client.crypto.decryptMedia(fileEvent.content.file); const mxc = await client.uploadContent(decrypted); - await appendUserPictures(roomId, mxc); + let type; + if (event.content.msgtype === "m.image") { + type = 'p' + } else if (event.content.msgtype === "m.video") { + type = 'v' + } + await appendUserPictures(roomId, mxc, type); let pictures_count = await getAmountOfUserPictures(roomId); if (pictures_count < maxAmountOfPhotoesPerUser) { await client.sendText(roomId, messages.setup.more + String(maxAmountOfPhotoesPerUser - pictures_count)); @@ -153,7 +161,6 @@ client.on("room.message", async (roomId, event) => { await showRandomProfileToUser(client, roomId); break; case '2': - // await setUserState(roomId, 'view_mut_likes'); await showNewLikes(client, roomId); await client.sendText(roomId, messages.general.menu); break; @@ -165,9 +172,6 @@ client.on("room.message", async (roomId, event) => { break; } break; - // case 'view_mutual_likes': - // // await showNewLikes(client, roomId) - // break; default: await client.sendText(roomId, messages.errors.didntunderstand); return; @@ -213,28 +217,4 @@ client.on("room.invite", async (roomId, event) => { }); -client.start().then(() => logInfo("Bot started!")); - - -/* -{ -"body":"test", -"file": { - "url":"mxc://foxarmy.org/GSzzyXNgrSaOQjiHlnoKCQGd" -} -} -*/ - -/* -{ -"body":"test", -"info":{ - "w":256, - "h":256, - "mimetype":"image/png" -}, -"file": { - "url":"mxc://foxarmy.org/GSzzyXNgrSaOQjiHlnoKCQGd" -} -} -*/ \ No newline at end of file +client.start().then(() => logInfo("Bot started!")); \ No newline at end of file diff --git a/src/interactions.js b/src/interactions.js index e6c59b7..9ac78b0 100644 --- a/src/interactions.js +++ b/src/interactions.js @@ -5,7 +5,16 @@ import { readMessages } from './utils.js'; -import { db, setUserState, selectProfilesForUser, setUserCurrentlyViewingProfile, getProfileInfo, getAllLikesForUser, removeLikeFromUser, checkForMutualLike } from "./db.js" +import { + db, + setUserState, + selectProfilesForUser, + setUserCurrentlyViewingProfile, + getProfileInfo, + getAllLikesForUser, + checkForMutualLike, + markLikeAsRead, +} from "./db.js" const messages = readMessages() @@ -41,13 +50,19 @@ ${chosenProfile.description}` await setUserCurrentlyViewingProfile(roomId, chosenProfile.room_id) await client.sendText(roomId, message); - if (chosenProfile.pictures_urls) { - for (let picture of chosenProfile.pictures_urls) { - console.log(picture) + if (chosenProfile.media) { + for (let media of chosenProfile.media) { + console.log(media) + let msgtype + if (media.type == 'p') { + msgtype = "m.image" + } else if (media.type == 'v') { + msgtype = "m.video" + } await client.sendMessage(roomId, { - msgtype: "m.image", - body: "image.png", - url: picture + msgtype: msgtype, + body: "Profile media", + url: media.url }); } } @@ -67,13 +82,19 @@ ${profileInfo.description}` await client.sendText(roomId, messages.general.showalike); await client.sendText(roomId, message); - if (profileInfo.pictures_urls) { - for (let picture of profileInfo.pictures_urls) { - console.log(picture) + if (profileInfo.media) { + for (let media of profileInfo.media) { + console.log(media) + let msgtype + if (media.type == 'p') { + msgtype = "m.image" + } else if (media.type == 'v') { + msgtype = "m.video" + } await client.sendMessage(roomId, { - msgtype: "m.image", - body: "image.png", - url: picture + msgtype: msgtype, + body: "Profile media", + url: media.url }); } } @@ -83,13 +104,12 @@ ${profileInfo.description}` const showNewLikes = async (client, roomId) => { let likes = (await getAllLikesForUser(roomId)); - console.log(`likes`); - console.log(likes) for (let liked of likes) { - // if (await checkForMutualLike(liked.roomId, roomId)) { - await showProfileToUser(client, roomId, liked.room_id); - await removeLikeFromUser(roomId, liked.room_id); - // } + console.log(await checkForMutualLike(liked.sender, roomId)) + if (await checkForMutualLike(liked.sender, roomId)) { + await showProfileToUser(client, roomId, liked.sender); + await markLikeAsRead(liked.sender, roomId); + } } }