refactor(ticket): split handler 690→4 files ≤200, ≤4 files/folder (KISS/SOLID); move forum under service
This commit is contained in:
parent
e0d3b44118
commit
56ee953a8e
9 changed files with 693 additions and 693 deletions
128
src/feature/ticket/service/mod.rs
Normal file
128
src/feature/ticket/service/mod.rs
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
//! Ticket service
|
||||
pub mod forum;
|
||||
use serenity::all::{ChannelId, CreateAttachment, CreateMessage, Http, MessageId};
|
||||
use sqlx::SqlitePool;
|
||||
use std::sync::OnceLock;
|
||||
use tracing::{info, warn};
|
||||
static POOL: OnceLock<SqlitePool> = OnceLock::new();
|
||||
pub fn set_pool(p: SqlitePool) {
|
||||
let _ = POOL.set(p);
|
||||
}
|
||||
pub fn pool() -> Option<&'static SqlitePool> {
|
||||
POOL.get()
|
||||
}
|
||||
#[rustfmt::skip]
|
||||
async fn ensure_tables(p: &SqlitePool) {
|
||||
let _ = sqlx::query("CREATE TABLE IF NOT EXISTS ticket_blacklist (guild_id TEXT NOT NULL, user_id TEXT NOT NULL, reason TEXT NOT NULL, PRIMARY KEY (guild_id, user_id))").execute(p).await;
|
||||
let _ = sqlx::query("CREATE TABLE IF NOT EXISTS ticket_claims (channel_id TEXT PRIMARY KEY, guild_id TEXT NOT NULL, claimed_by TEXT NOT NULL)").execute(p).await;
|
||||
let _ = sqlx::query("CREATE TABLE IF NOT EXISTS tickets (channel_id TEXT PRIMARY KEY, guild_id TEXT NOT NULL, opener_id TEXT NOT NULL)").execute(p).await;
|
||||
}
|
||||
#[rustfmt::skip]
|
||||
pub async fn is_blacklisted(p: &SqlitePool, g: u64, u: u64) -> Option<String> {
|
||||
ensure_tables(p).await;
|
||||
let row: Option<(String,)> = sqlx::query_as("SELECT reason FROM ticket_blacklist WHERE guild_id=? AND user_id=?").bind(g.to_string()).bind(u.to_string()).fetch_optional(p).await.unwrap_or(None);
|
||||
row.map(|r| r.0)
|
||||
}
|
||||
#[rustfmt::skip]
|
||||
pub async fn add_blacklist(p: &SqlitePool, g: u64, u: u64, r: &str) -> anyhow::Result<()> {
|
||||
ensure_tables(p).await;
|
||||
sqlx::query("INSERT OR REPLACE INTO ticket_blacklist(guild_id,user_id,reason) VALUES(?,?,?)").bind(g.to_string()).bind(u.to_string()).bind(r).execute(p).await?;
|
||||
info!(guild=g, user=u, "blacklist added");
|
||||
Ok(())
|
||||
}
|
||||
#[rustfmt::skip]
|
||||
pub async fn remove_blacklist(p: &SqlitePool, g: u64, u: u64) -> bool {
|
||||
ensure_tables(p).await;
|
||||
let res = sqlx::query("DELETE FROM ticket_blacklist WHERE guild_id=? AND user_id=?").bind(g.to_string()).bind(u.to_string()).execute(p).await;
|
||||
matches!(res, Ok(r) if r.rows_affected()>0)
|
||||
}
|
||||
#[rustfmt::skip]
|
||||
pub async fn record_ticket(p: &SqlitePool, c: u64, g: u64, o: u64) {
|
||||
ensure_tables(p).await;
|
||||
let _ = sqlx::query("INSERT OR REPLACE INTO tickets(channel_id,guild_id,opener_id) VALUES(?,?,?)").bind(c.to_string()).bind(g.to_string()).bind(o.to_string()).execute(p).await;
|
||||
}
|
||||
#[rustfmt::skip]
|
||||
pub async fn get_opener(p: &SqlitePool, c: u64) -> Option<u64> {
|
||||
ensure_tables(p).await;
|
||||
let row: Option<(String,)> = sqlx::query_as("SELECT opener_id FROM tickets WHERE channel_id=?").bind(c.to_string()).fetch_optional(p).await.unwrap_or(None);
|
||||
row.and_then(|(s,)| s.parse().ok())
|
||||
}
|
||||
#[rustfmt::skip]
|
||||
pub async fn get_claim(p: &SqlitePool, c: u64) -> Option<u64> {
|
||||
ensure_tables(p).await;
|
||||
let row: Option<(String,)> = sqlx::query_as("SELECT claimed_by FROM ticket_claims WHERE channel_id=?").bind(c.to_string()).fetch_optional(p).await.unwrap_or(None);
|
||||
row.and_then(|(s,)| s.parse().ok())
|
||||
}
|
||||
#[rustfmt::skip]
|
||||
pub async fn try_claim(p: &SqlitePool, c: u64, g: u64, cl: u64) -> bool {
|
||||
ensure_tables(p).await;
|
||||
if get_claim(p,c).await.is_some() { return false; }
|
||||
let res = sqlx::query("INSERT OR IGNORE INTO ticket_claims(channel_id,guild_id,claimed_by) VALUES(?,?,?)").bind(c.to_string()).bind(g.to_string()).bind(cl.to_string()).execute(p).await;
|
||||
matches!(res, Ok(r) if r.rows_affected()>0)
|
||||
}
|
||||
#[rustfmt::skip]
|
||||
pub async fn cleanup_ticket(p: &SqlitePool, c: u64) {
|
||||
let _ = sqlx::query("DELETE FROM tickets WHERE channel_id=?").bind(c.to_string()).execute(p).await;
|
||||
let _ = sqlx::query("DELETE FROM ticket_claims WHERE channel_id=?").bind(c.to_string()).execute(p).await;
|
||||
}
|
||||
#[rustfmt::skip]
|
||||
pub async fn fetch_transcript(h: &Http, ch: ChannelId) -> String {
|
||||
let mut all=Vec::new();
|
||||
let mut before:Option<MessageId>=None;
|
||||
loop {
|
||||
let mut b=serenity::all::GetMessages::new().limit(100);
|
||||
if let Some(id)=before { b=b.before(id); }
|
||||
let msgs=match ch.messages(h,b).await { Ok(m)=>m, Err(e)=>{warn!(error=%e,"fetch_transcript failed"); break;}};
|
||||
if msgs.is_empty(){break;}
|
||||
before=msgs.last().map(|m| m.id);
|
||||
let len=msgs.len();
|
||||
all.extend(msgs);
|
||||
if len<100{break;}
|
||||
}
|
||||
all.sort_by_key(|m| m.id);
|
||||
let mut out=String::new();
|
||||
for m in all {
|
||||
out.push_str(&format!("{} ({}):\n",m.author.name,m.author.id));
|
||||
if !m.content.is_empty(){out.push_str(&m.content); out.push('\n');}
|
||||
for e in &m.embeds {
|
||||
if let Some(t)=&e.title{out.push_str(&format!("[Embed title: {}]\n",t));}
|
||||
if let Some(d)=&e.description{out.push_str(&format!("[Embed: {}]\n",d));}
|
||||
}
|
||||
out.push_str("---\n");
|
||||
}
|
||||
if out.is_empty(){out.push_str("No messages in ticket.\n");}
|
||||
out
|
||||
}
|
||||
#[rustfmt::skip]
|
||||
pub async fn upload_hastebin(t: &str) -> Option<String> {
|
||||
let c=reqwest::Client::new();
|
||||
let b=serde_json::json!({"document":t});
|
||||
let r=c.post("https://hastebin.com/documents").json(&b).send().await.ok()?;
|
||||
if !r.status().is_success(){return None;}
|
||||
let v:serde_json::Value=r.json().await.ok()?;
|
||||
let k=v.get("key")?.as_str()?;
|
||||
Some(format!("https://hastebin.com/{k}"))
|
||||
}
|
||||
#[rustfmt::skip]
|
||||
pub async fn deliver_transcript(h: &Http, ch: ChannelId, closer: u64, tr: String, url: Option<String>) {
|
||||
const LOG: u64=1509503717127356446;
|
||||
let lc=ChannelId::new(LOG);
|
||||
if let Some(u)=url.clone(){
|
||||
let _=lc.send_message(h,CreateMessage::new().content(format!("Ticket <#{ch}> closed by <@{closer}>\nTranscript: {u}"))).await;
|
||||
} else {
|
||||
let a=CreateAttachment::bytes(tr.clone().into_bytes(),"transcript.txt");
|
||||
let _=lc.send_message(h,CreateMessage::new().content(format!("Ticket <#{ch}> closed by <@{closer}>")).add_file(a)).await;
|
||||
}
|
||||
if let Some(p)=pool(){
|
||||
if let Some(o)=get_opener(p,ch.get()).await{
|
||||
if let Ok(dm)=serenity::all::UserId::new(o).create_dm_channel(h).await{
|
||||
if let Some(u)=url{
|
||||
let _=dm.send_message(h,CreateMessage::new().content(format!("Your ticket <#{ch}> was closed. Transcript: {u}"))).await;
|
||||
} else {
|
||||
let a=CreateAttachment::bytes(tr.into_bytes(),"transcript.txt");
|
||||
let _=dm.send_message(h,CreateMessage::new().content(format!("Your ticket <#{ch}> was closed.")).add_file(a)).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue