huge inital commit

This commit is contained in:
leca 2024-08-03 03:48:20 +03:00
parent b8d715af63
commit ca95e4652d
12 changed files with 2662 additions and 1 deletions

8
.gitignore vendored
View File

@ -130,6 +130,12 @@ dist
.yarn/install-state.gz
.pnp.*
scratch
#Custom
reg.js
config.json
#Bot data
encryption_bot_sled
bot.json
postgres

11
Dockerfile Normal file
View File

@ -0,0 +1,11 @@
FROM node:20.10.0
WORKDIR /usr/src/app
COPY package.json ./package.json
COPY package-lock.json ./package-lock.json
RUN npm i
COPY . .
CMD ["npm", "run" ,"start"]

49
db.drawio Normal file
View File

@ -0,0 +1,49 @@
<mxfile host="app.diagrams.net" agent="Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36" version="24.7.6">
<diagram id="R2lEEEUBdFMjLlhIrx00" name="Page-1">
<mxGraphModel dx="656" dy="384" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="850" pageHeight="1100" math="0" shadow="0" extFonts="Permanent Marker^https://fonts.googleapis.com/css?family=Permanent+Marker">
<root>
<mxCell id="0" />
<mxCell id="1" parent="0" />
<mxCell id="7XvpWkOTBmjUhqAazzN1-1" value="users" style="swimlane;fontStyle=0;childLayout=stackLayout;horizontal=1;startSize=30;horizontalStack=0;resizeParent=1;resizeParentMax=0;resizeLast=0;collapsible=1;marginBottom=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="285" y="130" width="140" height="410" as="geometry" />
</mxCell>
<mxCell id="7XvpWkOTBmjUhqAazzN1-2" value="mx_id VARCHAR(64)" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" parent="7XvpWkOTBmjUhqAazzN1-1" vertex="1">
<mxGeometry y="30" width="140" height="30" as="geometry" />
</mxCell>
<mxCell id="7XvpWkOTBmjUhqAazzN1-3" value="room_id VARCHAR(64)" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" parent="7XvpWkOTBmjUhqAazzN1-1" vertex="1">
<mxGeometry y="60" width="140" height="30" as="geometry" />
</mxCell>
<mxCell id="7XvpWkOTBmjUhqAazzN1-4" value="&lt;div&gt;&lt;span style=&quot;background-color: initial;&quot;&gt;pictures_urls TEXT[]&lt;/span&gt;&lt;br&gt;&lt;/div&gt;" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" parent="7XvpWkOTBmjUhqAazzN1-1" vertex="1">
<mxGeometry y="90" width="140" height="30" as="geometry" />
</mxCell>
<mxCell id="M61CuUsqLGTxYLAPg2Ho-9" value="name VARCHAR(32)" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="7XvpWkOTBmjUhqAazzN1-1">
<mxGeometry y="120" width="140" height="30" as="geometry" />
</mxCell>
<mxCell id="M61CuUsqLGTxYLAPg2Ho-10" value="age SMALLINT" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="7XvpWkOTBmjUhqAazzN1-1">
<mxGeometry y="150" width="140" height="30" as="geometry" />
</mxCell>
<mxCell id="M61CuUsqLGTxYLAPg2Ho-18" value="gender BOOLEAN -- 1 means male" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="7XvpWkOTBmjUhqAazzN1-1">
<mxGeometry y="180" width="140" height="30" as="geometry" />
</mxCell>
<mxCell id="M61CuUsqLGTxYLAPg2Ho-17" value="interest VARCHAR(6) -- male, female or both." style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="7XvpWkOTBmjUhqAazzN1-1">
<mxGeometry y="210" width="140" height="30" as="geometry" />
</mxCell>
<mxCell id="M61CuUsqLGTxYLAPg2Ho-1" value="&lt;div&gt;description VARCHAR(512)&lt;/div&gt;" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="7XvpWkOTBmjUhqAazzN1-1">
<mxGeometry y="240" width="140" height="30" as="geometry" />
</mxCell>
<mxCell id="M61CuUsqLGTxYLAPg2Ho-7" value="&lt;div&gt;country VARCHAR(64)&lt;/div&gt;" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="7XvpWkOTBmjUhqAazzN1-1">
<mxGeometry y="270" width="140" height="30" as="geometry" />
</mxCell>
<mxCell id="M61CuUsqLGTxYLAPg2Ho-8" value="city VARCHAR(64)" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="7XvpWkOTBmjUhqAazzN1-1">
<mxGeometry y="300" width="140" height="30" as="geometry" />
</mxCell>
<mxCell id="M61CuUsqLGTxYLAPg2Ho-11" value="current_action VARCHAR(16)" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="7XvpWkOTBmjUhqAazzN1-1">
<mxGeometry y="330" width="140" height="30" as="geometry" />
</mxCell>
<mxCell id="M61CuUsqLGTxYLAPg2Ho-16" value="currently_viewing VARCHAR(64) --link to &quot;room_id&quot;" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="7XvpWkOTBmjUhqAazzN1-1">
<mxGeometry y="360" width="140" height="50" as="geometry" />
</mxCell>
</root>
</mxGraphModel>
</diagram>
</mxfile>

