Add /code feature.

This commit is contained in:
AB
2021-01-08 17:32:39 +03:00
parent 8f3cccf01d
commit 4b142ae8ef
8 changed files with 916 additions and 771 deletions

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "desubot" name = "desubot"
version = "0.1.0" version = "0.5.0"
authors = ["AB <ab@hexor.ru>"] authors = ["AB <ab@hexor.ru>"]
edition = "2018" edition = "2018"
@@ -32,4 +32,9 @@ markov = "1.1.0"
rand = "0.7.3" rand = "0.7.3"
mystem = "0.2.1" mystem = "0.2.1"
async-trait = "0.1.42" async-trait = "0.1.42"
sqlparser = "0.7.0" sqlparser = "0.7.0"
[dependencies.syntect]
version = "4.4"
default-features = false
features = ["parsing", "dump-load", "regex-onig"]

2
README
View File

@@ -9,7 +9,7 @@ Telegram bot with light group statistic and heavy spy features.
* Generate sentences using Markov Chains trained on history with /markov_all. * Generate sentences using Markov Chains trained on history with /markov_all.
== TODO == == TODO ==
* Syntax highlighting for code exported to image. * Syntax highlighting for CODE exported to image.
== 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.

36
assets/help_text.rs Normal file
View File

@@ -0,0 +1,36 @@
static CODE: &str = "<b>Code highlighter</b>
<i>Usage</i>
<pre>/CODE
#&lt;theme - Dracula by default&gt;
&lt;CODE&gt;
#&lt;lang - JS by default&gt;</pre>
Language may be defined by both name and extension - Rust, rs...
Max lines - 80
List of themes:
1337
DarkNeon
Dracula
GitHub
Monokai_Extended
Monokai_Extended_Bright
Monokai_Extended_Light
Monokai_Extended_Origin
Nord
OneHalfDark
OneHalfLight
Solarized_(dark)
Solarized_(light)
Sublime_Snazzy
TwoDark
ansi-dark
ansi-light
base16
base16-256
gruvbox
gruvbox-light
gruvbox-white
zenburn
";

File diff suppressed because it is too large Load Diff

View File

@@ -348,10 +348,9 @@ pub(crate) async fn get_file(file_id: String) -> Result<i64, errors::Error> {
file_rowid file_rowid
} }
async fn add_word(word: &String) -> Result<i64, errors::Error> { async fn add_word(word: &str) -> Result<i64, errors::Error> {
match get_stop_word(&word).await { if get_stop_word(&word).await.is_err() {
Err(_) => return Err(errors::Error::WordInStopList), return Err(errors::Error::WordInStopList);
_ => {}
} }
let conn = open()?; let conn = open()?;
let word_rowid = let word_rowid =
@@ -365,7 +364,7 @@ async fn add_word(word: &String) -> Result<i64, errors::Error> {
Ok(word_rowid) Ok(word_rowid)
} }
async fn get_stop_word(stop_word: &String) -> Result<(), errors::Error> { async fn get_stop_word(stop_word: &str) -> Result<(), errors::Error> {
let conn = open()?; let conn = open()?;
match conn.execute_named( match conn.execute_named(
"SELECT rowid FROM stop_words WHERE word = (:stop_word)", "SELECT rowid FROM stop_words WHERE word = (:stop_word)",

View File

@@ -23,11 +23,39 @@ pub enum Error {
SQLBannedCommand(String), SQLBannedCommand(String),
SQLInvalidCommand, SQLInvalidCommand,
SQLResultTooLong(String), SQLResultTooLong(String),
CodeHighlightningError,
} }
impl fmt::Display for Error { impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "An error occurred.") write!(f, "An error occurred.")
// match self {
// _ => write!(f, "An error occurred."),
// // Error::UserNotFound => {}
// // Error::SQLITE3Error(_) => {}
// // Error::TelegramError(_) => {}
// // Error::ReqwestError(_) => {}
// // Error::ConfNotFound => {}
// // Error::WordNotFound => {}
// // Error::WordInStopList => {}
// // Error::IOError(_) => {}
// // Error::FileNotFound => {}
// // Error::JsonParseError(_) => {}
// // Error::PopenError(_) => {}
// // Error::MystemError(_) => {}
// // Error::SQLBannedCommand(_) => {}
// // Error::SQLInvalidCommand => {}
// // Error::SQLResultTooLong(_) => {}
// // Error::CodeHighlightningError(Help) => write!(f, "Code highlighter.\
// // <b>Usage</b><pre>/CODE\
// // #&lt;theme&gt;\
// // &lt;CODE&gt;\
// // #&lt;lang&gt;</pre>\
// // \
// // List of themes:\
// // .")
// Error::CodeHighlightningError(help) => write!(f, "{}", help.description)
// }
} }
} }

