mirror of
https://github.com/house-of-vanity/desubot.git
synced 2025-07-07 20:54:06 +00:00
Compare commits
49 Commits
Author | SHA1 | Date | |
---|---|---|---|
739109e5f7 | |||
4ac7920815 | |||
47f1ab9348 | |||
28cdf4be2e | |||
0bcb99089b | |||
0bba91486e | |||
207717491c | |||
f185c31a17 | |||
aeacc15439 | |||
ccf54bc279 | |||
e07f173b7c | |||
bcd869273d | |||
1ee12fa0c0 | |||
76dab182cf | |||
bacdf559c4 | |||
82a81fffb6 | |||
392669703d | |||
2fb5b03fbf | |||
55a7dd6a67 | |||
ff6e04988e | |||
7c067065bd | |||
175f056add | |||
20f366572b | |||
e5079fa584 | |||
fdc52b7198 | |||
789b918bab | |||
66a93e85b8 | |||
e13a2688ab | |||
04220703a3 | |||
9518ffd69b | |||
5a6cb37ebb | |||
6c2837a76f | |||
d97eaf4284 | |||
e7e0c6923e | |||
382cc56492 | |||
6f48f116c9 | |||
282efe5be4 | |||
4175fe9029 | |||
f1dc1d0897 | |||
d5879a82b6 | |||
456c887a53 | |||
d8b37b32df | |||
47c68ee432 | |||
da53927288 | |||
ac2be9929a | |||
7432ce6398 | |||
049a3c4987 | |||
0831e3f503 | |||
a0f4c40be0 |
96
.github/workflows/build-push.yml
vendored
96
.github/workflows/build-push.yml
vendored
@ -11,38 +11,65 @@ jobs:
|
||||
build-linux:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v4
|
||||
- name: Build
|
||||
run: cargo build --verbose --release
|
||||
- name: Upload Linux binary
|
||||
uses: actions/upload-artifact@v1
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: desubot
|
||||
path: ./target/release/desubot
|
||||
|
||||
build-windows:
|
||||
runs-on: windows-latest
|
||||
build-push-docker:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Build
|
||||
run: cargo build --verbose --release
|
||||
- name: Upload Windows binary
|
||||
uses: actions/upload-artifact@v1
|
||||
- name: Branch name
|
||||
id: branch_name
|
||||
run: |
|
||||
echo ::set-output name=SOURCE_NAME::${GITHUB_REF#refs/*/}
|
||||
echo ::set-output name=SOURCE_BRANCH::${GITHUB_REF#refs/heads/}
|
||||
echo ::set-output name=SOURCE_TAG::${GITHUB_REF#refs/tags/}
|
||||
-
|
||||
name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
-
|
||||
name: Login to DockerHub
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
name: desubot.exe
|
||||
path: ./target/release/desubot.exe
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
-
|
||||
name: Build and push
|
||||
id: docker_build_latest
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
push: true
|
||||
tags: ultradesu/desubot:latest
|
||||
-
|
||||
name: Build and push
|
||||
id: docker_build_tag
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
push: true
|
||||
tags: ultradesu/desubot:${{ steps.branch_name.outputs.SOURCE_TAG }}
|
||||
|
||||
publish:
|
||||
name: Publish release
|
||||
needs: [build-windows, build-linux]
|
||||
needs: [build-linux]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
# - name: Checkout
|
||||
# uses: actions/checkout@v4
|
||||
- name: Get the version (git tag)
|
||||
id: get_version
|
||||
run: |
|
||||
echo ${GITHUB_REF/refs\/tags\/v/}
|
||||
echo ::set-output name=VERSION::${GITHUB_REF/refs\/tags\/v/}
|
||||
echo ::set-output name=FULL_TAG::${GITHUB_REF/refs\/tags\//}
|
||||
|
||||
- name: Get the repo data (git tag)
|
||||
id: get_repo_data
|
||||
run: |
|
||||
@ -50,40 +77,25 @@ jobs:
|
||||
echo ::set-output name=AUTHOR::$(echo "$GITHUB_REPOSITORY" | awk -F / '{print $1}')
|
||||
echo ::set-output name=REPO_NAME::$(echo "$GITHUB_REPOSITORY" | awk -F / '{print $2}')
|
||||
shell: bash
|
||||
- name: Create Release
|
||||
id: create_release
|
||||
uses: actions/create-release@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
tag_name: ${{ github.ref }}
|
||||
release_name: Release ${{ github.ref }}
|
||||
draft: false
|
||||
prerelease: false
|
||||
- name: Upload binary assets
|
||||
|
||||
- name: Prepare release downloading
|
||||
run: |
|
||||
mkdir artifacts
|
||||
|
||||
- name: Download Linux binary
|
||||
uses: actions/download-artifact@v1
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: desubot
|
||||
path: ./artifacts/
|
||||
- name: Download Windows binary
|
||||
uses: actions/download-artifact@v1
|
||||
with:
|
||||
name: desubot.exe
|
||||
path: ./artifacts/
|
||||
- name: Upload binary assets
|
||||
run: |
|
||||
wget https://github.com/aktau/github-release/releases/download/v0.7.2/linux-amd64-github-release.tar.bz2
|
||||
tar xjf linux-amd64-github-release.tar.bz2
|
||||
export GITHUB_TOKEN=${{ secrets.GITHUB_TOKEN }}
|
||||
for artifact in ./artifacts/*; do
|
||||
./bin/linux/amd64/github-release upload \
|
||||
-u ${{ steps.get_repo_data.outputs.AUTHOR }} \
|
||||
-r ${{ steps.get_repo_data.outputs.REPO_NAME }} \
|
||||
--tag ${{ steps.get_version.outputs.FULL_TAG }} \
|
||||
--name ${artifact} \
|
||||
--file ${artifact}
|
||||
done
|
||||
|
||||
- name: Build
|
||||
run: echo ${{ github.sha }} > Release.txt
|
||||
|
||||
- name: Test
|
||||
run: cat Release.txt
|
||||
|
||||
- name: Release
|
||||
uses: softprops/action-gh-release@v2
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
with:
|
||||
files: './artifacts/*'
|
||||
|
4
.gitignore
vendored
4
.gitignore
vendored
@ -6,4 +6,6 @@ memory.sqlite3
|
||||
/video
|
||||
/voice
|
||||
/.idea
|
||||
Cargo.lock
|
||||
Cargo.lock
|
||||
k8s/k8s-deploy.yaml
|
||||
/identifier.sqlite
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "desubot"
|
||||
version = "0.5.5"
|
||||
version = "0.5.12"
|
||||
authors = ["AB <ab@hexor.ru>"]
|
||||
edition = "2018"
|
||||
|
||||
@ -12,7 +12,8 @@ tokio = { version = "0.2", features = ["full"]}
|
||||
tracing = "0.1.9"
|
||||
tracing-futures = "0.2"
|
||||
multipart = { version = "0.16", default-features = false, features = ["client"] }
|
||||
telegram-bot = "0.8.0"
|
||||
#telegram-bot = "0.8.0"
|
||||
telegram-bot = { git = "https://github.com/ayrat555/telegram-bot", branch = "ayrat555/api-fixes-10" }
|
||||
silicon = "0.4.0"
|
||||
hyper = "0.13"
|
||||
hyper-tls = { version = "0.4", optional = true }
|
||||
@ -29,7 +30,7 @@ log = { version = "^0.4.5", features = ["std"] }
|
||||
subprocess = "0.2.6"
|
||||
serde_json = "1.0"
|
||||
markov = "1.1.0"
|
||||
rand = "0.7.3"
|
||||
rand = "0.8.5"
|
||||
mystem = "^0.2"
|
||||
#mystem = { path = "../mystem-rs" }
|
||||
async-trait = "0.1.42"
|
||||
|
14
Dockerfile
Normal file
14
Dockerfile
Normal file
@ -0,0 +1,14 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
|
||||
FROM rust:bookworm AS builder
|
||||
WORKDIR /desubot
|
||||
ADD ./ /desubot/
|
||||
RUN cargo build --release
|
||||
|
||||
FROM debian:bookworm
|
||||
WORKDIR /storage
|
||||
COPY --from=builder /desubot/target/release/desubot /usr/bin/
|
||||
COPY mystem /usr/bin/
|
||||
RUN apt update && apt install -y fontconfig openssl ca-certificates && rm -rf /var/lib/apt/lists/*
|
||||
ENTRYPOINT desubot
|
||||
|
14
README
14
README
@ -1,14 +0,0 @@
|
||||
Desubot
|
||||
Telegram bot with light group statistic and heavy spy features.
|
||||
|
||||
== Features ==
|
||||
* Collect all the messages sent to group.
|
||||
* Collect all the media sent to group including voice, stickers, video, video notes, documents.
|
||||
* /here command to mention all members.
|
||||
* Alongside with saving whole message bot perform blacklist filter and stemming for every word (only Russian). "Красивую собаку мыли негры" -> "красивый собака мыть негр"
|
||||
* Generate sentences using Markov Chains trained on history with /markov_all.
|
||||
* Syntax highlighting for CODE exported to image.
|
||||
|
||||
== Important ==
|
||||
* Desubot uses MyStem by Yandex for word stemming and assume that mystem binary is available in PATH.
|
||||
On Windows it may be placed on working directory. Both Linux and Windows mystem binary is in repo.
|
34
README.md
Normal file
34
README.md
Normal file
@ -0,0 +1,34 @@
|
||||
[](https://app.fossa.com/projects/git%2Bgithub.com%2Fhouse-of-vanity%2Fdesubot?ref=badge_shield)
|
||||
|
||||
# Desubot Telegram Bot
|
||||
|
||||
**Desubot** is a Telegram bot with light group statistics and powerful spy features.
|
||||
|
||||
## Features
|
||||
|
||||
- **Collect all messages**: The bot collects all messages sent to the group.
|
||||
- **Collect all media**: The bot saves all media sent to the group, including voice messages, stickers, videos, video notes, and documents.
|
||||
- **/here command**: Mention all group members.
|
||||
- **Blacklist filter and stemming**: The bot saves the entire message, performs blacklist filtering, and stems every word (Russian only). For example, "Красивую собаку мыли негры" -> "красивый собака мыть негр".
|
||||
- **Markov Chain sentence generation**: The bot generates sentences using Markov Chains trained on the history with the `/markov_all` command.
|
||||
- **Syntax highlighting for CODE**: Export code with syntax highlighting to an image.
|
||||
|
||||
## Important
|
||||
|
||||
- **MyStem**: Desubot uses MyStem by Yandex for word stemming and assumes that the `mystem` binary is available in the PATH.
|
||||
- **Ubuntu dependencies**: The following packages are required:
|
||||
|
||||
```bash
|
||||
libssl-dev libsqlite3-dev cmake libfreetype-dev pkg-config
|
||||
|
||||
|
||||
[Docker Hub](https://hub.docker.com/repository/docker/ultradesu/desubot/general)
|
||||
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
## License
|
||||
[](https://app.fossa.com/projects/git%2Bgithub.com%2Fhouse-of-vanity%2Fdesubot?ref=badge_large)
|
||||
|
@ -1,5 +1,5 @@
|
||||
#[allow(dead_code)]
|
||||
static CODE_HELP: &str = "<b>Code highlighter</b>
|
||||
pub(crate) static CODE_HELP: &str = "<b>Code highlighter</b>
|
||||
|
||||
<i>Usage</i>
|
||||
<pre>/code
|
||||
@ -7,7 +7,7 @@ static CODE_HELP: &str = "<b>Code highlighter</b>
|
||||
#<lang - JS by default> #<theme - Dracula by default></pre>
|
||||
|
||||
Language may be defined by both name and extension - Rust, rs...
|
||||
Max lines - 80
|
||||
Max length - 4000
|
||||
|
||||
List of themes:
|
||||
1337
|
||||
|
431
assets/stop-words.txt
Normal file
431
assets/stop-words.txt
Normal file
@ -0,0 +1,431 @@
|
||||
а
|
||||
в
|
||||
г
|
||||
е
|
||||
ж
|
||||
и
|
||||
к
|
||||
м
|
||||
о
|
||||
с
|
||||
т
|
||||
у
|
||||
я
|
||||
чо
|
||||
че
|
||||
ща
|
||||
ваще
|
||||
бы
|
||||
stat
|
||||
вообще
|
||||
ThreadTopBot
|
||||
/stat
|
||||
во
|
||||
вы
|
||||
да
|
||||
до
|
||||
ее
|
||||
ей
|
||||
ею
|
||||
её
|
||||
же
|
||||
за
|
||||
из
|
||||
им
|
||||
их
|
||||
ли
|
||||
мы
|
||||
на
|
||||
не
|
||||
ни
|
||||
но
|
||||
ну
|
||||
нх
|
||||
об
|
||||
он
|
||||
от
|
||||
по
|
||||
со
|
||||
та
|
||||
те
|
||||
то
|
||||
ту
|
||||
ты
|
||||
уж
|
||||
без
|
||||
был
|
||||
вам
|
||||
вас
|
||||
ваш
|
||||
вон
|
||||
вот
|
||||
все
|
||||
всю
|
||||
вся
|
||||
всё
|
||||
где
|
||||
год
|
||||
два
|
||||
две
|
||||
дел
|
||||
для
|
||||
его
|
||||
ему
|
||||
еще
|
||||
ещё
|
||||
или
|
||||
ими
|
||||
имя
|
||||
как
|
||||
кем
|
||||
ком
|
||||
кто
|
||||
лет
|
||||
мне
|
||||
мог
|
||||
мож
|
||||
мои
|
||||
мой
|
||||
мор
|
||||
моя
|
||||
моё
|
||||
над
|
||||
нам
|
||||
нас
|
||||
наш
|
||||
нее
|
||||
ней
|
||||
нем
|
||||
нет
|
||||
нею
|
||||
неё
|
||||
них
|
||||
оба
|
||||
она
|
||||
они
|
||||
оно
|
||||
под
|
||||
пор
|
||||
при
|
||||
про
|
||||
раз
|
||||
сам
|
||||
сих
|
||||
так
|
||||
там
|
||||
тем
|
||||
тех
|
||||
том
|
||||
тот
|
||||
тою
|
||||
три
|
||||
тут
|
||||
уже
|
||||
чем
|
||||
что
|
||||
эта
|
||||
эти
|
||||
это
|
||||
эту
|
||||
алло
|
||||
буду
|
||||
будь
|
||||
бывь
|
||||
была
|
||||
были
|
||||
было
|
||||
быть
|
||||
вами
|
||||
ваша
|
||||
ваше
|
||||
ваши
|
||||
ведь
|
||||
весь
|
||||
вниз
|
||||
всем
|
||||
всех
|
||||
всею
|
||||
года
|
||||
году
|
||||
даже
|
||||
двух
|
||||
день
|
||||
если
|
||||
есть
|
||||
зато
|
||||
кого
|
||||
кому
|
||||
куда
|
||||
лишь
|
||||
люди
|
||||
мало
|
||||
меля
|
||||
меня
|
||||
мимо
|
||||
мира
|
||||
мной
|
||||
мною
|
||||
мочь
|
||||
надо
|
||||
нами
|
||||
наша
|
||||
наше
|
||||
наши
|
||||
него
|
||||
нему
|
||||
ниже
|
||||
ними
|
||||
один
|
||||
пока
|
||||
пора
|
||||
пять
|
||||
рано
|
||||
сама
|
||||
сами
|
||||
само
|
||||
саму
|
||||
свое
|
||||
свои
|
||||
свою
|
||||
себе
|
||||
себя
|
||||
семь
|
||||
стал
|
||||
суть
|
||||
твой
|
||||
твоя
|
||||
твоё
|
||||
тебе
|
||||
тебя
|
||||
теми
|
||||
того
|
||||
тоже
|
||||
тому
|
||||
туда
|
||||
хоть
|
||||
хотя
|
||||
чаще
|
||||
чего
|
||||
чему
|
||||
чтоб
|
||||
чуть
|
||||
этим
|
||||
этих
|
||||
этой
|
||||
этом
|
||||
этот
|
||||
более
|
||||
будем
|
||||
будет
|
||||
будто
|
||||
будут
|
||||
вверх
|
||||
вдали
|
||||
вдруг
|
||||
везде
|
||||
внизу
|
||||
время
|
||||
всего
|
||||
всеми
|
||||
всему
|
||||
всюду
|
||||
давно
|
||||
даром
|
||||
долго
|
||||
друго
|
||||
занят
|
||||
затем
|
||||
зачем
|
||||
здесь
|
||||
иметь
|
||||
какая
|
||||
какой
|
||||
когда
|
||||
кроме
|
||||
лучше
|
||||
между
|
||||
менее
|
||||
много
|
||||
могут
|
||||
может
|
||||
можно
|
||||
можхо
|
||||
назад
|
||||
низко
|
||||
нужно
|
||||
одной
|
||||
около
|
||||
опять
|
||||
очень
|
||||
перед
|
||||
позже
|
||||
после
|
||||
потом
|
||||
почти
|
||||
пятый
|
||||
разве
|
||||
рядом
|
||||
самим
|
||||
самих
|
||||
самой
|
||||
самом
|
||||
своей
|
||||
своих
|
||||
сеаой
|
||||
снова
|
||||
собой
|
||||
собою
|
||||
такая
|
||||
также
|
||||
такие
|
||||
такое
|
||||
такой
|
||||
тобой
|
||||
тобою
|
||||
тогда
|
||||
тысяч
|
||||
уметь
|
||||
часто
|
||||
через
|
||||
чтобы
|
||||
шесть
|
||||
этими
|
||||
этого
|
||||
этому
|
||||
близко
|
||||
больше
|
||||
будете
|
||||
будешь
|
||||
бывает
|
||||
важная
|
||||
важное
|
||||
важные
|
||||
важный
|
||||
вокруг
|
||||
восемь
|
||||
всегда
|
||||
второй
|
||||
далеко
|
||||
дальше
|
||||
девять
|
||||
десять
|
||||
должно
|
||||
другая
|
||||
другие
|
||||
других
|
||||
другое
|
||||
другой
|
||||
занята
|
||||
занято
|
||||
заняты
|
||||
значит
|
||||
именно
|
||||
иногда
|
||||
каждая
|
||||
каждое
|
||||
каждые
|
||||
каждый
|
||||
кругом
|
||||
меньше
|
||||
начала
|
||||
нельзя
|
||||
нибудь
|
||||
никуда
|
||||
ничего
|
||||
обычно
|
||||
однако
|
||||
одного
|
||||
отсюда
|
||||
первый
|
||||
потому
|
||||
почему
|
||||
просто
|
||||
против
|
||||
раньше
|
||||
самими
|
||||
самого
|
||||
самому
|
||||
своего
|
||||
сейчас
|
||||
сказал
|
||||
совсем
|
||||
теперь
|
||||
только
|
||||
третий
|
||||
хорошо
|
||||
хотеть
|
||||
хочешь
|
||||
четыре
|
||||
шестой
|
||||
восьмой
|
||||
впрочем
|
||||
времени
|
||||
говорил
|
||||
говорит
|
||||
девятый
|
||||
десятый
|
||||
кажется
|
||||
конечно
|
||||
которая
|
||||
которой
|
||||
которые
|
||||
который
|
||||
которых
|
||||
наверху
|
||||
наконец
|
||||
недавно
|
||||
немного
|
||||
нередко
|
||||
никогда
|
||||
однажды
|
||||
посреди
|
||||
сегодня
|
||||
седьмой
|
||||
сказала
|
||||
сказать
|
||||
сколько
|
||||
слишком
|
||||
сначала
|
||||
спасибо
|
||||
двадцать
|
||||
довольно
|
||||
которого
|
||||
наиболее
|
||||
недалеко
|
||||
особенно
|
||||
отовсюду
|
||||
двадцатый
|
||||
миллионов
|
||||
несколько
|
||||
прекрасно
|
||||
процентов
|
||||
четвертый
|
||||
двенадцать
|
||||
непрерывно
|
||||
пожалуйста
|
||||
пятнадцать
|
||||
семнадцать
|
||||
тринадцать
|
||||
двенадцатый
|
||||
одиннадцать
|
||||
пятнадцатый
|
||||
семнадцатый
|
||||
тринадцатый
|
||||
шестнадцать
|
||||
восемнадцать
|
||||
девятнадцать
|
||||
одиннадцатый
|
||||
четырнадцать
|
||||
шестнадцатый
|
||||
восемнадцатый
|
||||
девятнадцатый
|
||||
действительно
|
||||
четырнадцатый
|
||||
многочисленная
|
||||
многочисленное
|
||||
многочисленные
|
||||
многочисленный
|
||||
ага
|
||||
делать
|
||||
писать
|
||||
бот
|
43
k8s/k8s-deploy-example.yaml
Normal file
43
k8s/k8s-deploy-example.yaml
Normal file
@ -0,0 +1,43 @@
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: desubot-api-token
|
||||
data:
|
||||
token: 123.... # Base64 encoded token.
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: StatefulSet
|
||||
metadata:
|
||||
name: desubot
|
||||
spec:
|
||||
serviceName: "desubot"
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: desubot
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: desubot
|
||||
spec:
|
||||
containers:
|
||||
- name: desubot
|
||||
image: ultradesu/desubot:latest
|
||||
volumeMounts:
|
||||
- name: storage
|
||||
mountPath: /storage
|
||||
env:
|
||||
- name: TELEGRAM_BOT_TOKEN
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: desubot-api-token
|
||||
key: token
|
||||
volumeClaimTemplates:
|
||||
- metadata:
|
||||
name: storage
|
||||
spec:
|
||||
accessModes: [ "ReadWriteOnce" ]
|
||||
resources:
|
||||
requests:
|
||||
storage: 50Gi
|
161
src/commands.rs
161
src/commands.rs
@ -21,6 +21,7 @@ use syntect::highlighting::Theme;
|
||||
use syntect::parsing::SyntaxReference;
|
||||
use syntect::util::LinesWithEndings;
|
||||
use telegram_bot::prelude::*;
|
||||
use telegram_bot::*;
|
||||
use telegram_bot::{Api, Message, ParseMode, UserId};
|
||||
|
||||
include!("../assets/help_text.rs");
|
||||
@ -31,6 +32,12 @@ pub struct Here {
|
||||
pub struct Top {
|
||||
pub data: String,
|
||||
}
|
||||
pub struct ConfTop {
|
||||
pub data: String,
|
||||
}
|
||||
pub struct GlobalTop {
|
||||
pub data: String,
|
||||
}
|
||||
pub struct MarkovAll {
|
||||
pub data: String,
|
||||
}
|
||||
@ -46,6 +53,9 @@ pub struct Sql {
|
||||
pub struct Code {
|
||||
pub data: String,
|
||||
}
|
||||
pub struct Scheme {
|
||||
pub data: String,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
pub trait Execute {
|
||||
@ -59,6 +69,68 @@ pub trait Execute {
|
||||
) -> Result<(), Error>;
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Execute for Scheme {
|
||||
async fn exec(&self, api: &Api, message: &Message) -> Result<(), Error> {
|
||||
match api
|
||||
.send(
|
||||
message
|
||||
.text_reply(format!(
|
||||
"{}{}{}",
|
||||
"<pre>",
|
||||
include_str!("../assets/scheme.sql").to_string(),
|
||||
"</pre>"
|
||||
))
|
||||
.parse_mode(ParseMode::Html),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(_) => debug!("/scheme command sent to {}", message.chat.id()),
|
||||
Err(_) => warn!("/scheme command sent failed to {}", message.chat.id()),
|
||||
};
|
||||
match {
|
||||
Code {
|
||||
data: format!(
|
||||
"{}{}",
|
||||
include_str!("../assets/scheme.sql").to_string(),
|
||||
"\n#sql"
|
||||
),
|
||||
}
|
||||
.exec_with_result(&api, &message)
|
||||
.await
|
||||
} {
|
||||
Ok(path) => {
|
||||
let file = InputFileUpload::with_path(path.clone());
|
||||
// api.send(message.chat.document(&file)).await?;
|
||||
//
|
||||
// // Send an image from disk
|
||||
api.send(message.chat.document(&file)).await?;
|
||||
//debug!("{:#?}", formatter);
|
||||
let _ = std::fs::remove_file(&path);
|
||||
}
|
||||
Err(_) => {
|
||||
let _ = api
|
||||
.send(message.text_reply(CODE_HELP).parse_mode(ParseMode::Html))
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn exec_with_result(&self, api: &Api, message: &Message) -> Result<String, Error> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
async fn exec_mystem(
|
||||
&self,
|
||||
api: &Api,
|
||||
message: &Message,
|
||||
mystem: &mut MyStem,
|
||||
) -> Result<(), Error> {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Execute for Sql {
|
||||
async fn exec(&self, api: &Api, message: &Message) -> Result<(), Error> {
|
||||
@ -281,6 +353,84 @@ impl Execute for Top {
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Execute for GlobalTop {
|
||||
async fn exec(&self, api: &Api, message: &Message) -> Result<(), Error> {
|
||||
let top = db::get_global_top().await?;
|
||||
let mut msg = "<b>Global top words:</b>\n<pre>".to_string();
|
||||
let mut counter = 1;
|
||||
for word in top.iter() {
|
||||
msg = format!(
|
||||
"{} <b>{}</b> {} - {}\n",
|
||||
msg, counter, word.word, word.count
|
||||
);
|
||||
counter += 1;
|
||||
}
|
||||
msg = format!("{}{}", msg, "</pre>");
|
||||
match api
|
||||
.send(message.text_reply(msg).parse_mode(ParseMode::Html))
|
||||
.await
|
||||
{
|
||||
Ok(_) => debug!("/global_top command sent to {}", message.chat.id()),
|
||||
Err(_) => warn!("/global_top command sent failed to {}", message.chat.id()),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn exec_with_result(&self, api: &Api, message: &Message) -> Result<String, Error> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
#[allow(unused_variables)]
|
||||
async fn exec_mystem(
|
||||
&self,
|
||||
api: &Api,
|
||||
message: &Message,
|
||||
mystem: &mut MyStem,
|
||||
) -> Result<(), Error> {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Execute for ConfTop {
|
||||
async fn exec(&self, api: &Api, message: &Message) -> Result<(), Error> {
|
||||
let top = db::get_conf_top(&message).await?;
|
||||
let mut msg = "<b>Conf top words:</b>\n<pre>".to_string();
|
||||
let mut counter = 1;
|
||||
for word in top.iter() {
|
||||
msg = format!(
|
||||
"{} <b>{}</b> {} - {}\n",
|
||||
msg, counter, word.word, word.count
|
||||
);
|
||||
counter += 1;
|
||||
}
|
||||
msg = format!("{}{}", msg, "</pre>");
|
||||
match api
|
||||
.send(message.text_reply(msg).parse_mode(ParseMode::Html))
|
||||
.await
|
||||
{
|
||||
Ok(_) => debug!("/conf_top command sent to {}", message.chat.id()),
|
||||
Err(_) => warn!("/conf_top command sent failed to {}", message.chat.id()),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn exec_with_result(&self, api: &Api, message: &Message) -> Result<String, Error> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
#[allow(unused_variables)]
|
||||
async fn exec_mystem(
|
||||
&self,
|
||||
api: &Api,
|
||||
message: &Message,
|
||||
mystem: &mut MyStem,
|
||||
) -> Result<(), Error> {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Execute for MarkovAll {
|
||||
async fn exec(&self, api: &Api, message: &Message) -> Result<(), Error> {
|
||||
@ -289,7 +439,7 @@ impl Execute for MarkovAll {
|
||||
chain.feed(messages);
|
||||
let mut sentences = chain.generate();
|
||||
let mut msg = String::new();
|
||||
for _ in 1..rand::thread_rng().gen_range(2, 10) {
|
||||
for _ in 1..rand::thread_rng().gen_range(2..10) {
|
||||
msg = format!("{} {}", msg, sentences.pop().unwrap());
|
||||
}
|
||||
match api
|
||||
@ -327,8 +477,8 @@ impl Execute for Markov {
|
||||
chain.feed(messages);
|
||||
let mut sentences = chain.generate();
|
||||
let mut msg = String::new();
|
||||
for _ in 1..rand::thread_rng().gen_range(2, 10) {
|
||||
msg = format!("{} {}", msg, sentences.pop().unwrap());
|
||||
for _ in 1..rand::thread_rng().gen_range(2..10) {
|
||||
msg = format!("{} {}", msg, sentences.pop().unwrap_or(" ".into()));
|
||||
}
|
||||
match api
|
||||
.send(message.text_reply(msg.trim()).parse_mode(ParseMode::Html))
|
||||
@ -571,9 +721,6 @@ impl Execute for Code {
|
||||
.split("\n")
|
||||
.map(|s| s.to_string())
|
||||
.collect();
|
||||
if lines.len() >= 81 {
|
||||
return Err(CodeHighlightningError);
|
||||
}
|
||||
let last_line = &lines[lines.len() - 1];
|
||||
|
||||
let tags = last_line
|
||||
@ -613,7 +760,7 @@ impl Execute for Code {
|
||||
.collect();
|
||||
|
||||
let theme = if theme.len() != 1 {
|
||||
ts.themes.get("Dracula").unwrap()
|
||||
ts.themes.get("gruvbox").unwrap()
|
||||
} else {
|
||||
theme[0]
|
||||
};
|
||||
|
81
src/db.rs
81
src/db.rs
@ -1,3 +1,5 @@
|
||||
#[allow(unused_mut)]
|
||||
#[allow(dead_code)]
|
||||
use crate::errors;
|
||||
use crate::utils;
|
||||
use rusqlite::{named_params, params, Connection, Error, Result};
|
||||
@ -34,6 +36,30 @@ pub(crate) fn update_scheme() -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn load_stopwords() -> Result<()> {
|
||||
info!("Populating stop words wait please.");
|
||||
let conn = open()?;
|
||||
for table in include_str!("../assets/stop-words.txt")
|
||||
.split('\n')
|
||||
.into_iter()
|
||||
{
|
||||
let word = table.trim();
|
||||
if word != "" {
|
||||
let mut _stmt = conn
|
||||
.prepare_cached(
|
||||
"
|
||||
INSERT OR IGNORE INTO
|
||||
stop_words('word')
|
||||
VALUES (:word)
|
||||
",
|
||||
)?
|
||||
.insert(params![word]);
|
||||
}
|
||||
}
|
||||
info!("Stop words updated.");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn get_user(id: telegram_bot::UserId) -> Result<telegram_bot::User, errors::Error> {
|
||||
let conn = open()?;
|
||||
let mut stmt = conn.prepare_cached(
|
||||
@ -447,7 +473,7 @@ pub(crate) async fn add_sentence(
|
||||
Err(e) => panic!("SQLITE3 Error: Relations failed: {:?}", e),
|
||||
}
|
||||
}
|
||||
Err(_) => debug!("Word {} is in stop list.", &word.lex[0].lex),
|
||||
Err(_) => debug!("Word {} is in a stop list.", &word.lex[0].lex),
|
||||
}
|
||||
}
|
||||
conn.execute("END TRANSACTION", params![]);
|
||||
@ -486,3 +512,56 @@ pub(crate) async fn get_top(
|
||||
}
|
||||
Ok(top)
|
||||
}
|
||||
|
||||
pub(crate) async fn get_global_top() -> Result<Vec<TopWord>, errors::Error> {
|
||||
let conn = open()?;
|
||||
let mut stmt = conn.prepare_cached(
|
||||
"
|
||||
SELECT w.word, COUNT(*) as count FROM relations r
|
||||
LEFT JOIN word w ON w.id = r.word_id
|
||||
GROUP BY w.word
|
||||
ORDER BY count DESC
|
||||
LIMIT 50
|
||||
",
|
||||
)?;
|
||||
|
||||
let mut rows = stmt.query_named(named_params! {})?;
|
||||
let mut top = Vec::new();
|
||||
|
||||
while let Some(row) = rows.next()? {
|
||||
top.push(TopWord {
|
||||
word: row.get(0)?,
|
||||
count: row.get(1)?,
|
||||
})
|
||||
}
|
||||
Ok(top)
|
||||
}
|
||||
|
||||
pub(crate) async fn get_conf_top(
|
||||
message: &telegram_bot::Message,
|
||||
) -> Result<Vec<TopWord>, errors::Error> {
|
||||
let conf_id = i64::from(message.chat.id());
|
||||
|
||||
let conn = open()?;
|
||||
let mut stmt = conn.prepare_cached(
|
||||
"
|
||||
SELECT w.word, COUNT(*) as count FROM relations r
|
||||
LEFT JOIN word w ON w.id = r.word_id
|
||||
WHERE r.conf_id = :conf_id
|
||||
GROUP BY w.word
|
||||
ORDER BY count DESC
|
||||
LIMIT 10
|
||||
",
|
||||
)?;
|
||||
|
||||
let mut rows = stmt.query_named(named_params! {":conf_id": conf_id})?;
|
||||
let mut top = Vec::new();
|
||||
|
||||
while let Some(row) = rows.next()? {
|
||||
top.push(TopWord {
|
||||
word: row.get(0)?,
|
||||
count: row.get(1)?,
|
||||
})
|
||||
}
|
||||
Ok(top)
|
||||
}
|
||||
|
@ -1,5 +1,7 @@
|
||||
//use crate::commands::Command;
|
||||
use crate::commands::{Code, Execute, Here, Markov, MarkovAll, Omedeto, Sql, Top};
|
||||
use crate::commands::{
|
||||
Code, ConfTop, Execute, GlobalTop, Here, Markov, MarkovAll, Omedeto, Scheme, Sql, Top,
|
||||
};
|
||||
use crate::db;
|
||||
use crate::errors;
|
||||
use crate::utils;
|
||||
@ -38,11 +40,30 @@ pub async fn handler(
|
||||
.await
|
||||
} {
|
||||
Ok(path) => {
|
||||
let mut cnt_lines = 0;
|
||||
for _ in s.lines() {
|
||||
cnt_lines = cnt_lines + 1;
|
||||
}
|
||||
let mut cnt_chars = 0;
|
||||
for _ in s.chars() {
|
||||
cnt_chars = cnt_chars + 1;
|
||||
}
|
||||
let file = InputFileUpload::with_path(path.clone());
|
||||
info!("lines: {}, chars: {}", cnt_lines, cnt_chars);
|
||||
// api.send(message.chat.document(&file)).await?;
|
||||
//
|
||||
// // Send an image from disk
|
||||
api.send(message.chat.photo(&file)).await?;
|
||||
if cnt_chars > 4000 {
|
||||
let _ = api
|
||||
.send(message.text_reply(CODE_HELP).parse_mode(ParseMode::Html))
|
||||
.await?;
|
||||
return Ok(());
|
||||
}
|
||||
if cnt_lines < 81 {
|
||||
api.send(message.chat.photo(&file)).await?;
|
||||
} else {
|
||||
api.send(message.chat.document(&file)).await?;
|
||||
}
|
||||
//debug!("{:#?}", formatter);
|
||||
let _ = std::fs::remove_file(&path);
|
||||
}
|
||||
@ -53,7 +74,14 @@ pub async fn handler(
|
||||
}
|
||||
}
|
||||
}
|
||||
s if s.contains("/here") || s.contains("@here") => {
|
||||
s if s.contains("/here")
|
||||
|| s.contains("@here")
|
||||
|| s.contains("/хере")
|
||||
|| s.contains("@хере")
|
||||
|| s.contains("@all")
|
||||
|| s.contains("\"руку")
|
||||
|| s.contains("\"хере") =>
|
||||
{
|
||||
db::add_sentence(&message, mystem).await?;
|
||||
Here {
|
||||
data: "".to_string(),
|
||||
@ -83,15 +111,22 @@ pub async fn handler(
|
||||
.await?;
|
||||
}
|
||||
},
|
||||
"/top" => {
|
||||
"/top" | "/stat" => {
|
||||
Top {
|
||||
data: "".to_string(),
|
||||
}
|
||||
.exec(&api, &message)
|
||||
.await?
|
||||
}
|
||||
"/stat" => {
|
||||
Top {
|
||||
"/global_top" | "/global_stat" => {
|
||||
GlobalTop {
|
||||
data: "".to_string(),
|
||||
}
|
||||
.exec(&api, &message)
|
||||
.await?
|
||||
}
|
||||
"/conf_stat" | "/conf_top" => {
|
||||
ConfTop {
|
||||
data: "".to_string(),
|
||||
}
|
||||
.exec(&api, &message)
|
||||
@ -105,7 +140,24 @@ pub async fn handler(
|
||||
.await?
|
||||
}
|
||||
"/markov" => {
|
||||
Markov {
|
||||
if title != "PRIVATE" {
|
||||
Markov {
|
||||
data: "".to_string(),
|
||||
}
|
||||
.exec(&api, &message)
|
||||
.await?
|
||||
} else {
|
||||
let _ = api
|
||||
.send(
|
||||
message
|
||||
.text_reply("Allowed in groups only")
|
||||
.parse_mode(ParseMode::Html),
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
"/scheme" | "/schema" => {
|
||||
Scheme {
|
||||
data: "".to_string(),
|
||||
}
|
||||
.exec(&api, &message)
|
||||
|
45
src/main.rs
45
src/main.rs
@ -1,4 +1,6 @@
|
||||
#![allow(unreachable_code)]
|
||||
use std::{env, process};
|
||||
use tokio::time::{delay_for, Duration};
|
||||
|
||||
use futures::StreamExt;
|
||||
use telegram_bot::*;
|
||||
@ -16,7 +18,7 @@ use mystem::MyStem;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), errors::Error> {
|
||||
env_logger::from_env(Env::default().default_filter_or("info")).init();
|
||||
env_logger::Builder::from_env(Env::default().default_filter_or("info")).init();
|
||||
let mut mystem = match MyStem::new() {
|
||||
Ok(mystem) => mystem,
|
||||
Err(e) => {
|
||||
@ -28,6 +30,10 @@ async fn main() -> Result<(), errors::Error> {
|
||||
Ok(_) => {}
|
||||
Err(e) => panic!("Database error: {:?}", e),
|
||||
}
|
||||
match db::load_stopwords() {
|
||||
Ok(_) => {}
|
||||
Err(e) => panic!("Database error: {:?}", e),
|
||||
}
|
||||
let token = match env::var("TELEGRAM_BOT_TOKEN") {
|
||||
Ok(token) => token,
|
||||
Err(_) => {
|
||||
@ -44,18 +50,33 @@ async fn main() -> Result<(), errors::Error> {
|
||||
me.first_name,
|
||||
me.id
|
||||
);
|
||||
while let Some(update) = stream.next().await {
|
||||
let update = update?;
|
||||
if let UpdateKind::Message(message) = update.kind {
|
||||
db::add_conf(message.clone()).await?;
|
||||
db::add_user(message.clone()).await?;
|
||||
match handlers::handler(api.clone(), message, token.clone(), &mut mystem, me.clone())
|
||||
.await
|
||||
{
|
||||
Ok(_) => {}
|
||||
Err(e) => warn!("An error occurred handling command. {:?}", e),
|
||||
}
|
||||
loop {
|
||||
while let Some(update) = stream.next().await {
|
||||
match update {
|
||||
Ok(u) => {
|
||||
if let UpdateKind::Message(message) = u.kind {
|
||||
db::add_conf(message.clone()).await?;
|
||||
db::add_user(message.clone()).await?;
|
||||
match handlers::handler(
|
||||
api.clone(),
|
||||
message,
|
||||
token.clone(),
|
||||
&mut mystem,
|
||||
me.clone(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(_) => {}
|
||||
Err(e) => warn!("An error occurred handling command. {:?}", e),
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
warn!("Telegram API Error: {:?}", e);
|
||||
}
|
||||
};
|
||||
}
|
||||
delay_for(Duration::from_secs(2)).await;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
Reference in New Issue
Block a user