diff --git a/src/database/migrations/0001_init.cql b/src/database/migrations/0001_init.cql index c292854..9fc364c 100644 --- a/src/database/migrations/0001_init.cql +++ b/src/database/migrations/0001_init.cql @@ -60,12 +60,11 @@ CREATE TABLE IF NOT EXISTS payphone.participants ( PRIMARY KEY (call_id, user_id, channel_id) ); --- TTL 30 days. -CREATE TABLE IF NOT EXISTS payphone.messages ( +CREATE TYPE IF NOT EXISTS payphone.message ( + id BIGINT, call_id BIGINT, - message_id BIGINT, - author_id BIGINT, + author_id BIGINT, author_name TEXT, author_avatar_url TEXT, author_mask ASCII, @@ -82,6 +81,15 @@ CREATE TABLE IF NOT EXISTS payphone.messages ( is_deleted BOOLEAN, is_edited BOOLEAN, is_censored BOOLEAN, +); + +-- TTL 30 days. +CREATE TABLE IF NOT EXISTS payphone.messages ( + call_id BIGINT, + message_id BIGINT, + author_id BIGINT, + + data FROZEN, PRIMARY KEY (call_id, message_id) ); @@ -95,21 +103,10 @@ CREATE TABLE IF NOT EXISTS payphone.servers ( CREATE TABLE IF NOT EXISTS payphone.reports ( report_id BIGINT, + user_id BIGINT, - message_author_id BIGINT, - message_author_name TEXT, - message_author_avatar_url TEXT, - message_author_mask ASCII, - - message_id BIGINT, - message_content TEXT, - message_attachments LIST, - message_sticker BIGINT, - - message_replying_to_id BIGINT, - message_replying_to_content TEXT, - message_replying_to_attachments LIST, - message_replying_to_sticker BIGINT, + message FROZEN, + replying_to FROZEN, created_at TIMESTAMP, reviewed_at TIMESTAMP, @@ -119,28 +116,18 @@ CREATE TABLE IF NOT EXISTS payphone.reports ( reviewed_by_id BIGINT, resolution ASCII, - PRIMARY KEY (message_author_id, report_id) + PRIMARY KEY (user_id, report_id) ); CREATE TABLE IF NOT EXISTS payphone.user_incidents ( user_id BIGINT, incident_id BIGINT, + report_id BIGINT, -- ban, warning type ASCII, - call_id BIGINT, - report_id BIGINT, - - message_id BIGINT, - message_content TEXT, - message_attachments LIST, - message_sticker BIGINT, - - message_replying_to_id BIGINT, - message_replying_to_content TEXT, - message_replying_to_attachments LIST, - message_replying_to_sticker BIGINT, + message FROZEN, reason TEXT, @@ -164,4 +151,4 @@ CREATE TABLE IF NOT EXISTS payphone.server_incidents ( created_at TIMESTAMP, expires_at TIMESTAMP, duration ASCII -); \ No newline at end of file +); diff --git a/src/database/models.rs b/src/database/models.rs index f68937f..e89e7a4 100644 --- a/src/database/models.rs +++ b/src/database/models.rs @@ -1,4 +1,5 @@ use scylla::{ + DeserializeValue, SerializeValue, client::session::Session, deserialize::row::DeserializeRow, errors::{ExecutionError, SingleRowError}, @@ -11,12 +12,15 @@ use thiserror::Error; use time::{OffsetDateTime, UtcDateTime}; use twilight_http::Client; use twilight_model::{ + channel::Message as TwilightMessage, guild::{Guild as TwilightGuild, GuildInfo as TwilightGuildInfo}, id::Id, user::User as TwilightUser, }; -use crate::{constants::DEFAULT_PREFIX, utils::get_random_u64}; +use crate::{ + constants::DEFAULT_PREFIX, groups::calls::events::DisplayProfile, utils::get_random_u64, +}; #[derive(Debug, Error)] pub enum DatabaseOrDiscordError { @@ -827,3 +831,152 @@ impl Call { Ok(()) } } + +#[derive(Debug, Clone, SerializeValue, DeserializeValue)] +pub struct MessageData { + pub id: i64, + pub call_id: i64, + + pub author_id: i64, + pub author_name: String, + pub author_avatar_url: String, + pub author_mask: Option, + + pub content: String, + pub attachments: Vec, + pub sticker: Option, + + pub propagation_message_id: Option, + pub propagation_channel_id: Option, + + pub replying_to_id: Option, + + pub is_deleted: bool, + pub is_edited: bool, + pub is_censored: bool, +} + +#[derive(Debug, Error)] +#[error("The message was missing either the event's message, the user's profile, or the call ID")] +pub struct MessageBuilderError; + +#[derive(Default)] +pub struct MessageBuilder { + message: Option, + profile: Option, + call_id: Option, +} + +impl MessageBuilder { + fn new() -> Self { + Self { + message: None, + profile: None, + call_id: None, + } + } + + pub fn message(mut self, message: TwilightMessage) -> Self { + self.message = Some(message); + self + } + + pub fn profile(mut self, profile: DisplayProfile) -> Self { + self.profile = Some(profile); + self + } + + pub fn call_id(mut self, call_id: u64) -> Self { + self.call_id = Some(call_id); + self + } + + pub fn build(self) -> Result { + let message = self.message.ok_or(MessageBuilderError)?; + let profile = self.profile.ok_or(MessageBuilderError)?; + let call_id = self.call_id.ok_or(MessageBuilderError)?; + + let data = MessageData { + id: u64_to_i64(message.id.get()), + call_id: u64_to_i64(call_id), + author_id: u64_to_i64(profile.id), + author_name: profile.name.clone(), + author_avatar_url: profile.avatar_url.clone(), + author_mask: profile.mask, + content: message.content, + attachments: message.attachments.iter().map(|a| a.url.clone()).collect(), + sticker: message + .sticker_items + .first() + .map(|s| u64_to_i64(s.id.get())), + propagation_message_id: None, + propagation_channel_id: None, + replying_to_id: None, + is_deleted: false, + is_edited: message.edited_timestamp.is_some(), + is_censored: false, + }; + + Ok(Message { + call_id, + message_id: message.id.get(), + author_id: profile.id, + data, + }) + } +} + +#[derive(Debug, Clone)] +pub struct Message { + pub call_id: u64, + pub message_id: u64, + pub author_id: u64, + pub data: MessageData, +} + +impl<'a> Object<'a> for Message { + type Row = (i64, i64, i64, MessageData); + + fn from_row(row: Self::Row) -> Self { + Self { + call_id: i64_to_u64(row.0), + message_id: i64_to_u64(row.1), + author_id: i64_to_u64(row.2), + data: row.3, + } + } + + fn to_row(value: Self) -> Self::Row { + ( + u64_to_i64(value.call_id), + u64_to_i64(value.message_id), + u64_to_i64(value.author_id), + value.data, + ) + } +} + +impl Message { + pub fn build() -> MessageBuilder { + MessageBuilder::new() + } + + pub async fn insert(&self, session: &Session) -> Result<(), DatabaseError> { + session + .query_unpaged( + r#" + INSERT INTO payphone.messages ( + call_id, + message_id, + author_id, + data + ) VALUES (?, ?, ?, ?); + "#, + Self::to_row(self.clone()), + ) + .await + .map_err(DatabaseError::QueryError)?; + + Ok(()) + } +} diff --git a/src/groups/calls/events.rs b/src/groups/calls/events.rs index e14edcd..fbe8804 100644 --- a/src/groups/calls/events.rs +++ b/src/groups/calls/events.rs @@ -5,7 +5,7 @@ use twilight_model::{gateway::payload::incoming::MessageCreate, id::Id}; use twilight_validate::request::webhook_username; use crate::{ - database::models::{Call, Channel}, + database::models::{Call, Channel, Message}, discord::images, framework::{ events::traits::{EventResult, OnMessageHandler}, @@ -99,6 +99,17 @@ impl OnMessageHandler for MessageRedirector { let profile = get_display_profile(event); + Message::build() + .call_id(call.id) + .message(event.0.clone()) + .profile(profile.clone()) + .build() + .expect( + "The message builder was missing either the call ID, the message, or the profile", + ) + .insert(&rt.state.db) + .await?; + rt.http .execute_webhook(Id::new(webhook_id), &webhook_token) .allowed_mentions(None) @@ -118,9 +129,12 @@ impl OnMessageHandler for MessageRedirector { } } -struct DisplayProfile { +#[derive(Debug, Clone)] +pub struct DisplayProfile { + pub id: u64, pub name: String, pub avatar_url: String, + pub mask: Option, } fn get_display_profile(event: &MessageCreate) -> DisplayProfile { @@ -153,5 +167,10 @@ fn get_display_profile(event: &MessageCreate) -> DisplayProfile { event.author.discriminator as u64, ); - DisplayProfile { name, avatar_url } + DisplayProfile { + id: event.author.id.get(), + name, + avatar_url, + mask: None, + } }