View File

@@ -1,184 +1,202 @@
//use crate::commands::Command; //use crate::commands::Command;
use crate::commands::{Code, Execute, Here, Markov, MarkovAll, Omedeto, Sql, Top}; use crate::commands::{Code, Execute, Here, Markov, MarkovAll, Omedeto, Sql, Top};
use crate::db; use crate::db;
use crate::errors; use crate::errors;
use crate::utils; use crate::utils;
use mystem::MyStem; use mystem::MyStem;
use telegram_bot::*; use telegram_bot::*;
pub async fn handler(
api: Api, include!("../assets/help_text.rs");
message: Message,
token: String, pub async fn handler(
mystem: &mut MyStem, api: Api,
me: User, message: Message,
) -> Result<(), errors::Error> { token: String,
match message.kind { mystem: &mut MyStem,
MessageKind::Text { ref data, .. } => { me: User,
let title = utils::get_title(&message); ) -> Result<(), errors::Error> {
info!( match message.kind {
"<{}({})>[{}({})]: {}", MessageKind::Text { ref data, .. } => {
&message.chat.id(), let title = utils::get_title(&message);
title, info!(
&message.from.id, "<{}({})>[{}({})]: {}",
&message.from.first_name, &message.chat.id(),
data title,
); &message.from.id,
db::add_sentence(&message, mystem).await?; &message.from.first_name,
let cleaned_message = data.replace(&format!("@{}", me.clone().username.unwrap()), ""); {if data.len() <= 200 {data} else {&data[..200]}}.replace("\n", " ")
match cleaned_message.as_str() { );
s if s.contains("/here") => {
Here { let cleaned_message = data.replace(&format!("@{}", me.clone().username.unwrap()), "");
data: "".to_string(), match cleaned_message.as_str() {
} s if s.to_string().starts_with("/code") => {
.exec(&api, &message)
.await? match {
} Code {
s if s.to_string().starts_with("/sql") => match { data: s.replace("/code", ""),
Sql { }
data: s.replace("/sql ", ""), .exec_with_result(&api, &message)
} .await
.exec_with_result(&api, &message) } {
.await Ok(path) => {
} { let file = InputFileUpload::with_path(path.clone());
Ok(msg) => { // api.send(message.chat.document(&file)).await?;
let _ = api //
.send( // // Send an image from disk
message api.send(message.chat.photo(&file)).await?;
.text_reply(msg) //debug!("{:#?}", formatter);
.parse_mode(ParseMode::Html), let _ = std::fs::remove_file(&path);
) }
.await?; Err(_) => {
}, let _ = api
Err(e) => { .send(message.text_reply(CODE).parse_mode(ParseMode::Html))
let _ = api .await?;
.send( }
message }
.text_reply(format!("Error: {:#?}", e)) }
.parse_mode(ParseMode::Html), s if s.contains("/here") => {
) db::add_sentence(&message, mystem).await?;
.await?; Here {
} data: "".to_string(),
}, }
s if s.to_string().starts_with("/code") => { .exec(&api, &message)
Code { .await?
data: s.to_string(), }
} s if s.to_string().starts_with("/sql") => match {
.exec(&api, &message) Sql {
.await? data: s.replace("/sql ", ""),
} }
"/top" => { .exec_with_result(&api, &message)
Top { .await
data: "".to_string(), } {
} Ok(msg) => {
.exec(&api, &message) let _ = api
.await? .send(message.text_reply(msg).parse_mode(ParseMode::Html))
} .await?;
"/stat" => { }
Top { Err(e) => {
data: "".to_string(), let _ = api
} .send(
.exec(&api, &message) message
.await? .text_reply(format!("Error: {:#?}", e))
} .parse_mode(ParseMode::Html),
"/markov_all" => { )
MarkovAll { .await?;
data: "".to_string(), }
} },
.exec(&api, &message) "/top" => {
.await? Top {
} data: "".to_string(),
"/markov" => { }
Markov { .exec(&api, &message)
data: "".to_string(), .await?
} }
.exec(&api, &message) "/stat" => {
.await? Top {
} data: "".to_string(),
"/omedeto" => { }
Omedeto { .exec(&api, &message)
data: "".to_string(), .await?
} }
.exec_mystem(&api, &message, mystem) "/markov_all" => {
.await? MarkovAll {
} data: "".to_string(),
_ => (), }
} .exec(&api, &message)
} .await?
MessageKind::Photo { ref caption, .. } => { }
let title = utils::get_title(&message); "/markov" => {
info!( Markov {
"<{}({})>[{}({})]: *PHOTO* {}", data: "".to_string(),
&message.chat.id(), }
title, .exec(&api, &message)
&message.from.id, .await?
&message.from.first_name, }
caption.clone().unwrap_or("NO_TITLE".to_string()) "/omedeto" => {
); Omedeto {
utils::get_files(api, message, token).await?; data: "".to_string(),
} }
.exec_mystem(&api, &message, mystem)
MessageKind::Document { ref caption, .. } => { .await?
let title = utils::get_title(&message); }
info!( _ => db::add_sentence(&message, mystem).await?,
"<{}({})>[{}({})]: *DOCUMENT* {}", }
&message.chat.id(), }
title, MessageKind::Photo { ref caption, .. } => {
&message.from.id, let title = utils::get_title(&message);
&message.from.first_name, info!(
caption.clone().unwrap_or("NO_TITLE".to_string()) "<{}({})>[{}({})]: *PHOTO* {}",
); &message.chat.id(),
utils::get_files(api, message, token).await?; title,
} &message.from.id,
&message.from.first_name,
MessageKind::Sticker { .. } => { caption.clone().unwrap_or_else(|| "NO_TITLE".to_string())
let title = utils::get_title(&message); );
info!( utils::get_files(api, message, token).await?;
"<{}({})>[{}({})]: *STICKER*", }
&message.chat.id(),
title, MessageKind::Document { ref caption, .. } => {
&message.from.id, let title = utils::get_title(&message);
&message.from.first_name, info!(
); "<{}({})>[{}({})]: *DOCUMENT* {}",
utils::get_files(api, message, token).await?; &message.chat.id(),
} title,
&message.from.id,
MessageKind::Voice { .. } => { &message.from.first_name,
let title = utils::get_title(&message); caption.clone().unwrap_or_else(|| "NO_TITLE".to_string())
info!( );
"<{}({})>[{}({})]: *VOICE*", utils::get_files(api, message, token).await?;
&message.chat.id(), }
title,
&message.from.id, MessageKind::Sticker { .. } => {
&message.from.first_name, let title = utils::get_title(&message);
); info!(
utils::get_files(api, message, token).await?; "<{}({})>[{}({})]: *STICKER*",
} &message.chat.id(),
title,
MessageKind::Video { .. } => { &message.from.id,
let title = utils::get_title(&message); &message.from.first_name,
info!( );
"<{}({})>[{}({})]: *VIDEO*", utils::get_files(api, message, token).await?;
&message.chat.id(), }
title,
&message.from.id, MessageKind::Voice { .. } => {
&message.from.first_name, let title = utils::get_title(&message);
); info!(
utils::get_files(api, message, token).await?; "<{}({})>[{}({})]: *VOICE*",
} &message.chat.id(),
title,
MessageKind::VideoNote { .. } => { &message.from.id,
let title = utils::get_title(&message); &message.from.first_name,
info!( );
"<{}({})>[{}({})]: *VIDEO_NOTE*", utils::get_files(api, message, token).await?;
&message.chat.id(), }
title,
&message.from.id, MessageKind::Video { .. } => {
&message.from.first_name, let title = utils::get_title(&message);
); info!(
utils::get_files(api, message, token).await?; "<{}({})>[{}({})]: *VIDEO*",
} &message.chat.id(),
_ => (), title,
}; &message.from.id,
Ok(()) &message.from.first_name,
} );
utils::get_files(api, message, token).await?;
}
MessageKind::VideoNote { .. } => {
let title = utils::get_title(&message);
info!(
"<{}({})>[{}({})]: *VIDEO_NOTE*",
&message.chat.id(),
title,
&message.from.id,
&message.from.first_name,
);
utils::get_files(api, message, token).await?;
}
_ => (),
};
Ok(())
}

View File

@@ -18,10 +18,9 @@ pub(crate) fn get_title(message: &Message) -> String {
} }
} }
pub(crate) async fn create_dir(dir: &String) { pub(crate) async fn create_dir(dir: &str) {
match fs_create_dir(dir) { if fs_create_dir(dir).is_ok() {
Ok(_) => info!("Dir {} created.", dir), info!("Dir {} created.", dir)
Err(_) => (),
} }
} }