16
docker-compose.yml Normal file
View File

@ -0,0 +1,16 @@
services:
meets:
build: .
depends_on:
- postgresql
env_file: meets.env
volumes:
- ./bot_data:/usr/src/app/bot_data
- type: bind
source: ./config.json
target: /usr/src/app/config.json
postgresql:
image: "postgres:15"
env_file: meets.env
volumes:
- ./postgres:/var/lib/postgresql/data

3
meets.env Normal file
View File

@ -0,0 +1,3 @@
POSTGRES_USER=meets
POSTGRES_PASSWORD=CH@NGEME!
POSTGRES_DB=meets

24
messages.json Normal file
View File

@ -0,0 +1,24 @@
{
"welcome": "Hello! I am a bot for new meetings in Matrix!\nI'll ask some questions to fill your profile. You can change that info later, if you will.\nThen, I'll start showing you another people's profiles.\nYou can like other profile, skip or send a message to the person.\nIf you mutually liked each other, you'll be given each other's MXID to start a DM.\nMy source code can be found on gitea: https://git.foxarmy.org/leca/daivinchik.\n\nGood luck!",
"setup": {
"country": "Write a name of country you are living in. Prefer local language. For example, 'Россия', 'United States' or 'Україна'.",
"city": "Write a name of city you are living in. Prefer local language. For example, 'Москва', 'Washington, D.C.', or 'Київ'.",
"name": "Write a name of your profile. Not longer than 32 symbols",
"description": "Write a description of your profile. Not longer than 512 symbols.",
"age": "Write your age. People yonger than 14 are disallowed to use the service.",
"gender": "Write your gender: Male or female.",
"interest": "Write who you are interested to talk to? Write 'both', 'male', or 'female'.",
"pictures": "Send up to five pictures that will be shown in your profile. Write any text message when you are done.",
"done": "Phew! You are done filling your profile! Now I'll start showing you others profiles!",
"more": "Got that cool picture! You still can upload up to this amount of pictures:",
"enough": "That's enough of pictures! Let's go further!"
},
"errors": {
"notadm": "This room is not a DM (direct or private messages). You should invite me in a DM! By the way, I support encrypted rooms! Bye and see you in DM!",
"toobig": "Sorry! The length of what you have written here is bigger than I expected! If you beleive that it is an error, please, file an issue on my git: https://git.foxarmy.org/leca/daivinchik. Otherwise, write something a little bit smaller! Thank you!",
"tooyoung": "Sorry! You are too young for this! Please, return when you'll turn at least 14! Thank you and good bye!",
"toomuch": "You have loaded more that 5 pictures. I'll not upload that. Going to the next step...",
"twogenders": "There are only two genders! Please, choose your biological one.",
"didntunderstand": "I cannot understand you answer. Please, read my question again and answer!"
}
}

2254
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

28
package.json Normal file
View File

@ -0,0 +1,28 @@
{
"name": "daivinchik",
"version": "0.0.1",
"description": "Bot for Matrix network for new meetings",
"scripts": {
"start": "node src/index.js",
"dev": "nodemon src/index.js",
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
"type": "git",
"url": "https://git.foxarmy.org/leca/daivinchik"
},
"keywords": [
"bot",
"matrix",
"meet",
"social"
],
"author": "leca",
"license": "GPL-3.0-or-later",
"dependencies": {
"matrix-bot-sdk": "^0.7.1",
"nodemon": "^3.1.4",
"pg": "^8.12.0"
},
"type": "module"
}

14
scheme.psql Normal file
View File

@ -0,0 +1,14 @@
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,
gender BOOLEAN, -- 1 means male
interest VARCHAR(6), -- male, female or both
description VARCHAR(512) DEFAULT NULL,
country VARCHAR(64) DEFAULT NULL,
city VARCHAR(64) DEFAULT NULL,
current_action VARCHAR(16) DEFAULT NULL,
currently_viewing VARCHAR(64) --link to "room_id"
);

