Skip to content

Commit

Permalink
Add config-creation forum autoresponse
Browse files Browse the repository at this point in the history
  • Loading branch information
jamesbt365 committed Dec 19, 2024
1 parent 2008b64 commit 2407469
Show file tree
Hide file tree
Showing 6 changed files with 168 additions and 11 deletions.
9 changes: 6 additions & 3 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,4 @@ reqwest = "0.11.22"
hex = "0.4.3"
to-arraystring = "0.1.3"
arrayvec = "0.7.4"
chrono = "0.4.39"
142 changes: 142 additions & 0 deletions src/events/config_creation.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
use crate::{commands::ERROR_COLOUR, Data};
use poise::serenity_prelude::{
self as serenity, ChannelId, CreateEmbed, CreateMessage, GuildChannel,
};

pub async fn thread_create(ctx: &serenity::Context, data: &Data, thread: &GuildChannel) {
// TODO: make this an environment variable?
if thread.parent_id != Some(ChannelId::from(1149679256981479464)) {
return;
}

// Already responded to this thread.
if data.forum_threads.read().unwrap().contains(&thread.id) {
return;
}

// This is jank until serenity 0.13 or switching tablet bot to serenity@next.
// This will prevent the bot from responding if it just joins the thread without it being a new thread.
if chrono::Utc::now() - *thread.id.created_at() > chrono::Duration::seconds(5) {
return;
}

data.forum_threads.write().unwrap().insert(thread.id);
collect_and_action(ctx, thread).await;
}

macro_rules! check_device {
($device:expr, $vendor_id:expr, $pattern:pat $(if $guard:expr)?) => {
(($device.vendor_id == $vendor_id) && matches!($device.product_id, $pattern $(if $guard)?))
};
}

async fn collect_and_action(ctx: &serenity::Context, thread: &GuildChannel) {
if let Some(message) = serenity::MessageCollector::new(ctx)
.timeout(std::time::Duration::from_secs(10))
.channel_id(thread.id)
.await
{
println!("{}", message.id);

if message.attachments.is_empty() {
let desc = "Sending diagnostics is a mandatory step! Please follow the instructions \
below or this request will be deleted.\n\n- Start OpenTabletDriver (if it \
is not already running)\n- Go to `Help` -> `Export Diagnostics` in the \
top menu\n- Save the file, then upload here.";

let embed = CreateEmbed::new()
.title("Exporting diagnostics")
.description(desc)
.color(ERROR_COLOUR);

let _ = thread
.id
.send_message(ctx, CreateMessage::new().embed(embed))
.await;
return;
}

for attachment in message.attachments {
// 5MB, unlikely to be a diagnostic and massive if it is one.
if attachment.size > 5000000 {
println!("big attachment");
continue;
}

let will_eval = match attachment.content_type {
Some(ref s) if s.contains("application/json") => true,
Some(ref s) if s.contains("text/plain") => true,
// user likely stripped the extension
None => true,
// something else, likely an image
_ => false,
};

if will_eval {
let Ok(data) = attachment.download().await else {
println!("Failed to download attachment, message was likely deleted.");
return;
};

let Ok(diag) = serde_json::from_slice::<MinimifiedDiagSchema>(&data) else {
println!("Could not parse attachment as diagnostic.");
continue;
};

let mut maybe_dump = None;
// Checks if it is a known problematic device that needs a string dump
// Right now it just ends at the first mention of a problematic device, but not
// many people ask for config support when they have multiple tablets plugged in?
for device in diag.hid_devices {
if check_device!(device, 21827, 129)
|| check_device!(device, 9580, 97 | 100 | 109 | 110 | 111)
{
maybe_dump = Some(device);
break;
}
}

if let Some(device) = maybe_dump {
let description = format!(
"Your device is known to have tricky identifiers to work with, and such a \
device string dump will help support this tablet faster. Please follow \
these instructions below.\n\n- Start OpenTabletDriver (if it is not \
already running)\n- Go to `Tablets` -> `Device string reader` in the top \
menu\n- Put `{}` in the top box\n- `{}` in the middle box\n- Press `Dump \
all`\n- Save the file, then upload here.",
device.vendor_id, device.product_id
);

let embed = CreateEmbed::new()
.title("String dump required")
.description(description)
.color(ERROR_COLOUR);

let _ = thread
.send_message(ctx, CreateMessage::new().embed(embed))
.await;
break;
}
}
}
}
}

