8 Commits

Author SHA1 Message Date
2fb5b03fbf Add global, conf stat. 2022-11-23 17:53:56 +02:00
55a7dd6a67 add @all command 2022-11-03 19:01:17 +02:00
AB
ff6e04988e Merge branch 'main' of https://github.com/house-of-vanity/desubot into main 2022-05-05 12:25:10 +03:00
AB
7c067065bd add a few stop words 2022-05-05 12:24:38 +03:00
175f056add Update README.md 2022-01-23 15:09:05 +03:00
AB
20f366572b Add scheme. 2022-01-23 15:02:21 +03:00
e5079fa584 Update db.rs 2021-12-08 17:51:32 +03:00
fdc52b7198 Update README.md
Add deps
2021-12-08 17:46:33 +03:00
7 changed files with 271 additions and 19 deletions

View File

@ -1,6 +1,6 @@
[package] [package]
name = "desubot" name = "desubot"
version = "0.5.8" version = "0.5.9"
authors = ["AB <ab@hexor.ru>"] authors = ["AB <ab@hexor.ru>"]
edition = "2018" edition = "2018"

View File

@ -13,8 +13,12 @@ Telegram bot with light group statistic and heavy spy features.
== Important == == Important ==
* Desubot uses MyStem by Yandex for word stemming and assume that mystem binary is available in PATH. * 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. * ubuntu deps: libssl-dev libsqlite3-dev cmake libfreetype-dev
![image](https://user-images.githubusercontent.com/4666566/150677613-32bdedf9-4b4c-4ec5-99cd-3d0221e56fb5.png)
![image](https://user-images.githubusercontent.com/4666566/150677660-183572b4-2a69-425f-a32c-dba5ec97e438.png)
## License ## License
[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fhouse-of-vanity%2Fdesubot.svg?type=large)](https://app.fossa.com/projects/git%2Bgithub.com%2Fhouse-of-vanity%2Fdesubot?ref=badge_large) [![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fhouse-of-vanity%2Fdesubot.svg?type=large)](https://app.fossa.com/projects/git%2Bgithub.com%2Fhouse-of-vanity%2Fdesubot?ref=badge_large)

View File

@ -1,5 +1,5 @@
#[allow(dead_code)] #[allow(dead_code)]
static CODE_HELP: &str = "<b>Code highlighter</b> pub(crate) static CODE_HELP: &str = "<b>Code highlighter</b>
<i>Usage</i> <i>Usage</i>
<pre>/code <pre>/code
@ -7,7 +7,7 @@ static CODE_HELP: &str = "<b>Code highlighter</b>
#&lt;lang - JS by default&gt; #&lt;theme - Dracula by default&gt;</pre> #&lt;lang - JS by default&gt; #&lt;theme - Dracula by default&gt;</pre>
Language may be defined by both name and extension - Rust, rs... Language may be defined by both name and extension - Rust, rs...
Max lines - 80 Max length - 4000
List of themes: List of themes:
1337 1337

View File

@ -11,6 +11,10 @@
т т
у у
я я
чо
че
ща
ваще
бы бы
stat stat
вообще вообще

View File

@ -21,6 +21,7 @@ use syntect::highlighting::Theme;
use syntect::parsing::SyntaxReference; use syntect::parsing::SyntaxReference;
use syntect::util::LinesWithEndings; use syntect::util::LinesWithEndings;
use telegram_bot::prelude::*; use telegram_bot::prelude::*;
use telegram_bot::*;
use telegram_bot::{Api, Message, ParseMode, UserId}; use telegram_bot::{Api, Message, ParseMode, UserId};
include!("../assets/help_text.rs"); include!("../assets/help_text.rs");
@ -31,6 +32,12 @@ pub struct Here {
pub struct Top { pub struct Top {
pub data: String, pub data: String,
} }
pub struct ConfTop {
pub data: String,
}
pub struct GlobalTop {
pub data: String,
}
pub struct MarkovAll { pub struct MarkovAll {
pub data: String, pub data: String,
} }
@ -46,6 +53,9 @@ pub struct Sql {
pub struct Code { pub struct Code {
pub data: String, pub data: String,
} }
pub struct Scheme {
pub data: String,
}
#[async_trait] #[async_trait]
pub trait Execute { pub trait Execute {
@ -59,6 +69,68 @@ pub trait Execute {
) -> Result<(), Error>; ) -> 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] #[async_trait]
impl Execute for Sql { impl Execute for Sql {
async fn exec(&self, api: &Api, message: &Message) -> Result<(), Error> { 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] #[async_trait]
impl Execute for MarkovAll { impl Execute for MarkovAll {
async fn exec(&self, api: &Api, message: &Message) -> Result<(), Error> { async fn exec(&self, api: &Api, message: &Message) -> Result<(), Error> {
@ -571,9 +721,6 @@ impl Execute for Code {
.split("\n") .split("\n")
.map(|s| s.to_string()) .map(|s| s.to_string())
.collect(); .collect();
if lines.len() >= 81 {
return Err(CodeHighlightningError);
}
let last_line = &lines[lines.len() - 1]; let last_line = &lines[lines.len() - 1];
let tags = last_line let tags = last_line
@ -613,7 +760,7 @@ impl Execute for Code {
.collect(); .collect();
let theme = if theme.len() != 1 { let theme = if theme.len() != 1 {
ts.themes.get("Dracula").unwrap() ts.themes.get("gruvbox").unwrap()
} else { } else {
theme[0] theme[0]
}; };

View File

@ -1,3 +1,5 @@
#[allow(unused_mut)]
#[allow(dead_code)]
use crate::errors; use crate::errors;
use crate::utils; use crate::utils;
use rusqlite::{named_params, params, Connection, Error, Result}; use rusqlite::{named_params, params, Connection, Error, Result};
@ -35,18 +37,23 @@ pub(crate) fn update_scheme() -> Result<()> {
} }
pub(crate) fn load_stopwords() -> Result<()> { pub(crate) fn load_stopwords() -> Result<()> {
info!("Populating stop words wait please.");
let conn = open()?; let conn = open()?;
for table in include_str!("../assets/stop-words.txt").split('\n').into_iter() { for table in include_str!("../assets/stop-words.txt")
.split('\n')
.into_iter()
{
let word = table.trim(); let word = table.trim();
if word != "" { if word != "" {
let mut stmt = conn.prepare_cached( let mut _stmt = conn
" .prepare_cached(
"
INSERT OR IGNORE INTO INSERT OR IGNORE INTO
stop_words('word') stop_words('word')
VALUES (:word) VALUES (:word)
", ",
)?.insert(params![word]); )?
//let mut rows = stmt.word(named_params! {":conf_id": conf_id})?; .insert(params![word]);
} }
} }
info!("Stop words updated."); info!("Stop words updated.");
@ -505,3 +512,56 @@ pub(crate) async fn get_top(
} }
Ok(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)
}

View File

@ -1,5 +1,7 @@
//use crate::commands::Command; //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::db;
use crate::errors; use crate::errors;
use crate::utils; use crate::utils;
@ -38,11 +40,30 @@ pub async fn handler(
.await .await
} { } {
Ok(path) => { 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()); let file = InputFileUpload::with_path(path.clone());
info!("lines: {}, chars: {}", cnt_lines, cnt_chars);
// api.send(message.chat.document(&file)).await?; // api.send(message.chat.document(&file)).await?;
// //
// // Send an image from disk // // 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); //debug!("{:#?}", formatter);
let _ = std::fs::remove_file(&path); let _ = std::fs::remove_file(&path);
} }
@ -57,6 +78,8 @@ pub async fn handler(
|| s.contains("@here") || s.contains("@here")
|| s.contains("/хере") || s.contains("/хере")
|| s.contains("@хере") || s.contains("@хере")
|| s.contains("@all")
|| s.contains("\"руку")
|| s.contains("\"хере") => || s.contains("\"хере") =>
{ {
db::add_sentence(&message, mystem).await?; db::add_sentence(&message, mystem).await?;
@ -88,15 +111,22 @@ pub async fn handler(
.await?; .await?;
} }
}, },
"/top" => { "/top" | "/stat" => {
Top { Top {
data: "".to_string(), data: "".to_string(),
} }
.exec(&api, &message) .exec(&api, &message)
.await? .await?
} }
"/stat" => { "/global_top" | "/global_stat" => {
Top { GlobalTop {
data: "".to_string(),
}
.exec(&api, &message)
.await?
}
"/conf_stat" | "/conf_top" => {
ConfTop {
data: "".to_string(), data: "".to_string(),
} }
.exec(&api, &message) .exec(&api, &message)
@ -116,6 +146,13 @@ pub async fn handler(
.exec(&api, &message) .exec(&api, &message)
.await? .await?
} }
s if s == "/scheme" || s == "/schema" => {
Scheme {
data: "".to_string(),
}
.exec(&api, &message)
.await?
}
"/omedeto" => { "/omedeto" => {
Omedeto { Omedeto {
data: "".to_string(), data: "".to_string(),