192
src/index.js Normal file
View File

@ -0,0 +1,192 @@
import {
EncryptionAlgorithm,
MatrixClient,
MessageEvent,
RustSdkCryptoStorageProvider,
SimpleFsStorageProvider,
} from "matrix-bot-sdk";
import { StoreType } from "@matrix-org/matrix-sdk-crypto-nodejs";
import fs from 'fs';
import {
logError,
logInfo,
readConfig,
readMessages
} from './utils.js';
import {
getClient
} from './initDb.js'
const db = await getClient()
const config = readConfig()
const messages = readMessages()
const homeserverUrl = config.homeserverURL;
const accessToken = config.token;
const crypto = new RustSdkCryptoStorageProvider("./bot_data/encryption_bot_sled", StoreType.Sled);
const storage = new SimpleFsStorageProvider("./bot_data/bot.json");
const client = new MatrixClient(homeserverUrl, accessToken, storage, crypto);
// client.on("room.message", async (roomId, event) => {
// let current_action = (await db.query("SELECT current_action FROM users WHERE room_id = $1", [roomId])).rows[0];
// console.log(state)
// if (body?.startsWith("!hello")) {
// await client.sendText(roomId, messages.welcome);
// await client.sendText(roomId, messages.setup.country);
// }
// });
client.on("room.message", async (roomId, event) => {
try {
if (event.content?.msgtype !== 'm.text' || event.content?.msgtype !== 'm.image') return;
if (event.sender === await client.getUserId()) return;
let current_action = (await db.query('SELECT current_action FROM users WHERE room_id = $1', [roomId])).rows[0];
let answer = event.content.body;
console.log(answer)
switch (current_action) {
case "country":
if (answer.length > 64) {
await client.sendText(roomId, messages.errors.toobig);
return;
}
await db.query("UPDATE users SET country = $1 WHERE room_id = $2", [answer, roomId]);
await client.sendText(roomId, `Set your country setting to "${answer}".`);
await client.sendText(roomId, messages.setup.city); //next question
await db.query("UPDATE users SET current_action = 'city' WHERE room_id = $1", [roomId]);
break;
case "city":
if (answer.length > 64) {
await client.sendText(roomId, messages.errors.toobig);
return;
}
await db.query("UPDATE users SET city = $1 WHERE room_id = $2", [answer, roomId]);
await client.sendText(roomId, `Set your city setting to "${answer}".`);
await client.sendText(roomId, messages.setup.name); //next question
await db.query("UPDATE users SET current_action = 'name' WHERE room_id = $1", [roomId]);
break;
case "name":
if (answer.length > 32) {
await client.sendText(roomId, messages.errors.toobig);
return;
}
await db.query("UPDATE users SET name = $1 WHERE room_id = $2", [answer, roomId]);
await client.sendText(roomId, `Set your name setting to "${answer}".`);
await client.sendText(roomId, messages.setup.age); //next question
await db.query("UPDATE users SET current_action = 'age' WHERE room_id = $1", [roomId]);
break;
case "age":
if (answer < 14) {
await client.sendText(roomId, messages.errors.tooyoung);
await client.leaveRoom(roomId)
return;
}
await db.query("UPDATE users SET age = $1 WHERE room_id = $2", [answer, roomId]);
await client.sendText(roomId, `Set your age setting to "${answer}".`);
await client.sendText(roomId, messages.setup.gender); //next question
await db.query("UPDATE users SET current_action = 'gender' WHERE room_id = $1", [roomId]);
break;
case "gender":
if (answer.toLowerCase() != "male" || answer.toLowerCase() != "female") {
await client.sendText(roomId, messages.errors.twogenders);
return;
}
await db.query("UPDATE users SET gender = $1 WHERE room_id = $2", [answer.toLowerCase() == "male" ? true : false, roomId]);
await client.sendText(roomId, `Set your gender setting to "${answer}".`);
await client.sendText(roomId, messages.setup.interest); //next question
await db.query("UPDATE users SET current_action = 'interest' WHERE room_id = $1", [roomId]);
break;
case "interest":
if (answer.toLowerCase() != "male" || answer.toLowerCase() != "female" || answer.toLowerCase() != "both") {
await client.sendText(roomId, messages.errors.didntunderstand);
return;
}
await db.query("UPDATE users SET interest = $1 WHERE room_id = $2", [answer.toLowerCase(), roomId]);
await client.sendText(roomId, `Set your interest setting to "${answer}".`);
await client.sendText(roomId, messages.setup.description); //next question
await db.query("UPDATE users SET current_action = 'description' WHERE room_id = $1", [roomId]);
break;
case "description":
if (answer.length > 512) {
await client.sendText(roomId, messages.errors.toobig);
return;
}
await db.query("UPDATE users SET description = $1 WHERE room_id = $2", [answer, roomId]);
await client.sendText(roomId, `Set your description setting to "${answer}".`);
await client.sendText(roomId, messages.setup.pictures); //next question
await db.query("UPDATE users SET current_action = 'pictures' WHERE room_id = $1", [roomId]);
break;
case "pictures":
if (event.content?.msgtype !== 'm.image') {
await client.sendText(roomId, messages.setup.done);
await db.query("UPDATE users SET current_action = 'view_profiles' WHERE room_id = $1", [roomId]);
} else {
let pictures_count = (await db.query("SELECT array_length(pictures_urls) FROM users WHERE room_id = $1"), [roomId]).rows[0];
if (pictures_count == 5) {
await client.sendText(roomId, messages.error.toomuch);
await db.query("UPDATE users SET current_action = 'view_profiles' WHERE room_id = $1", [roomId]);
} else {
const message = new MessageEvent(event);
const fileEvent = new MessageEvent(message.raw);
const decrypted = await client.crypto.decryptMedia(fileEvent.content.file);
const encrypted = await client.crypto.encryptMedia(Buffer.from(decrypted));
const mxc = await client.uploadContent(encrypted.buffer)
logInfo(`New media: ${mxc}`)
let pictures_count = (await db.query("UPDATE users SET pictures_urls = array_append(pictures_urls, $1) WHERE room_id = $2 RETURNING array_length(pictures_urls)", [mxc, roomId])).rows[0];
if (pictures_count < 5) {
await client.sendText(roomId, messages.setup.more);
} else {
await client.sendText(roomId, messages.setup.enough);
await db.query("UPDATE users SET current_action = 'view_profiles' WHERE room_id = $1", [roomId]);
}
}
}
}
} catch (e) {
logError(e)
}
});
client.on("room.invite", async (roomId) => {
try {
await client.joinRoom(roomId);
let members = await client.getAllRoomMembers(roomId)
let isDM = members.length == 2 ? true : false;
if (!isDM) {
client.sendText(roomId, messages.errors.notadm);
client.leaveRoom(roomId)
}
logInfo(`Bot has joined a room with ID ${roomId}`)
let mx_id = members[0].event.sender
console.log(`Sender is ${mx_id}`)
await db.query("INSERT INTO users(mx_id, room_id, current_action) VALUES ($1, $2, $3)", [mx_id, roomId, "country"])
await client.sendText(roomId, messages.welcome);
await client.sendText(roomId, messages.setup.country);
} catch (e) {
logError(e)
}
});
client.start().then(() => logInfo("Bot started!"));