#[derive(serde::Deserialize)]
struct MinimifiedDiagSchema {
#[serde(rename = "HID Devices")]
hid_devices: Vec<HidDevice>,
}

#[derive(serde::Deserialize)]
#[serde(rename_all = "PascalCase")]
struct HidDevice {
#[serde(rename = "VendorID")]
vendor_id: i16,
#[serde(rename = "ProductID")]
product_id: i16,
// I will put these in when I have a "surefire" way of getting the intended device.
// I will probably use them for checking if udev rules are installed (to get the right lengths)
//input_report_length: i16,
//output_report_length: i16,
}
8 changes: 4 additions & 4 deletions src/events/issues/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,10 +80,10 @@ pub async fn message(data: &Data, ctx: &Context, message: &Message) {
.timeout(Duration::from_secs(60))
.await
{
let has_perms = press.member.as_ref().map_or(false, |member| {
member.permissions.map_or(false, |member_perms| {
member_perms.contains(Permissions::MANAGE_MESSAGES)
})
let has_perms = press.member.as_ref().is_some_and(|member| {
member
.permissions
.is_some_and(|member_perms| member_perms.contains(Permissions::MANAGE_MESSAGES))
});

// Users who do not own the message or have permissions cannot execute the interactions.
Expand Down
5 changes: 4 additions & 1 deletion src/events/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use poise::serenity_prelude as serenity;
use crate::{Data, Error};

pub mod code;
pub mod config_creation;
pub mod issues;

pub async fn event_handler(
Expand All @@ -11,14 +12,16 @@ pub async fn event_handler(
_framework: poise::FrameworkContext<'_, Data, Error>,
data: &Data,
) -> Result<(), Error> {
#[allow(clippy::single_match)]
match event {
serenity::FullEvent::Message { new_message } => {
if !new_message.author.bot && new_message.guild_id.is_some() {
issues::message(data, ctx, new_message).await;
code::message(ctx, new_message).await;
}
}
serenity::FullEvent::ThreadCreate { thread } => {
config_creation::thread_create(ctx, data, thread).await;
}
_ => (),
}

Expand Down
14 changes: 11 additions & 3 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,20 @@ pub(crate) mod events;
pub(crate) mod formatting;
pub(crate) mod structures;

use std::collections::HashSet;
use std::sync::RwLock;
use std::time::Duration;
use std::{env, sync::Arc};

use octocrab::Octocrab;
use poise::serenity_prelude::{self as serenity, GatewayIntents};
use poise::serenity_prelude::{self as serenity, ChannelId, GatewayIntents};
use structures::BotState;

pub struct Data {
pub octocrab: Arc<Octocrab>,
pub state: RwLock<BotState>,
/// Small manual cache for threads we have already responded to.
pub forum_threads: RwLock<HashSet<ChannelId>>,
}
type Error = Box<dyn std::error::Error + Send + Sync>;
type Context<'a> = poise::Context<'a, Data, Error>;
Expand Down Expand Up @@ -119,13 +122,18 @@ async fn main() {
Box::pin(async move {
println!("Logged in as {}", ready.user.name);
poise::builtins::register_globally(ctx, &framework.options().commands).await?;
Ok(Data { octocrab, state })
Ok(Data {
octocrab,
state,
forum_threads: RwLock::new(HashSet::new()),
})
})
});

let intents = GatewayIntents::GUILD_MESSAGES
| GatewayIntents::DIRECT_MESSAGES
| GatewayIntents::MESSAGE_CONTENT;
| GatewayIntents::MESSAGE_CONTENT
| GatewayIntents::GUILDS;

let mut client = serenity::Client::builder(discord_token, intents)
.framework(framework)
Expand Down

0 comments on commit 2407469

Please sign in to comment.