initial commit

This commit is contained in:
qugalet 2023-07-09 23:53:49 +03:00
commit a1806cbac9
17 changed files with 3155 additions and 0 deletions

7
.dockerignore Normal file
View file

@ -0,0 +1,7 @@
node_modules/
dist/
config.json
domains.txt
nicknames.txt
matrix_db.json
.env

1
.env.example Normal file
View file

@ -0,0 +1 @@
DATABASE_URL="postgresql://uabot@postgres:5432/uabot"

25
.eslintrc.js Normal file
View file

@ -0,0 +1,25 @@
module.exports = {
env: {
es2021: true,
node: true
},
extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended'],
parser: '@typescript-eslint/parser',
parserOptions: {
ecmaVersion: 'latest',
sourceType: 'module'
},
plugins: ['@typescript-eslint', 'prettier'],
rules: {
yoda: 'error',
eqeqeq: 'error',
complexity: 'error',
'prefer-const': 'error',
'prefer-template': 'error',
'object-shorthand': 'warn',
'prettier/prettier': 'warn',
'prefer-destructuring': 'warn',
'prefer-arrow-callback': 'error',
'@typescript-eslint/no-namespace': 'off'
}
};

7
.gitignore vendored Normal file
View file

@ -0,0 +1,7 @@
node_modules/
dist/
config.json
domains.txt
nicknames.txt
matrix_db.json
.env

12
.prettierrc Normal file
View file

@ -0,0 +1,12 @@
{
"tabWidth": 2,
"useTabs": false,
"singleQuote": true,
"arrowParens": "avoid",
"bracketSpacing": true,
"semi": true,
"printWidth": 100,
"trailingComma": "none",
"endOfLine": "lf"
}

15
Dockerfile Normal file
View file

@ -0,0 +1,15 @@
FROM node:18-bookworm
WORKDIR /app
COPY package.json .
RUN npm i
COPY . .
RUN npm run generate
RUN npm run build
ENTRYPOINT [ "./entrypoint.sh" ]

58
README.md Normal file
View file

@ -0,0 +1,58 @@
## UA-Bot
**UA-Bot** - Bot for Mastodon that reboosts message if mentioned.
### Features
* Moderation - Send to Matrix room before reblog
* Reply or broken thread detection (not fully tested)
* Block instances and nicknames by regex
### Install
First, install Postgres.
Second, clone repo.
Copy `.env.example` to `.env` and edit with your data:
```sh
DATABASE_URL="postgresql://uabot@postgres:5432/uabot"
```
Also copy `config.json.example` to `config.json` with your data:
* `mastodonClientId` - Mastodon Client Id
* `mastodonAccessToken` - Mastodon Access Token
* `mastodonHomeserverURL` - Mastodon Homeserver URL
* `matrixHomeserverURL` - Matrix Homeserver URL bot for moderation
* `matrixAccessToken` - Matrix Access Token for Matrix bot for moderation
* `matrixRoomId` - Matrix Room Id for moderation
Also create `nicknames.txt` and `domains.txt` (regex files):
* `nicknames.txt` - block by username or display name
* `domains.txt` - block by domain name
Tip: `#` in the beginning of the regex file means that this line will be ignored. You can use it as comment
Now you can install using **Dockerimage** or **manually**:
#### Docker
Build `Dockerfile` and run
Also, mount `config.json`, `domains.txt` and `nicknames.txt` to the `/app`
### Manually
Install Node >= 18
Next, install packages, generate Prisma package for the project and build it:
```sh
npm i # alias for npm install
npm run generate
npm run build
```
Migrate database and run:
```sh
npm run migrate
npm start
```

8
config.json.example Normal file
View file

@ -0,0 +1,8 @@
{
"pleromaClientId": "",
"pleromaAccessToken": "",
"pleromaHomeserverURL": "",
"matrixHomeserverURL": "",
"matrixAccessToken": "",
"matrixRoomId": ""
}

13
docker-compose.yml Normal file
View file

@ -0,0 +1,13 @@
version: '3'
services:
bot:
build: .
env_file:
- ./.env
volumes:
- ./config.json:/app/config.json
- ./nicknames.txt:/app/nicknames.txt
- ./domains.txt:/app/domains.txt
extra_hosts:
- host.docker.internal:host-gateway

3
entrypoint.sh Executable file
View file

@ -0,0 +1,3 @@
#!/bin/sh
npm run migrate
npm run start

30
package.json Normal file
View file

@ -0,0 +1,30 @@
{
"name": "uagroupbot",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"watch": "tsc -w",
"build": "tsc",
"start": "node ./dist",
"generate": "prisma generate",
"migrate": "prisma migrate deploy"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"@tsconfig/node18": "^18.2.0",
"eslint": "^8.44.0",
"nodemon": "^2.0.22",
"npm-run-all": "^4.1.5",
"prettier": "^3.0.0",
"typescript": "^5.1.6"
},
"dependencies": {
"@prisma/client": "4.14.1",
"masto": "^5.11.3",
"matrix-bot-sdk": "^0.6.6",
"prisma": "^4.16.2"
}
}

