aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCara Salter <cara@devcara.com>2022-05-03 13:57:09 -0400
committerCara Salter <cara@devcara.com>2022-05-03 13:57:09 -0400
commit5999e6a803a7b848acf054918fec9ee5024d5697 (patch)
treeeae1f3986c41f7d7c1a956e4606a8120c6032e05
parent8f4277c55a2079edf1c9a69383c353e1cb9ef55c (diff)
downloadglitch-ng-5999e6a803a7b848acf054918fec9ee5024d5697.tar.gz
glitch-ng-5999e6a803a7b848acf054918fec9ee5024d5697.zip
filter: Initial message filter implementation
Also a custom error type, tracing_subscriber, and unsafe impls
-rw-r--r--Cargo.lock167
-rw-r--r--Cargo.toml7
-rw-r--r--flake.lock17
-rw-r--r--flake.nix22
-rw-r--r--migrations/20220429210913_message_filter.sql8
-rw-r--r--migrations/20220429214038_guild_ids_filters.sql2
-rw-r--r--src/commands/filters.rs82
-rw-r--r--src/commands/mod.rs1
-rw-r--r--src/errors.rs21
-rw-r--r--src/handler.rs17
-rw-r--r--src/main.rs11
-rw-r--r--src/models.rs73
12 files changed, 420 insertions, 8 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 791625a..15c782d 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -3,6 +3,15 @@
version = 3
[[package]]
+name = "addr2line"
+version = "0.17.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b9ecd88a8c8378ca913a680cd98f0f13ac67383d35993f86c90a70e3f137816b"
+dependencies = [
+ "gimli",
+]
+
+[[package]]
name = "adler"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -20,6 +29,15 @@ dependencies = [
]
[[package]]
+name = "aho-corasick"
+version = "0.7.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
name = "ansi_term"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -71,6 +89,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
+name = "backtrace"
+version = "0.3.65"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "11a17d453482a265fd5f8479f2a3f405566e6ca627837aaddb85af8b1ab8ef61"
+dependencies = [
+ "addr2line",
+ "cc",
+ "cfg-if",
+ "libc",
+ "miniz_oxide 0.5.1",
+ "object",
+ "rustc-demangle",
+]
+
+[[package]]
name = "base64"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -136,6 +169,33 @@ dependencies = [
]
[[package]]
+name = "color-eyre"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ebf286c900a6d5867aeff75cfee3192857bb7f24b547d4f0df2ed6baa812c90"
+dependencies = [
+ "backtrace",
+ "color-spantrace",
+ "eyre",
+ "indenter",
+ "once_cell",
+ "owo-colors",
+ "tracing-error",
+]
+
+[[package]]
+name = "color-spantrace"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1ba75b3d9449ecdccb27ecbc479fdc0b87fa2dd43d2f8298f9bf0e59aacc8dce"
+dependencies = [
+ "once_cell",
+ "owo-colors",
+ "tracing-core",
+ "tracing-error",
+]
+
+[[package]]
name = "core-foundation"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -324,6 +384,16 @@ dependencies = [
]
[[package]]
+name = "eyre"
+version = "0.6.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4c2b6b5a29c02cdc822728b7d7b8ae1bab3e3b05d44522770ddd49722eeac7eb"
+dependencies = [
+ "indenter",
+ "once_cell",
+]
+
+[[package]]
name = "fastrand"
version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -341,7 +411,7 @@ dependencies = [
"cfg-if",
"crc32fast",
"libc",
- "miniz_oxide",
+ "miniz_oxide 0.4.4",
]
[[package]]
@@ -383,6 +453,7 @@ checksum = "f73fe65f54d1e12b726f517d3e2135ca3125a437b6d998caf1962961f7172d9e"
dependencies = [
"futures-channel",
"futures-core",
+ "futures-executor",
"futures-io",
"futures-sink",
"futures-task",
@@ -406,6 +477,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3"
[[package]]
+name = "futures-executor"
+version = "0.3.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9420b90cfa29e327d0429f19be13e7ddb68fa1cccb09d65e5706b8c7a749b8a6"
+dependencies = [
+ "futures-core",
+ "futures-task",
+ "futures-util",
+]
+
+[[package]]
name = "futures-intrusive"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -423,6 +505,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc4045962a5a5e935ee2fdedaa4e08284547402885ab326734432bed5d12966b"
[[package]]
+name = "futures-macro"
+version = "0.3.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "33c1e13800337f4d4d7a316bf45a567dbcb6ffe087f16424852d97e97a91f512"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
name = "futures-sink"
version = "0.3.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -443,6 +536,7 @@ dependencies = [
"futures-channel",
"futures-core",
"futures-io",
+ "futures-macro",
"futures-sink",
"futures-task",
"memchr",
@@ -473,22 +567,32 @@ dependencies = [
]
[[package]]
+name = "gimli"
+version = "0.26.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "78cc372d058dcf6d5ecd98510e7fbc9e5aec4d21de70f65fea8fecebcd881bd4"
+
+[[package]]
name = "glitch-ng"
version = "0.6.0"
dependencies = [
"chrono",
+ "color-eyre",
"dotenv",
+ "futures",
"log",
"poise",
"rand",
+ "regex",
"reqwest",
"serde",
"serde_json",
"serenity",
"sqlx",
+ "thiserror",
"tokio",
"tracing",
- "tracing-subscriber",
+ "tracing-subscriber 0.2.25",
]
[[package]]
@@ -664,6 +768,12 @@ dependencies = [
]
[[package]]
+name = "indenter"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683"
+
+[[package]]
name = "indexmap"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -807,6 +917,15 @@ dependencies = [
]
[[package]]
+name = "miniz_oxide"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d2b29bd4bc3f33391105ebee3589c19197c4271e3e5a9ec9bfe8127eeff8f082"
+dependencies = [
+ "adler",
+]
+
+[[package]]
name = "mio"
version = "0.7.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -896,6 +1015,15 @@ dependencies = [
]
[[package]]
+name = "object"
+version = "0.28.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "40bec70ba014595f99f7aa110b84331ffe1ee9aece7fe6f387cc7e3ecda4d456"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
name = "once_cell"
version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -941,6 +1069,12 @@ dependencies = [
]
[[package]]
+name = "owo-colors"
+version = "3.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "decf7381921fea4dcb2549c5667eda59b3ec297ab7e2b5fc33eac69d2e7da87b"
+
+[[package]]
name = "parking_lot"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1094,6 +1228,8 @@ version = "1.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461"
dependencies = [
+ "aho-corasick",
+ "memchr",
"regex-syntax",
]
@@ -1180,6 +1316,12 @@ dependencies = [
]
[[package]]
+name = "rustc-demangle"
+version = "0.1.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342"
+
+[[package]]
name = "rustls"
version = "0.19.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1757,6 +1899,16 @@ dependencies = [
]
[[package]]
+name = "tracing-error"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d686ec1c0f384b1277f097b2f279a2ecc11afe8c133c1aabf036a27cb4cd206e"
+dependencies = [
+ "tracing",
+ "tracing-subscriber 0.3.11",
+]
+
+[[package]]
name = "tracing-log"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1800,6 +1952,17 @@ dependencies = [
]
[[package]]
+name = "tracing-subscriber"
+version = "0.3.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4bc28f93baff38037f64e6f43d34cfa1605f27a49c34e8a04c5e78b0babf2596"
+dependencies = [
+ "sharded-slab",
+ "thread_local",
+ "tracing-core",
+]
+
+[[package]]
name = "try-lock"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/Cargo.toml b/Cargo.toml
index 88342e4..a21e76d 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -12,11 +12,18 @@ log = "0.4"
tracing = "0.1"
tracing-subscriber = "0.2"
+thiserror = "1"
+color-eyre = "0.6"
+
rand = "0.8"
reqwest = "0.11"
chrono = { version = "0.4", features = ["serde"] }
+regex = "1"
+
+futures = "0.3"
+
[dependencies.serenity]
git = "https://github.com/serenity-rs/serenity"
branch = "next"
diff --git a/flake.lock b/flake.lock
index ce6187f..90c5af7 100644
--- a/flake.lock
+++ b/flake.lock
@@ -15,6 +15,22 @@
"type": "github"
}
},
+ "mozpkgs": {
+ "flake": false,
+ "locked": {
+ "lastModified": 1650459918,
+ "narHash": "sha256-sroCK+QJTmoXtcRkwZyKOP9iAYOPID2Bwdxn4GkG16w=",
+ "owner": "mozilla",
+ "repo": "nixpkgs-mozilla",
+ "rev": "e1f7540fc0a8b989fb8cf701dc4fd7fc76bcf168",
+ "type": "github"
+ },
+ "original": {
+ "owner": "mozilla",
+ "repo": "nixpkgs-mozilla",
+ "type": "github"
+ }
+ },
"naersk": {
"inputs": {
"nixpkgs": "nixpkgs"
@@ -64,6 +80,7 @@
"root": {
"inputs": {
"flake-utils": "flake-utils",
+ "mozpkgs": "mozpkgs",
"naersk": "naersk",
"nixpkgs": "nixpkgs_2"
}
diff --git a/flake.nix b/flake.nix
index ceb4ddb..d9c9ba5 100644
--- a/flake.nix
+++ b/flake.nix
@@ -2,13 +2,29 @@
inputs = {
flake-utils.url = "github:numtide/flake-utils";
naersk.url = "github:nix-community/naersk";
+
+ mozpkgs = {
+ url = "github:mozilla/nixpkgs-mozilla";
+ flake = false;
+ };
};
- outputs = { self, nixpkgs, flake-utils, naersk }:
+ outputs = { self, nixpkgs, flake-utils, naersk, mozpkgs }:
flake-utils.lib.eachDefaultSystem (
system: let
pkgs = nixpkgs.legacyPackages."${system}";
- naersk-lib = naersk.lib."${system}";
+
+ mozilla = pkgs.callPackage (mozpkgs + "/package-set.nix") {};
+ rust = (mozilla.rustChannelOf {
+ date = "2022-04-30";
+ channel = "nightly";
+ sha256= "pKjVkFhROJV0+JZKx2n4Fn9fJFuGX8pZW3LjUAN+Jx0=";
+ }).rust;
+
+ naersk-lib = naersk.lib."${system}".override {
+ cargo = rust;
+ rustc = rust;
+ };
in
rec {
# `nix build`
@@ -95,7 +111,7 @@
# `nix develop`
devShell = pkgs.mkShell {
- nativeBuildInputs = with pkgs; [ rustc cargo ] ++ deps;
+ nativeBuildInputs = with pkgs; [ rust ] ++ deps;
};
}
);
diff --git a/migrations/20220429210913_message_filter.sql b/migrations/20220429210913_message_filter.sql
new file mode 100644
index 0000000..95b7844
--- /dev/null
+++ b/migrations/20220429210913_message_filter.sql
@@ -0,0 +1,8 @@
+-- Add migration script here
+
+CREATE TYPE filter_action AS ENUM ( 'delete', 'review' );
+CREATE TABLE message_filter(
+ id SERIAL PRIMARY KEY,
+ pattern TEXT NOT NULL,
+ action filter_action NOT NULL DEFAULT 'review'
+);
diff --git a/migrations/20220429214038_guild_ids_filters.sql b/migrations/20220429214038_guild_ids_filters.sql
new file mode 100644
index 0000000..302b809
--- /dev/null
+++ b/migrations/20220429214038_guild_ids_filters.sql
@@ -0,0 +1,2 @@
+-- Add migration script here
+ALTER TABLE message_filter ADD COLUMN guild_id TEXT NOT NULL;
diff --git a/src/commands/filters.rs b/src/commands/filters.rs
new file mode 100644
index 0000000..35fefac
--- /dev/null
+++ b/src/commands/filters.rs
@@ -0,0 +1,82 @@
+use crate::{Context, Error, models::{FilterAction, MessageFilter}};
+use poise::{serenity_prelude as serenity, AutocompleteChoice};
+
+use std::str::FromStr;
+
+use futures::{Stream, StreamExt};
+
+/// Base command for all filter actions
+///
+/// Provides a CRUD interface for managing automatic message filters
+#[poise::command(slash_command, prefix_command)]
+pub async fn filter(ctx: Context<'_>) -> Result<(), Error> {
+ Ok(())
+}
+
+/// Lists message filters
+///
+/// Usage:
+/// filter list
+#[poise::command(slash_command, ephemeral, prefix_command)]
+pub async fn list(ctx: Context<'_>) -> Result<(), Error> {
+ let pool = ctx.data().pg.lock().unwrap().clone();
+
+ let filters = sqlx::query_as::<_, MessageFilter>("SELECT * FROM message_filter WHERE guild_id=$1").bind(ctx.guild().unwrap().id.0.to_string())
+ .fetch_all(&pool).await?;
+
+ let mut list = String::from("");
+ if filters.len() == 0 {
+ list = "No filters set, try adding one with `/filter add`!".to_string();
+ } else {
+ for f in filters {
+ list.push_str(&format!("{}\n", f.to_string()));
+ }
+ }
+
+ ctx.send(|m| {
+ m.embed(|e| {
+ e.title("Message Filter List");
+ e.description(list);
+ e
+ })
+ }).await?;
+ Ok(())
+}
+
+/// Creates a new message filter
+///
+/// Usage:
+/// filter add <pattern> <action>
+///
+/// Where <action> is one of "review" or "delete"
+#[poise::command(slash_command, ephemeral, prefix_command)]
+pub async fn add(ctx: Context<'_>,
+ #[description = "The regular expression to match against"]
+ regex: String,
+ #[description = "The action to take when the expression is matched"]
+ #[autocomplete = "ac_action"]
+ action: String,
+ ) -> Result<(), Error> {
+
+ let pool = ctx.data().pg.lock().unwrap().clone();
+
+ let action = FilterAction::from_str(&action)?;
+
+ sqlx::query("INSERT INTO message_filter (pattern, action, guild_id) VALUES ($1, $2, $3)")
+ .bind(regex)
+ .bind(action as FilterAction)
+ .bind(ctx.guild().unwrap().id.0.to_string())
+ .execute(&pool)
+ .await?;
+
+ ctx.say("Sounds good! I've written that down and will keep a close eye out").await?;
+
+ Ok(())
+}
+
+
+async fn ac_action(_ctx: Context<'_>, partial: String) -> impl Stream<Item = String> {
+ futures::stream::iter(&["review", "delete"])
+ .filter(move |name| futures::future::ready(name.starts_with(&partial)))
+ .map(|name| name.to_string())
+}
diff --git a/src/commands/mod.rs b/src/commands/mod.rs
index 3ffb136..2ce5d6a 100644
--- a/src/commands/mod.rs
+++ b/src/commands/mod.rs
@@ -3,3 +3,4 @@ pub mod meta;
pub mod osu;
pub mod pony;
pub mod reactionroles;
+pub mod filters;
diff --git a/src/errors.rs b/src/errors.rs
new file mode 100644
index 0000000..6e78011
--- /dev/null
+++ b/src/errors.rs
@@ -0,0 +1,21 @@
+use poise::FrameworkError;
+
+
+#[derive(Debug, thiserror::Error)]
+pub enum Error {
+ #[error("Serenity: {0}")]
+ Serenity(#[from] serenity::prelude::SerenityError),
+
+ #[error("SQL: {0}")]
+ Sql(#[from] sqlx::Error),
+
+ #[error("Unknown: {0}")]
+ Unknown(String),
+
+ #[error("Catch-all: {0}")]
+ CatchAll(#[from] Box<dyn std::error::Error>),
+}
+
+unsafe impl Send for Error { }
+unsafe impl Sync for Error { }
+
diff --git a/src/handler.rs b/src/handler.rs
index 7b4568f..a52b31a 100644
--- a/src/handler.rs
+++ b/src/handler.rs
@@ -1,6 +1,6 @@
use poise::serenity_prelude as serenity;
-use crate::models::ReactionRole;
+use crate::models::*;
use crate::{Data, Error};
use tracing::info;
@@ -72,7 +72,20 @@ pub async fn event_handler(
}
add_reaction.delete(&ctx.http).await?;
- }
+ },
+ poise::Event::Message { new_message } => {
+ let current_user = ctx.http.get_current_user().await?;
+
+ if new_message.author.id.0 != current_user.id.0 {
+ // This message does *not* belong to the bot
+ println!("Message from not-bot");
+ let filters = sqlx::query_as::<_, MessageFilter>(
+ "SELECT * FROM message_filter WHERE guild_id=$1")
+ .bind(new_message.guild_id.unwrap().0.to_string())
+ .fetch_all(&pool)
+ .await?;
+ }
+ },
_ => (),
}
}
diff --git a/src/main.rs b/src/main.rs
index 88d612e..38a849b 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,4 +1,4 @@
-#![forbid(missing_docs)]
+#![deny(missing_docs)]
/*!
* Full rewrite of the [Glitch](https://glitchbot.net) bot in Poise with slash commands
*
@@ -17,6 +17,7 @@ type Context<'a> = poise::Context<'a, Data, Error>;
mod commands;
mod handler;
mod models;
+mod errors;
/// Contains data shared between all commands
pub struct Data {
@@ -70,6 +71,7 @@ async fn register(ctx: Context<'_>, #[flag] global: bool) -> Result<(), Error> {
async fn main() {
// Initialize environment and logging
dotenv().unwrap();
+ color_eyre::install().unwrap();
tracing_subscriber::fmt::init();
info!("Initialized logging");
let options = poise::FrameworkOptions {
@@ -94,6 +96,13 @@ async fn main() {
],
..commands::reactionroles::rroles()
},
+ poise::Command {
+ subcommands: vec![
+ commands::filters::list(),
+ commands::filters::add(),
+ ],
+ ..commands::filters::filter()
+ },
],
// This requires a closure, for some reason
on_error: |error| Box::pin(on_error(error)),
diff --git a/src/models.rs b/src/models.rs
index 09d5d7c..b8dd5b1 100644
--- a/src/models.rs
+++ b/src/models.rs
@@ -1,3 +1,9 @@
+use std::str::FromStr;
+
+use poise::AutocompleteChoice;
+
+use crate::errors::Error;
+
/**
* Describes a Reaction Role as it appears in SQL
*/
@@ -17,3 +23,70 @@ pub struct ReactionRole {
/// The ID of the role to be toggled by the menu option
pub role_id: String,
}
+
+
+#[derive(Debug, Clone, sqlx::Type)]
+#[sqlx(type_name="filter_action", rename_all="lowercase")]
+pub enum FilterAction {
+ Review,
+ Delete
+}
+/**
+ * Describes a message filter as it appears in SQL
+ */
+
+#[derive(Debug, Clone, sqlx::FromRow)]
+pub struct MessageFilter {
+ /// Primary Key
+ pub id: i32,
+
+ /// Pattern
+ pub pattern: String,
+
+ pub action: FilterAction,
+
+ pub guild_id: String,
+}
+
+impl FromStr for FilterAction {
+ type Err = Error;
+ fn from_str(s: &str) -> Result<Self, Self::Err> {
+ match s {
+ "review" => Ok(FilterAction::Review),
+ "delete" => Ok(FilterAction::Delete),
+ _ => {
+ return Err(Error::Unknown("invalid option".to_string()));
+ }
+ }
+ }
+}
+
+impl ToString for FilterAction {
+ fn to_string(&self) -> String {
+ match self {
+ FilterAction::Review => "Review".to_string(),
+ FilterAction::Delete => "Delete".to_string(),
+ }
+ }
+}
+
+impl ToString for MessageFilter {
+ fn to_string(&self) -> String {
+ format!("`{}`: {}", self.pattern, self.action.to_string())
+ }
+}
+
+impl From<FilterAction> for AutocompleteChoice<String> {
+ fn from(m: FilterAction) -> Self {
+ match m {
+ FilterAction::Review => AutocompleteChoice {
+ name: "Flag the message for review and send it to a channel for moderators".to_string(),
+ value: "review".to_string()
+ },
+ FilterAction::Delete => AutocompleteChoice {
+ name: "Deletes the message and logs the deletion in a channel for moderators".to_string(),
+ value: "delete".to_string()
+ }
+ }
+ }
+}