Skip to content

Commit

Permalink
feat: work in progress ocr auto responder
Browse files Browse the repository at this point in the history
  • Loading branch information
oSumAtrIX committed Aug 25, 2022
1 parent 3ad345b commit 23bc1aa
Show file tree
Hide file tree
Showing 11 changed files with 666 additions and 93 deletions.
476 changes: 472 additions & 4 deletions Cargo.lock

Large diffs are not rendered by default.

5 changes: 4 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,19 @@ panic = "abort"

[dependencies]
bson = "2.4"
serde_with_macros = "2.0"
mongodb = "2.3"
poise = "0.3"
decancer = "1.4"
tokio = { version = "1.20.1", features = ["rt-multi-thread"] }
tokio = { version = "1.20", features = ["rt-multi-thread"] }
dotenv = "0.15"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
regex = "1.0"
serde_regex = "1.1"
reqwest = { version = "0.11" }
chrono = "0.4"
dirs = "4.0"
tesseract = "0.12"
tracing = { version = "0.1", features = ["max_level_debug", "release_max_level_info"] }
tracing-subscriber = "0.3"
10 changes: 8 additions & 2 deletions configuration.example.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,17 @@
{
"includes": {
"channels": [0],
"match": [""]
"match": {
"text": [],
"ocr": [""]
}
},
"excludes": {
"roles": [0],
"match": [""]
"match": {
"text": [""],
"ocr": []
}
},
"condition": {
"user": {
Expand Down
25 changes: 17 additions & 8 deletions configuration.revanced.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,14 @@
{
"includes": {
"channels": [952946952348270626, 953965039105232906],
"match": ["(?i)(((vanced|download|install|get|manager).*){2,})"]
"match": {
"ocr": [],
"text": ["(?i)(((vanced|download|install|get|manager).*){2,})"]
}
},
"excludes": {
"roles": [934329947164667954],
"match": ["(?i)music", "(?i)eta", "(?i)error"]
"match": { "ocr": [], "text": ["(?i)music", "(?i)eta", "(?i)error"] }
},
"condition": {
"user": {
Expand Down Expand Up @@ -66,11 +69,14 @@
{
"includes": {
"channels": [952946952348270626, 953965039105232906],
"match": ["(?i)(((help|how|install|fix|vanced|manager).*){3,})"]
"match": {
"ocr": [],
"text": ["(?i)(((help|how|install|fix|vanced|manager).*){3,})"]
}
},
"excludes": {
"roles": [934329947164667954],
"match": ["(?i)download", "(?i)get"]
"match": { "ocr": [], "text": ["(?i)download", "(?i)get"] }
},
"condition": {
"user": {
Expand Down Expand Up @@ -110,13 +116,16 @@
{
"includes": {
"channels": [952946952348270626, 953965039105232906],
"match": [
"(?i)(((vanced|when|where|release|out|progress|update|manager|eta).*){2,})"
]
"match": {
"ocr": [],
"text": [
"(?i)(((vanced|when|where|release|out|progress|update|manager|eta).*){2,})"
]
}
},
"excludes": {
"roles": [934329947164667954],
"match": ["(?i)error", "(?i)problem"]
"match": { "ocr": [], "text": ["(?i)error", "(?i)problem"] }
},
"condition": {
"user": {
Expand Down
14 changes: 13 additions & 1 deletion configuration.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -145,11 +145,23 @@
"minItems": 1
},
"match": {
"type": "object",
"properties": {
"text": {
"$ref": "#/$defs/regex",
"description": "A list of regex strings."
},
"ocr": {
"$ref": "#/$defs/regex",
"description": "A list of regex strings to ocr."
}
}
},
"regex": {
"type": "array",
"items": {
"type": "string"
},
"description": "A list of regex strings.",
"uniqueItems": true,
"minItems": 1
},
Expand Down
7 changes: 2 additions & 5 deletions src/db/model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,16 @@ use std::fmt::Display;

use bson::Document;
use serde::{Deserialize, Serialize};
use serde_with_macros::skip_serializing_none;

// Models
#[skip_serializing_none]
#[derive(Serialize, Deserialize, Debug, Default)]
pub struct Muted {
#[serde(skip_serializing_if = "Option::is_none")]
pub user_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub guild_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub taken_roles: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub expires: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub reason: Option<String>,
}

Expand Down
2 changes: 1 addition & 1 deletion src/events/guild_member_addition.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@ use crate::utils::decancer::cure;

pub async fn guild_member_addition(ctx: &serenity::Context, new_member: &serenity::Member) {
cure(ctx, &None, new_member).await;
}
}
189 changes: 122 additions & 67 deletions src/events/message_create.rs
Original file line number Diff line number Diff line change
@@ -1,84 +1,139 @@
use chrono::{DateTime, Duration, NaiveDateTime, Utc};
use poise::serenity_prelude::Attachment;
use regex::Regex;
use tracing::debug;

use super::*;
use crate::utils::bot::get_data_lock;
use crate::utils::ocr;

fn contains_match(regex: &[Regex], text: &str) -> bool {
fn contains_match(regex: &Vec<Regex>, text: &str) -> bool {
regex.iter().any(|r| r.is_match(text))
}

async fn attachments_contains(attachments: &Vec<Attachment>, regex: &Vec<Regex>) -> bool {
for attachment in attachments {
debug!("Checking attachment {}", &attachment.url);

if !&attachment.content_type.as_ref().unwrap().contains("image") {
continue;
}

if contains_match(
regex,
&ocr::get_text_from_image_url(&attachment.url).await.unwrap(),
) {
return true;
}
}
false
}

pub async fn message_create(ctx: &serenity::Context, new_message: &serenity::Message) {
debug!("Received message: {}", new_message.content);

if new_message.guild_id.is_none() || new_message.author.bot {
return;
}

if let Some(message_response) = get_data_lock(ctx)
.await
.read()
.await
.configuration
.message_responses
.iter()
.find(|&response| {
// check if the message was sent in a channel that is included in the responder
response.includes.channels.iter().any(|&channel_id| channel_id == new_message.channel_id.0)
// check if the message was sent by a user that is not excluded from the responder
&& !response.excludes.roles.iter().any(|&role_id| role_id == new_message.author.id.0)
// check if the message does not match any of the excludes
&& !contains_match(&response.excludes.match_field, &new_message.content)
// check if the message matches any of the includes
&& contains_match(&response.includes.match_field, &new_message.content)
})
{
let min_age = message_response.condition.user.server_age;

if min_age != 0 {
let joined_at = ctx
.http
.get_member(new_message.guild_id.unwrap().0, new_message.author.id.0)
.await
.unwrap()
.joined_at
.unwrap()
.unix_timestamp();

let must_joined_at =
DateTime::<Utc>::from_utc(NaiveDateTime::from_timestamp(joined_at, 0), Utc);
let but_joined_at = Utc::now() - Duration::days(min_age);

if must_joined_at <= but_joined_at {
return;
}

new_message.channel_id
.send_message(&ctx.http, |m| {
m.reference_message(new_message);
match &message_response.response.embed {
Some(embed) => m.embed(|e| {
e.title(&embed.title)
.description(&embed.description)
.color(embed.color)
.fields(embed.fields.iter().map(|field| {
(field.name.clone(), field.value.clone(), field.inline)
}))
.footer(|f| {
f.text(&embed.footer.text);
f.icon_url(&embed.footer.icon_url)
})
.thumbnail(&embed.thumbnail.url)
.image(&embed.image.url)
.author(|a| {
a.name(&embed.author.name).icon_url(&embed.author.icon_url)
})
}),
None => m.content(message_response.response.message.as_ref().unwrap()),
}
})
.await
.expect("Could not reply to message author.");
}
}
let data_lock = get_data_lock(ctx).await;
let responses = &data_lock.read().await.configuration.message_responses;

for response in responses {
// check if the message was sent in a channel that is included in the responder
if !response
.includes
.channels
.iter()
.any(|&channel_id| channel_id == new_message.channel_id.0)
{
continue;
}

let excludes = &response.excludes;
// check if the message was sent by a user that is not excluded from the responder
if excludes
.roles
.iter()
.any(|&role_id| role_id == new_message.author.id.0)
{
continue;
}

let message = &new_message.content;
let contains_attachments = !new_message.attachments.is_empty();

// check if the message does not match any of the excludes
if contains_match(&excludes.match_field.text, &message) {
continue;
}

if contains_attachments && !excludes.match_field.ocr.is_empty() {
if attachments_contains(&new_message.attachments, &excludes.match_field.ocr).await {
continue;
}
}

// check if the message does match any of the includes
if !(contains_match(&response.includes.match_field.text, &message)
|| (contains_attachments
&& !response.includes.match_field.ocr.is_empty()
&& attachments_contains(
&new_message.attachments,
&response.includes.match_field.ocr,
)
.await))
{
continue;
}

let min_age = response.condition.user.server_age;

if min_age != 0 {
let joined_at = ctx
.http
.get_member(new_message.guild_id.unwrap().0, new_message.author.id.0)
.await
.unwrap()
.joined_at
.unwrap()
.unix_timestamp();

let must_joined_at =
DateTime::<Utc>::from_utc(NaiveDateTime::from_timestamp(joined_at, 0), Utc);
let but_joined_at = Utc::now() - Duration::days(min_age);

if must_joined_at <= but_joined_at {
return;
}

new_message
.channel_id
.send_message(&ctx.http, |m| {
m.reference_message(new_message);
match &response.response.embed {
Some(embed) => m.embed(|e| {
e.title(&embed.title)
.description(&embed.description)
.color(embed.color)
.fields(embed.fields.iter().map(|field| {
(field.name.clone(), field.value.clone(), field.inline)
}))
.footer(|f| {
f.text(&embed.footer.text);
f.icon_url(&embed.footer.icon_url)
})
.thumbnail(&embed.thumbnail.url)
.image(&embed.image.url)
.author(|a| {
a.name(&embed.author.name).icon_url(&embed.author.icon_url)
})
}),
None => m.content(response.response.message.as_ref().unwrap()),
}
})
.await
.expect("Could not reply to message author.");
}
}
}
18 changes: 14 additions & 4 deletions src/model/application.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use std::path::Path;
use dirs::config_dir;
use regex::Regex;
use serde::{Deserialize, Serialize};
use serde_with_macros::skip_serializing_none;

#[derive(Default, Serialize, Deserialize)]
pub struct Configuration {
Expand Down Expand Up @@ -144,15 +145,24 @@ pub struct Author {
#[derive(Serialize, Deserialize)]
pub struct Includes {
pub channels: Vec<u64>,
#[serde(rename = "match", with = "serde_regex")]
pub match_field: Vec<Regex>,
#[serde(rename = "match")]
pub match_field: Match,
}

#[derive(Serialize, Deserialize)]
pub struct Excludes {
pub roles: Vec<u64>,
#[serde(rename = "match", with = "serde_regex")]
pub match_field: Vec<Regex>,
#[serde(rename = "match")]
pub match_field: Match,
}

#[skip_serializing_none]
#[derive(Serialize, Deserialize)]
pub struct Match {
#[serde(with = "serde_regex")]
pub text: Vec<Regex>,
#[serde(with = "serde_regex")]
pub ocr: Vec<Regex>,
}

#[derive(Serialize, Deserialize)]
Expand Down
Loading

0 comments on commit 23bc1aa

Please sign in to comment.