2822
pnpm-lock.yaml Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,10 @@
-- CreateTable
CREATE TABLE "Post" (
"postId" TEXT NOT NULL,
"matrixId" TEXT NOT NULL,
CONSTRAINT "Post_pkey" PRIMARY KEY ("postId")
);
-- CreateIndex
CREATE UNIQUE INDEX "Post_matrixId_key" ON "Post"("matrixId");

View file

@ -0,0 +1,3 @@
# Please do not edit this file manually
# It should be added in your version-control system (i.e. Git)
provider = "postgresql"

16
prisma/schema.prisma Normal file
View file

@ -0,0 +1,16 @@
// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model Post {
postId String @id
matrixId String @unique
}

115
src/index.ts Normal file
View file

@ -0,0 +1,115 @@
import { MatrixClient, MessageEvent, SimpleFsStorageProvider } from 'matrix-bot-sdk';
import { PrismaClient } from '@prisma/client';
import { readFile } from 'fs/promises';
import { join } from 'path';
function parseAsyncRegexFile(file: Buffer): RegExp[] {
const data = file.toString();
return data
.split('\n')
.filter(regex => !regex.trim().startsWith('#'))
.map(regex => new RegExp(regex));
}
async function main() {
const config = JSON.parse((await readFile(join(__dirname, '..', 'config.json'))).toString());
console.log(config);
const mastodon = await (
await import('masto')
).login({
url: config.mastodonHomeserverURL,
accessToken: config.mastodonAccessToken,
disableVersionCheck: true
});
const matrix = new MatrixClient(
config.matrixHomeserverURL,
config.matrixAccessToken,
new SimpleFsStorageProvider('matrix_db.json')
);
const nicknameRegexes = parseAsyncRegexFile(await readFile(join(__dirname, '..', 'domains.txt')));
const domainRegexes = parseAsyncRegexFile(await readFile(join(__dirname, '..', 'nicknames.txt')));
const db = new PrismaClient();
const ws = await mastodon.v1.stream.streamUser();
ws.on('notification', async notification => {
console.log('tests');
if (
nicknameRegexes.some(regex => regex.test(notification.account.displayName)) ||
nicknameRegexes.some(regex => regex.test(notification.account.acct.split('@').at(0) || '')) ||
domainRegexes.some(regex => regex.test(notification.account.acct.split('@').at(-1) || ''))
) {
await mastodon.v1.accounts.block(notification.account.id);
await matrix.sendText(
config.matrixRoomId,
`Юзер ${notification.account.acct} був заблокований автоматично`
);
}
if (
notification.type === 'mention' &&
!notification.status?.inReplyToId &&
!notification.status?.inReplyToAccountId &&
notification.status?.visibility === 'public'
) {
const matrixId = await matrix.sendText(
config.matrixRoomId,
`Одобряємо?\n${notification.status.url}\nт - Так\nн - Ні`
);
await db.post.create({
data: {
postId: notification.status.id,
matrixId
}
});
}
if (notification.type === 'follow') {
await mastodon.v1.statuses.create({
visibility: 'public',
status: `Вітаємо у нашій спільноті! @${notification.account.acct}`
});
console.log('follow message');
}
});
matrix.on('room.message', async (roomId, event) => {
if (
roomId === config.matrixRoomId &&
event['content'] &&
['т', 'н'].includes(event['content']['body'].split('\n').at(-1).trim()) &&
event['content']['m.relates_to']['m.in_reply_to']
) {
const post = await db.post.findUnique({
where: {
matrixId: event['content']['m.relates_to']['m.in_reply_to']['event_id']
}
});
if (post && event['content']['body'].split('\n').at(-1) === 'т') {
console.log('test');
await mastodon.v1.statuses.reblog(post.postId);
await matrix.replyText(
config.matrixRoomId,
event['content']['m.relates_to']['m.in_reply_to']['event_id'],
'Пост успішно опубліковано'
);
}
if (post && event['content']['body'].split('\n').at(-1) === 'н') {
await matrix.redactEvent(
config.matrixRoomId,
event['content']['m.relates_to']['event_id'],
'Не одобрили :('
);
await db.post.delete({
where: {
matrixId: event['content']['m.relates_to']['m.in_reply_to']['event_id']
}
});
await matrix.replyText(
config.matrixRoomId,
event['content']['m.relates_to']['m.in_reply_to']['event_id'],
'Пост відхилено'
);
}
}
});
await db.$connect();
await matrix.start();
}
main().then(() => console.log('everything started!'));

10
tsconfig.json Normal file
View file

@ -0,0 +1,10 @@
{
"extends": "@tsconfig/node18/tsconfig.json",
"compilerOptions": {
"outDir": "dist",
"esModuleInterop": true,
"resolveJsonModule": true,
"target": "esnext"
},
"esModuleInterop": true
}