mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-17 21:15:32 +09:00
refactor(schema): simplify validation and OpenAPI generation (#2709)
This commit is contained in:
1 parent
4bc5593f9f
commit
de1fd95a99
510 files changed
+29494
-36131
No files matched your search
@@ -0,0 +1,236 @@
|
||||
use std::cmp::Ordering;
|
||||
|
||||
use serde_json::Value;
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct Attachment {
|
||||
pub id: String,
|
||||
pub url: String,
|
||||
pub filename: String,
|
||||
pub nsfw: Option<bool>,
|
||||
pub content_type: Option<String>,
|
||||
pub width: Option<u32>,
|
||||
pub height: Option<u32>,
|
||||
pub size: Option<u64>,
|
||||
pub ncmec_status: String,
|
||||
pub ncmec_report_id: Option<String>,
|
||||
pub ncmec_failure_reason: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct Message {
|
||||
pub id: String,
|
||||
pub content: String,
|
||||
pub timestamp: String,
|
||||
pub author_id: String,
|
||||
pub author_username: String,
|
||||
pub author_global_name: Option<String>,
|
||||
pub author_discriminator: String,
|
||||
pub author_avatar: Option<String>,
|
||||
pub channel_id: String,
|
||||
pub channel_nsfw: Option<bool>,
|
||||
pub channel_content_warning_level: Option<i32>,
|
||||
pub channel_content_warning_text: Option<String>,
|
||||
pub guild_nsfw: Option<bool>,
|
||||
pub attachments: Vec<Attachment>,
|
||||
}
|
||||
|
||||
pub fn ordered_messages(values: &[Value]) -> Vec<Message> {
|
||||
let mut messages: Vec<Message> = values.iter().map(message_from_value).collect();
|
||||
messages.sort_by(compare_message_ids);
|
||||
messages
|
||||
}
|
||||
|
||||
fn message_from_value(value: &Value) -> Message {
|
||||
let attachments = value["attachments"]
|
||||
.as_array()
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.map(attachment_from_value)
|
||||
.collect();
|
||||
Message {
|
||||
id: value_id(&value["id"]).unwrap_or_default(),
|
||||
content: value["content"].as_str().unwrap_or("").to_owned(),
|
||||
timestamp: value["timestamp"].as_str().unwrap_or("").to_owned(),
|
||||
author_id: value_id(&value["author_id"]).unwrap_or_default(),
|
||||
author_username: value["author_username"]
|
||||
.as_str()
|
||||
.unwrap_or("Unknown")
|
||||
.to_owned(),
|
||||
author_global_name: value["author_global_name"].as_str().map(ToOwned::to_owned),
|
||||
author_discriminator: value_id(&value["author_discriminator"])
|
||||
.unwrap_or_else(|| "0000".to_owned()),
|
||||
author_avatar: value["author_avatar"].as_str().map(ToOwned::to_owned),
|
||||
channel_id: value_id(&value["channel_id"]).unwrap_or_default(),
|
||||
channel_nsfw: value["channel_nsfw"].as_bool(),
|
||||
channel_content_warning_level: value["channel_content_warning_level"]
|
||||
.as_i64()
|
||||
.map(|n| n as i32),
|
||||
channel_content_warning_text: value["channel_content_warning_text"]
|
||||
.as_str()
|
||||
.map(ToOwned::to_owned),
|
||||
guild_nsfw: value["guild_nsfw"].as_bool(),
|
||||
attachments,
|
||||
}
|
||||
}
|
||||
|
||||
fn attachment_from_value(value: &Value) -> Attachment {
|
||||
Attachment {
|
||||
id: value_id(&value["id"]).unwrap_or_default(),
|
||||
url: value["url"].as_str().unwrap_or("").to_owned(),
|
||||
filename: value["filename"].as_str().unwrap_or("").to_owned(),
|
||||
nsfw: value["nsfw"].as_bool(),
|
||||
content_type: value["content_type"].as_str().map(ToOwned::to_owned),
|
||||
width: value["width"].as_u64().map(|n| n as u32),
|
||||
height: value["height"].as_u64().map(|n| n as u32),
|
||||
size: value["size"].as_u64(),
|
||||
ncmec_status: value["ncmec_status"]
|
||||
.as_str()
|
||||
.unwrap_or("not_submitted")
|
||||
.to_owned(),
|
||||
ncmec_report_id: value["ncmec_report_id"].as_str().map(ToOwned::to_owned),
|
||||
ncmec_failure_reason: value["ncmec_failure_reason"]
|
||||
.as_str()
|
||||
.map(ToOwned::to_owned),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn value_id(value: &Value) -> Option<String> {
|
||||
match value {
|
||||
Value::String(s) => Some(s.clone()),
|
||||
Value::Number(n) => Some(n.to_string()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn compare_message_ids(left: &Message, right: &Message) -> Ordering {
|
||||
match (left.id.parse::<u128>(), right.id.parse::<u128>()) {
|
||||
(Ok(l), Ok(r)) => l.cmp(&r),
|
||||
_ => left.id.cmp(&right.id),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use serde_json::json;
|
||||
|
||||
#[test]
|
||||
fn maps_message_and_attachment_fields() {
|
||||
let value = json!({
|
||||
"id": "9007199254740993",
|
||||
"content": "Message content",
|
||||
"timestamp": "2026-09-11T12:00:00Z",
|
||||
"author_id": 42,
|
||||
"author_username": "alice",
|
||||
"author_global_name": "Alice",
|
||||
"author_discriminator": 1234,
|
||||
"author_avatar": "avatar-hash",
|
||||
"channel_id": "100",
|
||||
"channel_nsfw": false,
|
||||
"channel_content_warning_level": 2,
|
||||
"channel_content_warning_text": "Content warning",
|
||||
"guild_nsfw": true,
|
||||
"attachments": [{
|
||||
"id": 9007199254740993_u64,
|
||||
"url": "https://cdn.example.com/image.png",
|
||||
"filename": "image.png",
|
||||
"nsfw": true,
|
||||
"content_type": "image/png",
|
||||
"width": 640,
|
||||
"height": 480,
|
||||
"size": 4096,
|
||||
"ncmec_status": "submitted",
|
||||
"ncmec_report_id": "report-id",
|
||||
"ncmec_failure_reason": "previous failure"
|
||||
}]
|
||||
});
|
||||
|
||||
assert_eq!(
|
||||
message_from_value(&value),
|
||||
Message {
|
||||
id: "9007199254740993".into(),
|
||||
content: "Message content".into(),
|
||||
timestamp: "2026-09-11T12:00:00Z".into(),
|
||||
author_id: "42".into(),
|
||||
author_username: "alice".into(),
|
||||
author_global_name: Some("Alice".into()),
|
||||
author_discriminator: "1234".into(),
|
||||
author_avatar: Some("avatar-hash".into()),
|
||||
channel_id: "100".into(),
|
||||
channel_nsfw: Some(false),
|
||||
channel_content_warning_level: Some(2),
|
||||
channel_content_warning_text: Some("Content warning".into()),
|
||||
guild_nsfw: Some(true),
|
||||
attachments: vec![Attachment {
|
||||
id: "9007199254740993".into(),
|
||||
url: "https://cdn.example.com/image.png".into(),
|
||||
filename: "image.png".into(),
|
||||
nsfw: Some(true),
|
||||
content_type: Some("image/png".into()),
|
||||
width: Some(640),
|
||||
height: Some(480),
|
||||
size: Some(4096),
|
||||
ncmec_status: "submitted".into(),
|
||||
ncmec_report_id: Some("report-id".into()),
|
||||
ncmec_failure_reason: Some("previous failure".into()),
|
||||
}],
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn retains_display_defaults_for_incomplete_snapshots() {
|
||||
let message = message_from_value(&json!({"attachments": [{}]}));
|
||||
|
||||
assert_eq!(message.author_username, "Unknown");
|
||||
assert_eq!(message.author_discriminator, "0000");
|
||||
assert_eq!(message.content, "");
|
||||
assert_eq!(message.author_global_name, None);
|
||||
assert_eq!(message.channel_nsfw, None);
|
||||
assert_eq!(
|
||||
message.attachments,
|
||||
vec![Attachment {
|
||||
id: String::new(),
|
||||
url: String::new(),
|
||||
filename: String::new(),
|
||||
nsfw: None,
|
||||
content_type: None,
|
||||
width: None,
|
||||
height: None,
|
||||
size: None,
|
||||
ncmec_status: "not_submitted".into(),
|
||||
ncmec_report_id: None,
|
||||
ncmec_failure_reason: None,
|
||||
}]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn orders_string_and_numeric_snowflakes_without_rounding() {
|
||||
let values = vec![
|
||||
json!({"id": "9007199254740993"}),
|
||||
json!({"id": "10"}),
|
||||
json!({"id": 2}),
|
||||
json!({"id": 9007199254740992_u64}),
|
||||
];
|
||||
let messages = ordered_messages(&values);
|
||||
let ids: Vec<&str> = messages.iter().map(|message| message.id.as_str()).collect();
|
||||
|
||||
assert_eq!(ids, ["2", "10", "9007199254740992", "9007199254740993"]);
|
||||
assert_eq!(values[0]["id"], "9007199254740993");
|
||||
assert!(ordered_messages(&[]).is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn preserves_message_order_when_ids_are_equal() {
|
||||
let values = [
|
||||
json!({"id": "10", "content": "first"}),
|
||||
json!({"id": 10, "content": "second"}),
|
||||
];
|
||||
let messages = ordered_messages(&values);
|
||||
|
||||
assert_eq!(messages[0].content, "first");
|
||||
assert_eq!(messages[1].content, "second");
|
||||
}
|
||||
}
|
||||
@@ -9,36 +9,7 @@ use super::user_display::format_user_display;
|
||||
use crate::config::AdminConfig;
|
||||
use crate::routes::auth::json_string;
|
||||
|
||||
pub struct Attachment {
|
||||
pub id: String,
|
||||
pub url: String,
|
||||
pub filename: String,
|
||||
pub nsfw: Option<bool>,
|
||||
pub content_type: Option<String>,
|
||||
pub width: Option<u32>,
|
||||
pub height: Option<u32>,
|
||||
pub size: Option<u64>,
|
||||
pub ncmec_status: String,
|
||||
pub ncmec_report_id: Option<String>,
|
||||
pub ncmec_failure_reason: Option<String>,
|
||||
}
|
||||
|
||||
pub struct Message {
|
||||
pub id: String,
|
||||
pub content: String,
|
||||
pub timestamp: String,
|
||||
pub author_id: String,
|
||||
pub author_username: String,
|
||||
pub author_global_name: Option<String>,
|
||||
pub author_discriminator: String,
|
||||
pub author_avatar: Option<String>,
|
||||
pub channel_id: String,
|
||||
pub channel_nsfw: Option<bool>,
|
||||
pub channel_content_warning_level: Option<i32>,
|
||||
pub channel_content_warning_text: Option<String>,
|
||||
pub guild_nsfw: Option<bool>,
|
||||
pub attachments: Vec<Attachment>,
|
||||
}
|
||||
use super::message_data::{Attachment, Message};
|
||||
|
||||
fn is_image(att: &Attachment) -> bool {
|
||||
att.content_type
|
||||
|
||||
@@ -13,6 +13,7 @@ pub mod error_display;
|
||||
pub mod form;
|
||||
pub mod icons;
|
||||
pub mod media;
|
||||
pub mod message_data;
|
||||
pub mod message_list;
|
||||
pub mod nsfw_indicators;
|
||||
pub mod page_container;
|
||||
|
||||
@@ -49,7 +49,7 @@ fn entry_count_hint(count: usize, cap: usize) -> Markup {
|
||||
p class="text-xs text-neutral-500" {
|
||||
(count) " of " (cap) " stored"
|
||||
@if count >= cap {
|
||||
" (at the cap; extra lines are dropped on save)"
|
||||
" (at the cap; remove an entry before adding another)"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -978,16 +978,18 @@ fn voice_noise_suppression_section(
|
||||
} else {
|
||||
("Inert", BadgeVariant::Default)
|
||||
};
|
||||
let backend_options = NoiseSuppressionBackend::ALL
|
||||
let backend_labels =
|
||||
NoiseSuppressionBackend::ALL.map(|backend| (backend.to_string(), backend.label()));
|
||||
let backend_options = backend_labels
|
||||
.iter()
|
||||
.map(|backend| (backend.as_str(), backend.label()))
|
||||
.map(|(value, label)| (value.as_str(), *label))
|
||||
.collect::<Vec<_>>();
|
||||
let included_user_ids = voice_noise_suppression.included_user_ids.join("\n");
|
||||
let excluded_user_ids = voice_noise_suppression.excluded_user_ids.join("\n");
|
||||
let guild_overrides = voice_noise_suppression
|
||||
.guild_overrides
|
||||
.iter()
|
||||
.map(|entry| format!("{}={}", entry.guild_id, entry.backend.as_str()))
|
||||
.map(|entry| format!("{}={}", entry.guild_id, entry.backend))
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n");
|
||||
section_card_with_description(
|
||||
@@ -1025,17 +1027,18 @@ fn voice_noise_suppression_section(
|
||||
"voice_ns_default_backend",
|
||||
"Default Backend",
|
||||
&backend_options,
|
||||
voice_noise_suppression.default_backend.as_str(),
|
||||
&voice_noise_suppression.default_backend.to_string(),
|
||||
))
|
||||
p class="text-xs text-neutral-500" {
|
||||
"The backend handed to every targeted user. A default that is not ticked \
|
||||
below is treated as unavailable and nobody is targeted."
|
||||
"The backend assigned by always-on user rules and the canary. A default \
|
||||
that is not ticked below is unavailable, but per-guild overrides can \
|
||||
still target users."
|
||||
}
|
||||
div class="grid grid-cols-1 gap-2 sm:grid-cols-2" {
|
||||
@for backend in NoiseSuppressionBackend::ALL {
|
||||
(checkbox(
|
||||
"voice_ns_enabled_backends[]",
|
||||
backend.as_str(),
|
||||
&backend.to_string(),
|
||||
backend.label(),
|
||||
voice_noise_suppression.enabled_backends.contains(&backend),
|
||||
true,
|
||||
@@ -1094,8 +1097,9 @@ fn voice_noise_suppression_section(
|
||||
))
|
||||
p class="text-xs text-neutral-500" {
|
||||
"One snowflake per line, or comma separated. These users are targeted \
|
||||
regardless of the percentage above. Anything that is not a plain \
|
||||
numeric ID is dropped."
|
||||
regardless of the percentage above. IDs must contain 1 to 20 decimal \
|
||||
digits. Invalid entries prevent the save; blank entries and duplicate \
|
||||
IDs are ignored."
|
||||
}
|
||||
}
|
||||
div class="flex flex-col gap-2" {
|
||||
@@ -1132,10 +1136,11 @@ fn voice_noise_suppression_section(
|
||||
VOICE_NS_MAX_GUILD_OVERRIDES,
|
||||
))
|
||||
p class="text-xs text-neutral-500" {
|
||||
"One per line as guild_id=backend, first line per guild wins. Targeted \
|
||||
users in that guild get that backend instead of the default. Lines \
|
||||
that are not a numeric guild ID and a ticked backend are dropped \
|
||||
without failing the save."
|
||||
"One per line as guild_id=backend. A guild \
|
||||
rule targets callers even outside the canary. Always-on user rules \
|
||||
take precedence, and excluded users stay off. Invalid lines and \
|
||||
conflicting rules for the same guild prevent the save. \
|
||||
Unticked backends stay stored but are inactive."
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,8 @@ use crate::{
|
||||
FORM_INPUT_CLASS, csrf_input, danger_button, form_actions, form_field_group,
|
||||
submit_button,
|
||||
},
|
||||
message_list::{Attachment, Message, message_deletion_script, message_list},
|
||||
message_data::{Message, ordered_messages, value_id},
|
||||
message_list::{message_deletion_script, message_list},
|
||||
page_container::{card, page_header},
|
||||
},
|
||||
layout::LayoutOptions,
|
||||
@@ -19,7 +20,6 @@ use crate::{
|
||||
};
|
||||
use maud::{Markup, html};
|
||||
use serde_json::Value;
|
||||
use std::cmp::Ordering;
|
||||
|
||||
const MESSAGE_BROWSE_SCRIPT: &str = r#"
|
||||
(function () {
|
||||
@@ -263,7 +263,7 @@ pub fn browse_messages_fragment(
|
||||
show_delete: bool,
|
||||
highlight_message_id: Option<&str>,
|
||||
) -> Markup {
|
||||
let messages = ordered_messages(result);
|
||||
let messages = response_messages(result);
|
||||
let has_more = result
|
||||
.get("has_more")
|
||||
.and_then(Value::as_bool)
|
||||
@@ -295,7 +295,7 @@ fn browse_result_card(
|
||||
csrf_token: &str,
|
||||
context_limit: u32,
|
||||
) -> Markup {
|
||||
let messages = ordered_messages(result);
|
||||
let messages = response_messages(result);
|
||||
let has_more = result
|
||||
.get("has_more")
|
||||
.and_then(Value::as_bool)
|
||||
@@ -345,7 +345,7 @@ fn search_result_card(
|
||||
query_text: &str,
|
||||
show_delete: bool,
|
||||
) -> Markup {
|
||||
let messages = ordered_messages(result);
|
||||
let messages = response_messages(result);
|
||||
let total = result
|
||||
.get("total")
|
||||
.and_then(Value::as_u64)
|
||||
@@ -382,7 +382,7 @@ fn lookup_result_card(
|
||||
show_delete: bool,
|
||||
context_limit: u32,
|
||||
) -> Markup {
|
||||
let messages = ordered_messages(result);
|
||||
let messages = response_messages(result);
|
||||
let channel_id = messages
|
||||
.first()
|
||||
.map(|m| m.channel_id.as_str())
|
||||
@@ -508,130 +508,12 @@ fn empty_state(text: &str) -> Markup {
|
||||
}
|
||||
}
|
||||
|
||||
fn ordered_messages(result: &Value) -> Vec<Message> {
|
||||
let mut messages: Vec<Message> = result
|
||||
.get("messages")
|
||||
.and_then(Value::as_array)
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.map(message_from_value)
|
||||
.collect();
|
||||
messages.sort_by(compare_message_ids);
|
||||
messages
|
||||
}
|
||||
|
||||
fn message_from_value(value: &Value) -> Message {
|
||||
let attachments = value
|
||||
.get("attachments")
|
||||
.and_then(Value::as_array)
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.map(attachment_from_value)
|
||||
.collect();
|
||||
Message {
|
||||
id: value.get("id").and_then(value_id).unwrap_or_default(),
|
||||
content: value
|
||||
.get("content")
|
||||
.and_then(Value::as_str)
|
||||
.unwrap_or("")
|
||||
.to_owned(),
|
||||
timestamp: value
|
||||
.get("timestamp")
|
||||
.and_then(Value::as_str)
|
||||
.unwrap_or("")
|
||||
.to_owned(),
|
||||
author_id: value
|
||||
.get("author_id")
|
||||
.and_then(value_id)
|
||||
.unwrap_or_default(),
|
||||
author_username: value
|
||||
.get("author_username")
|
||||
.and_then(Value::as_str)
|
||||
.unwrap_or("Unknown")
|
||||
.to_owned(),
|
||||
author_global_name: value
|
||||
.get("author_global_name")
|
||||
.and_then(Value::as_str)
|
||||
.map(ToOwned::to_owned),
|
||||
author_discriminator: value
|
||||
.get("author_discriminator")
|
||||
.and_then(value_id)
|
||||
.unwrap_or_else(|| "0000".to_owned()),
|
||||
author_avatar: value
|
||||
.get("author_avatar")
|
||||
.and_then(Value::as_str)
|
||||
.map(ToOwned::to_owned),
|
||||
channel_id: value
|
||||
.get("channel_id")
|
||||
.and_then(value_id)
|
||||
.unwrap_or_default(),
|
||||
channel_nsfw: value.get("channel_nsfw").and_then(Value::as_bool),
|
||||
channel_content_warning_level: value
|
||||
.get("channel_content_warning_level")
|
||||
.and_then(Value::as_i64)
|
||||
.map(|n| n as i32),
|
||||
channel_content_warning_text: value
|
||||
.get("channel_content_warning_text")
|
||||
.and_then(Value::as_str)
|
||||
.map(ToOwned::to_owned),
|
||||
guild_nsfw: value.get("guild_nsfw").and_then(Value::as_bool),
|
||||
attachments,
|
||||
}
|
||||
}
|
||||
|
||||
fn attachment_from_value(value: &Value) -> Attachment {
|
||||
Attachment {
|
||||
id: value.get("id").and_then(value_id).unwrap_or_default(),
|
||||
url: value
|
||||
.get("url")
|
||||
.and_then(Value::as_str)
|
||||
.unwrap_or("")
|
||||
.to_owned(),
|
||||
filename: value
|
||||
.get("filename")
|
||||
.and_then(Value::as_str)
|
||||
.unwrap_or("")
|
||||
.to_owned(),
|
||||
nsfw: value.get("nsfw").and_then(Value::as_bool),
|
||||
content_type: value
|
||||
.get("content_type")
|
||||
.and_then(Value::as_str)
|
||||
.map(ToOwned::to_owned),
|
||||
width: value.get("width").and_then(Value::as_u64).map(|n| n as u32),
|
||||
height: value
|
||||
.get("height")
|
||||
.and_then(Value::as_u64)
|
||||
.map(|n| n as u32),
|
||||
size: value.get("size").and_then(Value::as_u64),
|
||||
ncmec_status: value
|
||||
.get("ncmec_status")
|
||||
.and_then(Value::as_str)
|
||||
.unwrap_or("not_submitted")
|
||||
.to_owned(),
|
||||
ncmec_report_id: value
|
||||
.get("ncmec_report_id")
|
||||
.and_then(Value::as_str)
|
||||
.map(ToOwned::to_owned),
|
||||
ncmec_failure_reason: value
|
||||
.get("ncmec_failure_reason")
|
||||
.and_then(Value::as_str)
|
||||
.map(ToOwned::to_owned),
|
||||
}
|
||||
}
|
||||
|
||||
fn value_id(value: &Value) -> Option<String> {
|
||||
match value {
|
||||
Value::String(s) => Some(s.clone()),
|
||||
Value::Number(n) => Some(n.to_string()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn compare_message_ids(left: &Message, right: &Message) -> Ordering {
|
||||
match (left.id.parse::<u128>(), right.id.parse::<u128>()) {
|
||||
(Ok(l), Ok(r)) => l.cmp(&r),
|
||||
_ => left.id.cmp(&right.id),
|
||||
}
|
||||
fn response_messages(result: &Value) -> Vec<Message> {
|
||||
let values = result["messages"]
|
||||
.as_array()
|
||||
.map(Vec::as_slice)
|
||||
.unwrap_or_default();
|
||||
ordered_messages(values)
|
||||
}
|
||||
|
||||
fn browse_channel_form(config: &AdminConfig, csrf_token: &str, prefill: Option<&str>) -> Markup {
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
use std::cmp::Ordering;
|
||||
|
||||
use crate::{
|
||||
acl,
|
||||
api::types::ReportEntry,
|
||||
@@ -13,7 +11,8 @@ use crate::{
|
||||
data_field::{data_field, data_field_link_mono, data_field_mono, data_field_text},
|
||||
form::csrf_input,
|
||||
media::{guild_icon_url, initials, user_avatar_url},
|
||||
message_list::{Attachment, Message, message_deletion_script, message_list},
|
||||
message_data::ordered_messages,
|
||||
message_list::{message_deletion_script, message_list},
|
||||
nsfw_indicators::{
|
||||
adult_content_badge, channel_nsfw_state_badge, content_warning_badge,
|
||||
},
|
||||
@@ -26,7 +25,6 @@ use crate::{
|
||||
utils::timestamps::format_admin_timestamp,
|
||||
};
|
||||
use maud::{Markup, html};
|
||||
use serde_json::Value;
|
||||
|
||||
fn status_badge(status: i32) -> Markup {
|
||||
let (label, variant) = match status {
|
||||
@@ -534,123 +532,3 @@ fn basic_info_section_fragment(config: &AdminConfig, report: &ReportEntry) -> Ma
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
fn ordered_messages(values: &[Value]) -> Vec<Message> {
|
||||
let mut messages: Vec<Message> = values.iter().map(message_from_value).collect();
|
||||
messages.sort_by(compare_message_ids);
|
||||
messages
|
||||
}
|
||||
|
||||
fn message_from_value(value: &Value) -> Message {
|
||||
let attachments = value
|
||||
.get("attachments")
|
||||
.and_then(Value::as_array)
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.map(attachment_from_value)
|
||||
.collect();
|
||||
Message {
|
||||
id: value.get("id").and_then(value_id).unwrap_or_default(),
|
||||
content: value
|
||||
.get("content")
|
||||
.and_then(Value::as_str)
|
||||
.unwrap_or("")
|
||||
.to_owned(),
|
||||
timestamp: value
|
||||
.get("timestamp")
|
||||
.and_then(Value::as_str)
|
||||
.unwrap_or("")
|
||||
.to_owned(),
|
||||
author_id: value
|
||||
.get("author_id")
|
||||
.and_then(value_id)
|
||||
.unwrap_or_default(),
|
||||
author_username: value
|
||||
.get("author_username")
|
||||
.and_then(Value::as_str)
|
||||
.unwrap_or("Unknown")
|
||||
.to_owned(),
|
||||
author_global_name: value
|
||||
.get("author_global_name")
|
||||
.and_then(Value::as_str)
|
||||
.map(ToOwned::to_owned),
|
||||
author_discriminator: value
|
||||
.get("author_discriminator")
|
||||
.and_then(value_id)
|
||||
.unwrap_or_else(|| "0000".to_owned()),
|
||||
author_avatar: value
|
||||
.get("author_avatar")
|
||||
.and_then(Value::as_str)
|
||||
.map(ToOwned::to_owned),
|
||||
channel_id: value
|
||||
.get("channel_id")
|
||||
.and_then(value_id)
|
||||
.unwrap_or_default(),
|
||||
channel_nsfw: value.get("channel_nsfw").and_then(Value::as_bool),
|
||||
channel_content_warning_level: value
|
||||
.get("channel_content_warning_level")
|
||||
.and_then(Value::as_i64)
|
||||
.map(|n| n as i32),
|
||||
channel_content_warning_text: value
|
||||
.get("channel_content_warning_text")
|
||||
.and_then(Value::as_str)
|
||||
.map(ToOwned::to_owned),
|
||||
guild_nsfw: value.get("guild_nsfw").and_then(Value::as_bool),
|
||||
attachments,
|
||||
}
|
||||
}
|
||||
|
||||
fn attachment_from_value(value: &Value) -> Attachment {
|
||||
Attachment {
|
||||
id: value.get("id").and_then(value_id).unwrap_or_default(),
|
||||
url: value
|
||||
.get("url")
|
||||
.and_then(Value::as_str)
|
||||
.unwrap_or("")
|
||||
.to_owned(),
|
||||
filename: value
|
||||
.get("filename")
|
||||
.and_then(Value::as_str)
|
||||
.unwrap_or("")
|
||||
.to_owned(),
|
||||
nsfw: value.get("nsfw").and_then(Value::as_bool),
|
||||
content_type: value
|
||||
.get("content_type")
|
||||
.and_then(Value::as_str)
|
||||
.map(ToOwned::to_owned),
|
||||
width: value.get("width").and_then(Value::as_u64).map(|n| n as u32),
|
||||
height: value
|
||||
.get("height")
|
||||
.and_then(Value::as_u64)
|
||||
.map(|n| n as u32),
|
||||
size: value.get("size").and_then(Value::as_u64),
|
||||
ncmec_status: value
|
||||
.get("ncmec_status")
|
||||
.and_then(Value::as_str)
|
||||
.unwrap_or("not_submitted")
|
||||
.to_owned(),
|
||||
ncmec_report_id: value
|
||||
.get("ncmec_report_id")
|
||||
.and_then(Value::as_str)
|
||||
.map(ToOwned::to_owned),
|
||||
ncmec_failure_reason: value
|
||||
.get("ncmec_failure_reason")
|
||||
.and_then(Value::as_str)
|
||||
.map(ToOwned::to_owned),
|
||||
}
|
||||
}
|
||||
|
||||
fn value_id(value: &Value) -> Option<String> {
|
||||
match value {
|
||||
Value::String(s) => Some(s.clone()),
|
||||
Value::Number(n) => Some(n.to_string()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn compare_message_ids(left: &Message, right: &Message) -> Ordering {
|
||||
match (left.id.parse::<u128>(), right.id.parse::<u128>()) {
|
||||
(Ok(l), Ok(r)) => l.cmp(&r),
|
||||
_ => left.id.cmp(&right.id),
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user