22
src/initDb.js Normal file
View File

@ -0,0 +1,22 @@
import pg from 'pg'
import fs from 'fs'
const { Client } = pg
export const getClient = async () => {
const client = new Client({
user: process.env.POSTGRES_USER,
password: process.env.POSTGRES_PASSWORD,
host: "postgresql",
port: 5432,
database: process.env.POSTGRES_DB
});
await client.connect()
console.log(fs.readFileSync('./scheme.psql').toString())
await client.query(fs.readFileSync('./scheme.psql').toString())
return client
}

42
src/utils.js Normal file
View File

@ -0,0 +1,42 @@
import fs from 'fs'
const configPath = './config.json'
const messagesPath = './messages.json'
export const logError = (message) => {
let time = new Date
console.error(`[${time.toLocaleString()}] [LOG] [E] ${message}`)
}
export const logInfo = (message) => {
let time = new Date
console.log(`[${time.toLocaleString()}] [LOG] [I] ${message}`)
}
export const readConfig = () => {
if (!fs.existsSync(configPath)) {
fs.writeFileSync(configPath,
`{
"homeserverURL": "https://matrix.org/",
"token": "Super secret token! Do not show to anyone! Even your mum! ;)"
}`
);
logError('[LOG] [E] Config file was not found. I have created a template, please, edit it and restart a bot.')
process.exit(-1)
}
return JSON.parse(fs.readFileSync(configPath)
)
}
export const readMessages = () => {
if (!fs.existsSync(messagesPath)) {
logError("No 'messages.json' file found. Please, ensure that you are up to date by syncing using 'git pull' command.")
process.exit(-1)
}
return JSON.parse(fs.readFileSync(messagesPath))
}