initial commit
This commit is contained in:
commit
a1806cbac9
17 changed files with 3155 additions and 0 deletions
7
.dockerignore
Normal file
7
.dockerignore
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
node_modules/
|
||||||
|
dist/
|
||||||
|
config.json
|
||||||
|
domains.txt
|
||||||
|
nicknames.txt
|
||||||
|
matrix_db.json
|
||||||
|
.env
|
1
.env.example
Normal file
1
.env.example
Normal file
|
@ -0,0 +1 @@
|
||||||
|
DATABASE_URL="postgresql://uabot@postgres:5432/uabot"
|
25
.eslintrc.js
Normal file
25
.eslintrc.js
Normal 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
7
.gitignore
vendored
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
node_modules/
|
||||||
|
dist/
|
||||||
|
config.json
|
||||||
|
domains.txt
|
||||||
|
nicknames.txt
|
||||||
|
matrix_db.json
|
||||||
|
.env
|
12
.prettierrc
Normal file
12
.prettierrc
Normal 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
15
Dockerfile
Normal 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
58
README.md
Normal 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
8
config.json.example
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
{
|
||||||
|
"pleromaClientId": "",
|
||||||
|
"pleromaAccessToken": "",
|
||||||
|
"pleromaHomeserverURL": "",
|
||||||
|
"matrixHomeserverURL": "",
|
||||||
|
"matrixAccessToken": "",
|
||||||
|
"matrixRoomId": ""
|
||||||
|
}
|
13
docker-compose.yml
Normal file
13
docker-compose.yml
Normal 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
3
entrypoint.sh
Executable file
|
@ -0,0 +1,3 @@
|
||||||
|
#!/bin/sh
|
||||||
|
npm run migrate
|
||||||
|
npm run start
|
30
package.json
Normal file
30
package.json
Normal 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
2822
pnpm-lock.yaml
Normal file
File diff suppressed because it is too large
Load diff
10
prisma/migrations/20230708200924_initial/migration.sql
Normal file
10
prisma/migrations/20230708200924_initial/migration.sql
Normal 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");
|
3
prisma/migrations/migration_lock.toml
Normal file
3
prisma/migrations/migration_lock.toml
Normal 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
16
prisma/schema.prisma
Normal 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
115
src/index.ts
Normal 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
10
tsconfig.json
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
{
|
||||||
|
"extends": "@tsconfig/node18/tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "dist",
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"target": "esnext"
|
||||||
|
},
|
||||||
|
"esModuleInterop": true
|
||||||
|
}
|
Loading…
Reference in a new issue