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