chore(repo): remove dead code and useless tests (#3375)

This commit is contained in:
Hampus authored and GitHub committed 2026-10-10 21:43:29 +02:00
1 parent 7e177a1c90
commit 1cd62eb661
1391 files changed
+783 -120415

No files matched your search

+1 -2
View File
@@ -10,7 +10,7 @@ use axum::{
};
use fluxer_admin::{
build_router,
config::{AdminConfig, ProxyConfig, RuntimeEnv},
config::{AdminConfig, ProxyConfig},
session,
};
use serde_json::{Value, json};
@@ -85,7 +85,6 @@ async fn setup() -> TestApp {
fn production_config(api_endpoint: String) -> AdminConfig {
AdminConfig {
env: RuntimeEnv::Production,
host: "127.0.0.1".to_owned(),
port: 0,
secret_key_base: SECRET_KEY.to_owned(),
File diff suppressed because it is too large. Load diff
+1 -2
View File
@@ -11,7 +11,7 @@ use axum::{
};
use fluxer_admin::{
build_router,
config::{AdminConfig, ProxyConfig, RuntimeEnv},
config::{AdminConfig, ProxyConfig},
session,
};
use serde_json::{Value, json};
@@ -346,7 +346,6 @@ fn admin_user() -> Value {
fn test_config(api_endpoint: String) -> AdminConfig {
AdminConfig {
env: RuntimeEnv::Test,
host: "127.0.0.1".to_owned(),
port: 0,
secret_key_base: SECRET_KEY.to_owned(),
-635
View File
@@ -1,635 +0,0 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
#[path = "parity/mod.rs"]
mod parity_support;
use axum::{
Json, Router,
extract::{Path, RawQuery},
http::StatusCode,
response::{IntoResponse, Response},
routing::get,
};
use fluxer_admin::api::{
generated::types::{
AdminReportListResponse, LookupGuildResponse, ReportAdminResponseSchema,
SearchGuildsResponse,
},
types::{ReportEntry, SearchReportsResponse},
};
use parity_support::{
TEST_ACCESS_TOKEN, TEST_ADMIN_SECRET, TEST_ADMIN_USER_ID, api_fixtures, capture,
html_normalizer, rust_server,
};
use serde_json::{Value, json};
use std::{error::Error, io};
use tokio::net::TcpListener;
const SEARCH_REPORTS_V2: &str = include_str!("parity/fixtures/api/search_reports_v2.json");
const SEARCH_REPORTS_WEBHOOK: &str =
include_str!("parity/fixtures/api/search_reports_webhook.json");
const REPORT_WEBHOOK_DETAIL: &str = include_str!("parity/fixtures/api/report_webhook_detail.json");
const REPORT_EVIDENCE_DETAIL: &str =
include_str!("parity/fixtures/api/report_evidence_detail.json");
const EVIDENCE_REPORT_ID: &str = "1556352159220498613";
const EVIDENCE_BOT_ID: &str = "1556352159149195428";
const EVIDENCE_WEBHOOK_ID: &str = "1556352159132418208";
const EVIDENCE_DELETED_AUTHOR_ID: &str = "1556352159216304308";
const WEBHOOK_ID: &str = "1556114449176200401";
const WEBHOOK_REPORT_ID: &str = "1556114449264280806";
const BOT_REPORT_ID: &str = "1556114449268475111";
const BOT_USER_ID: &str = "1556114449192977621";
const WEBHOOK_CREATOR_ID: &str = "1556114449125868741";
#[test]
fn html_normalizer_canonicalizes_attribute_order_and_csrf_values() {
let left = r#"<form><input value="aaaaaaaa" name="_csrf" type="hidden"><svg><line x1="1" x2="2"></line></svg><a class="b" href="/static/app.css?v=123" id="x">Open</a></form>"#;
let right = r#"<form><input type="hidden" name="_csrf" value="bbbbbbbb"/><svg><line x2="2" x1="1"/></svg><a id="x" href="/static/app.css?v=456" class="b">Open</a></form>"#;
assert_eq!(
html_normalizer::normalize_html(left),
html_normalizer::normalize_html(right)
);
}
#[test]
fn text_normalizer_replaces_ports_assets_query_values_and_cookie_tokens() {
let raw = "http://127.0.0.1:31987/auth/start?state=abc&next=/static/app.css?v=dev admin_session=abcdef; csrf_token=12345; oauth_state=zz";
let normalized = html_normalizer::normalize_text(raw);
assert!(normalized.contains("127.0.0.1:__PORT__"));
assert!(normalized.contains("state=__OAUTH_STATE__"));
assert!(normalized.contains("/static/app.css"));
assert!(normalized.contains("admin_session=__SESSION__"));
assert!(normalized.contains("csrf_token=__CSRF_COOKIE__"));
assert!(normalized.contains("oauth_state=__OAUTH_STATE__"));
}
#[test]
fn text_normalizer_replaces_script_csrf_values() {
let raw = r#"<script>(function(){var csrf="aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";})()</script>"#;
let normalized = html_normalizer::normalize_text(raw);
assert!(normalized.contains(r#"var csrf="__CSRF_TOKEN__""#));
}
#[test]
fn html_normalizer_allows_intentional_rust_markup_fixes() {
let ts = r#"<html><head></head><body><div class="flex flex-col gap-8 items-stretch"></div><div class="flex flex-col gap-4 items-stretch"></div><script defer>window.__fluxerDrawerInit = true;</script><aside data-drawer-panel="user-peek" aria-hidden="true"></aside></body></html>"#;
let rust = r#"<!DOCTYPE html><html><head><script src="/static/htmx.min.js?t=parity" defer></script></head><body hx-boost="true"><div class="flex flex-col gap-8 items-center"></div><div id="users-results" class="flex flex-col gap-4 items-stretch"></div><script defer>document.body.addEventListener('showFlash', function () {});</script><script defer>window.__adminCopyToClipboard = function () {};</script><aside id="user-peek" data-drawer-panel="user-peek" popover="auto"></aside></body></html>"#;
assert_eq!(
html_normalizer::normalize_html(ts),
html_normalizer::normalize_html(rust)
);
}
#[test]
fn guild_search_fixture_matches_the_generated_response_contract() {
let response: SearchGuildsResponse =
serde_json::from_str(include_str!("parity/fixtures/api/search_guilds.json"))
.expect("guild search fixture must match the generated response contract");
assert_eq!(response.guilds.len(), 1);
let guild = &response.guilds[0];
assert_eq!(guild.name, "Parity Guild");
assert_eq!(guild.nsfw, Some(false));
assert_eq!(guild.content_warning_level.as_deref(), Some(&0));
}
#[test]
fn guild_lookup_fixture_matches_the_generated_response_contract() {
let response: LookupGuildResponse =
serde_json::from_str(include_str!("parity/fixtures/api/lookup_guild.json"))
.expect("guild lookup fixture must match the generated response contract");
let guild = response
.guild
.expect("guild lookup fixture must contain a guild");
assert_eq!(String::from(guild.name), "Parity Guild");
assert_eq!(guild.nsfw, Some(false));
assert_eq!(guild.content_warning_level.as_deref(), Some(&0));
assert_eq!(guild.channels.len(), 1);
assert_eq!(guild.channels[0].content_warning_level.as_deref(), Some(&0));
}
#[tokio::test(flavor = "multi_thread")]
async fn rust_admin_fixture_routes_cover_default_protected_routes() -> Result<(), Box<dyn Error>> {
let api_server = api_fixtures::ApiFixtureServer::start_default()
.await
.map_err(test_error)?;
let rust_admin = rust_server::start(api_server.base_url())
.await
.map_err(test_error)?;
let client = capture::capture_client().map_err(test_error)?;
let session = fluxer_admin::session::create_session(
TEST_ADMIN_USER_ID,
TEST_ACCESS_TOKEN,
TEST_ADMIN_SECRET,
);
let session_cookie = format!("{}={session}", fluxer_admin::session::SESSION_COOKIE_NAME);
let cases = [
("/dashboard", 302, Some("/users"), None),
("/users?q=Parity", 200, None, Some("Parity User")),
("/guilds?q=Parity", 200, None, Some("Parity Guild")),
(
"/guilds/1600000000000000001",
200,
None,
Some("Parity Guild"),
),
("/reports", 200, None, Some("1700000000000000001")),
(
"/reports/1700000000000000001",
200,
None,
Some("Report Details"),
),
];
for (route, expected_status, expected_location, expected_body) in cases {
let response =
capture::fetch_route(&client, rust_admin.base_url(), route, Some(&session_cookie))
.await
.map_err(test_error)?;
assert_eq!(response.status, expected_status, "{route}: {response:#?}");
if let Some(expected_location) = expected_location {
assert_eq!(
response.location.as_deref(),
Some(expected_location),
"{route}: {response:#?}"
);
}
if let Some(expected_body) = expected_body {
assert!(
response.body.contains(expected_body),
"{route}: expected body to contain {expected_body:?}\n{response:#?}"
);
}
}
Ok(())
}
#[test]
fn report_search_v2_fixture_matches_the_generated_and_hand_written_contracts() {
let generated: AdminReportListResponse = serde_json::from_str(SEARCH_REPORTS_V2)
.expect("v2 report search fixture must match the generated response contract");
let flow = generated.reports[0]
.flow
.as_ref()
.expect("the v2 message report has answers");
assert_eq!(flow.surface, "in_app");
assert_eq!(flow.steps.len(), 4);
assert_eq!(generated.reports[0].reason.as_deref(), Some("csam"));
assert_eq!(generated.reports[2].reason, None);
let response: SearchReportsResponse = serde_json::from_str(SEARCH_REPORTS_V2)
.expect("v2 report search fixture must match the hand-written response type");
assert_eq!(response.total, 3);
let [message, user, legacy] = response.reports.as_slice() else {
panic!("expected three reports");
};
assert_eq!(
message.reason_label.as_deref(),
Some("Child sexual abuse material")
);
assert_eq!(message.reason_highest_priority, Some(true));
assert_eq!(message.category.as_deref(), Some("child_safety"));
let user_flow = user.flow.as_ref().expect("the v2 user report has answers");
assert_eq!(user.reason.as_deref(), Some("harassment"));
assert_eq!(user_flow.locale.as_deref(), Some("de"));
assert_eq!(user_flow.steps[0].screen_id, "profile_intro");
assert!(user_flow.steps[0].option_id.is_none());
assert!(user_flow.steps[0].items.is_empty());
assert_eq!(
user_flow.steps[1]
.items
.iter()
.map(|item| item.id.as_str())
.collect::<Vec<_>>(),
["photo", "profile_text"]
);
assert!(legacy.reason.is_none());
assert!(legacy.reason_label.is_none());
assert!(legacy.flow.is_none());
assert!(legacy.reporter_good_faith_confirmed.is_none());
}
#[tokio::test(flavor = "multi_thread")]
async fn rust_admin_renders_v2_report_fixture_with_reason_and_answers() -> Result<(), Box<dyn Error>>
{
let api_endpoint = spawn_v2_report_api().await.map_err(test_error)?;
let rust_admin = rust_server::start(&api_endpoint)
.await
.map_err(test_error)?;
let client = capture::capture_client().map_err(test_error)?;
let session = fluxer_admin::session::create_session(
TEST_ADMIN_USER_ID,
TEST_ACCESS_TOKEN,
TEST_ADMIN_SECRET,
);
let session_cookie = format!("{}={session}", fluxer_admin::session::SESSION_COOKIE_NAME);
let list = capture::fetch_route(
&client,
rust_admin.base_url(),
"/reports?reason=csam",
Some(&session_cookie),
)
.await
.map_err(test_error)?;
assert_eq!(list.status, 200, "{list:#?}");
assert!(list.body.contains(r#"name="reason""#), "{}", list.body);
assert!(
list.body.contains("Priority: Child sexual abuse material"),
"{}",
list.body
);
assert!(
list.body.contains(r#"data-report-reason="csam""#),
"{}",
list.body
);
assert!(
!list.body.contains(r#"data-report-reason="harassment""#),
"{}",
list.body
);
let detail = capture::fetch_route(
&client,
rust_admin.base_url(),
"/reports/1556008115705480394",
Some(&session_cookie),
)
.await
.map_err(test_error)?;
assert_eq!(detail.status, 200, "{detail:#?}");
for expected in [
"Report Answers",
"Report profile",
"Which parts of their profile are a problem?: Pictures, Profile text",
"Shown to the reporter in de",
"Surface: In app",
"Harassment or bullying",
] {
assert!(
detail.body.contains(expected),
"{expected}\n{}",
detail.body
);
}
let legacy = capture::fetch_route(
&client,
rust_admin.base_url(),
"/reports/1556008115709674699",
Some(&session_cookie),
)
.await
.map_err(test_error)?;
assert_eq!(legacy.status, 200, "{legacy:#?}");
assert!(!legacy.body.contains("Report Answers"), "{}", legacy.body);
assert!(
!legacy.body.contains("data-report-reason"),
"{}",
legacy.body
);
Ok(())
}
#[test]
fn report_webhook_fixtures_match_the_generated_and_hand_written_contracts() {
let generated: AdminReportListResponse = serde_json::from_str(SEARCH_REPORTS_WEBHOOK)
.expect("webhook report search fixture must match the generated response contract");
assert_eq!(generated.reports.len(), 2);
let response: SearchReportsResponse = serde_json::from_str(SEARCH_REPORTS_WEBHOOK)
.expect("webhook report search fixture must match the hand-written response type");
let [webhook, bot] = response.reports.as_slice() else {
panic!("expected two reports");
};
assert_eq!(webhook.report_id, WEBHOOK_REPORT_ID);
assert!(webhook.reported_user_id.is_none());
assert_eq!(webhook.reported_webhook_id.as_deref(), Some(WEBHOOK_ID));
assert_eq!(
webhook.reported_webhook_name.as_deref(),
Some("Harbor Bulletin")
);
assert_eq!(bot.reported_user_id.as_deref(), Some(BOT_USER_ID));
assert!(bot.reported_webhook_id.is_none());
let generated_detail: ReportAdminResponseSchema = serde_json::from_str(REPORT_WEBHOOK_DETAIL)
.expect("webhook report detail fixture must match the generated response contract");
let via_generated: ReportEntry = serde_json::from_value(
serde_json::to_value(generated_detail).expect("serialize generated detail"),
)
.expect("hand-written report type from the generated detail");
let direct: ReportEntry = serde_json::from_str(REPORT_WEBHOOK_DETAIL)
.expect("webhook report detail fixture must match the hand-written report type");
for detail in [&via_generated, &direct] {
assert!(detail.reported_user_id.is_none());
assert_eq!(detail.reported_webhook_id.as_deref(), Some(WEBHOOK_ID));
assert_eq!(
detail.reported_webhook_creator_id.as_deref(),
Some(WEBHOOK_CREATOR_ID)
);
assert_eq!(
detail.reported_webhook_creator_global_name.as_deref(),
Some("Morgan Owner")
);
assert_eq!(detail.reported_webhook_type, Some(1));
assert_eq!(
detail.reported_webhook_channel_id,
detail.reported_channel_id
);
assert_eq!(detail.reported_webhook_guild_id, detail.reported_guild_id);
assert_eq!(
detail.reported_webhook_created_at.as_deref(),
Some("2026-10-04T01:23:15.892Z")
);
assert!(detail.reported_webhook_application_id.is_none());
let context = detail.message_context.as_ref().expect("message context");
let entry = context
.iter()
.find(|entry| entry["webhook_id"].as_str() == Some(WEBHOOK_ID))
.expect("the webhook message is in the context");
assert_eq!(entry["author_id"].as_str(), Some(WEBHOOK_ID));
assert_eq!(entry["author_username"].as_str(), Some("Harbor Bulletin"));
}
}
#[tokio::test(flavor = "multi_thread")]
async fn rust_admin_renders_webhook_report_fixture() -> Result<(), Box<dyn Error>> {
let api_endpoint = spawn_v2_report_api().await.map_err(test_error)?;
let rust_admin = rust_server::start(&api_endpoint)
.await
.map_err(test_error)?;
let client = capture::capture_client().map_err(test_error)?;
let session = fluxer_admin::session::create_session(
TEST_ADMIN_USER_ID,
TEST_ACCESS_TOKEN,
TEST_ADMIN_SECRET,
);
let session_cookie = format!("{}={session}", fluxer_admin::session::SESSION_COOKIE_NAME);
let webhook_marker = format!(r#"data-report-webhook="{WEBHOOK_ID}""#);
let webhook_user_link = format!("/users/{WEBHOOK_ID}");
let list = capture::fetch_route(
&client,
rust_admin.base_url(),
&format!("/reports?reported_webhook_id={WEBHOOK_ID}"),
Some(&session_cookie),
)
.await
.map_err(test_error)?;
assert_eq!(list.status, 200, "{list:#?}");
assert!(list.body.contains(WEBHOOK_REPORT_ID), "{}", list.body);
assert!(!list.body.contains(BOT_REPORT_ID), "{}", list.body);
assert!(list.body.contains(&webhook_marker), "{}", list.body);
assert!(list.body.contains("Harbor Bulletin"), "{}", list.body);
assert!(list.body.contains("Channel: general"), "{}", list.body);
assert!(!list.body.contains(&webhook_user_link), "{}", list.body);
let both = capture::fetch_route(
&client,
rust_admin.base_url(),
"/reports?reporter_id=1556114449113285826",
Some(&session_cookie),
)
.await
.map_err(test_error)?;
assert_eq!(both.status, 200, "{both:#?}");
assert!(
both.body
.contains(&format!(r#"href="/users/{BOT_USER_ID}""#)),
"{}",
both.body
);
let detail = capture::fetch_route(
&client,
rust_admin.base_url(),
&format!("/reports/{WEBHOOK_REPORT_ID}"),
Some(&session_cookie),
)
.await
.map_err(test_error)?;
assert_eq!(detail.status, 200, "{detail:#?}");
for expected in [
webhook_marker.clone(),
">Webhook ID<".to_owned(),
format!(r#"href="/reports?reported_webhook_id={WEBHOOK_ID}""#),
"Harbor Commons".to_owned(),
"Message Context".to_owned(),
format!(r#"data-message-webhook="{WEBHOOK_ID}""#),
format!(r#"href="/users/{BOT_USER_ID}""#),
">Webhook Creator<".to_owned(),
format!(r#"data-report-webhook-creator="{WEBHOOK_CREATOR_ID}""#),
format!(r#"href="/users/{WEBHOOK_CREATOR_ID}""#),
"Morgan Owner".to_owned(),
">Webhook Type<".to_owned(),
">Incoming<".to_owned(),
">Webhook Created<".to_owned(),
"Oct 4, 2026, 1:23 AM UTC".to_owned(),
] {
assert!(
detail.body.contains(&expected),
"{expected}\n{}",
detail.body
);
}
assert!(!detail.body.contains(&webhook_user_link), "{}", detail.body);
assert!(
!detail.body.contains("View Reported User"),
"{}",
detail.body
);
for absent in [
">Webhook Channel ID<",
">Webhook Guild ID<",
">Webhook Record<",
"data-report-webhook-creator-deleted",
] {
assert!(!detail.body.contains(absent), "{absent}\n{}", detail.body);
}
Ok(())
}
#[test]
fn report_evidence_fixture_matches_the_generated_and_hand_written_contracts() {
let generated: ReportAdminResponseSchema = serde_json::from_str(REPORT_EVIDENCE_DETAIL)
.expect("evidence report detail fixture must match the generated response contract");
let via_generated: ReportEntry = serde_json::from_value(
serde_json::to_value(generated).expect("serialize generated detail"),
)
.expect("hand-written report type from the generated detail");
let direct: ReportEntry = serde_json::from_str(REPORT_EVIDENCE_DETAIL)
.expect("evidence report detail fixture must match the hand-written report type");
for detail in [&via_generated, &direct] {
assert_eq!(detail.reported_user_id.as_deref(), Some(EVIDENCE_BOT_ID));
assert_eq!(detail.reported_user_bot, Some(true));
let snapshot = detail
.reported_profile_snapshot
.as_ref()
.expect("the bot author has a profile snapshot");
let user = snapshot.user.as_ref().expect("user snapshot");
assert_eq!(user.id, EVIDENCE_BOT_ID);
assert_eq!(user.username.as_deref(), Some("Harbor_Helper"));
let member = snapshot.member.as_ref().expect("member snapshot");
assert_eq!(member.guild_id, "1556352159090475158");
assert!(member.joined_at.is_some());
assert!(snapshot.guild.is_none());
let context = detail.message_context.as_ref().expect("message context");
let author_bot = |author_id: &str| {
context
.iter()
.filter(|entry| entry["author_id"].as_str() == Some(author_id))
.map(|entry| entry["author_bot"].clone())
.collect::<Vec<_>>()
};
assert_eq!(author_bot(EVIDENCE_BOT_ID), [json!(true), json!(true)]);
assert_eq!(author_bot(EVIDENCE_WEBHOOK_ID), [Value::Null]);
assert_eq!(author_bot(EVIDENCE_DELETED_AUTHOR_ID), [Value::Null]);
assert_eq!(author_bot("1556352159069503633"), [json!(false)]);
assert!(context.iter().all(|entry| {
entry["missing_attachments"]
.as_array()
.is_none_or(Vec::is_empty)
}));
}
}
#[tokio::test(flavor = "multi_thread")]
async fn rust_admin_renders_the_evidence_report_fixture() -> Result<(), Box<dyn Error>> {
let api_endpoint = spawn_v2_report_api().await.map_err(test_error)?;
let rust_admin = rust_server::start(&api_endpoint)
.await
.map_err(test_error)?;
let client = capture::capture_client().map_err(test_error)?;
let session = fluxer_admin::session::create_session(
TEST_ADMIN_USER_ID,
TEST_ACCESS_TOKEN,
TEST_ADMIN_SECRET,
);
let session_cookie = format!("{}={session}", fluxer_admin::session::SESSION_COOKIE_NAME);
let detail = capture::fetch_route(
&client,
rust_admin.base_url(),
&format!("/reports/{EVIDENCE_REPORT_ID}"),
Some(&session_cookie),
)
.await
.map_err(test_error)?;
assert_eq!(detail.status, 200, "{detail:#?}");
for expected in [
format!(r#"data-report-user-bot="{EVIDENCE_BOT_ID}""#),
format!(r#"data-message-author-bot="{EVIDENCE_BOT_ID}""#),
format!(r#"data-message-webhook="{EVIDENCE_WEBHOOK_ID}""#),
"At Report Time".to_owned(),
"Harbor_Helper#3966".to_owned(),
">Community Profile<".to_owned(),
r#"data-report-legal-hold="none""#.to_owned(),
] {
assert!(
detail.body.contains(&expected),
"{expected}\n{}",
detail.body
);
}
assert_eq!(
detail.body.matches("data-message-author-bot=").count(),
1,
"{}",
detail.body
);
assert!(
!detail.body.contains("data-missing-attachment"),
"{}",
detail.body
);
Ok(())
}
async fn spawn_v2_report_api() -> Result<String, String> {
let listener = TcpListener::bind("127.0.0.1:0")
.await
.map_err(|error| format!("failed to bind v2 report API: {error}"))?;
let port = listener
.local_addr()
.map_err(|error| format!("failed to read v2 report API address: {error}"))?
.port();
let app = Router::new()
.route(
"/admin/users/@me",
get(|| async {
fixture_json(include_str!("parity/fixtures/api/admin_user_me.json"))
}),
)
.route(
"/admin/report-reasons",
get(|| async {
Json(json!({"reasons": [
{"key": "csam", "label": "Child sexual abuse material", "highest_priority": true, "legacy_category_message": "child_safety", "legacy_category_user": "child_safety", "legacy_category_guild": "child_safety"},
{"key": "harassment", "label": "Harassment or bullying", "highest_priority": false, "legacy_category_message": "harassment", "legacy_category_user": "harassment", "legacy_category_guild": "harassment"}
]}))
.into_response()
}),
)
.route("/admin/reports", get(v2_report_search))
.route("/admin/reports/{report_id}", get(v2_report_detail));
tokio::spawn(async move {
let _ = axum::serve(listener, app).await;
});
Ok(format!("http://127.0.0.1:{port}"))
}
fn fixture_json(body: &'static str) -> Response {
let value: Value = serde_json::from_str(body).expect("fixture is JSON");
Json(value).into_response()
}
fn v2_reports() -> Vec<Value> {
[SEARCH_REPORTS_V2, SEARCH_REPORTS_WEBHOOK]
.into_iter()
.flat_map(|body| {
let value: Value = serde_json::from_str(body).expect("fixture is JSON");
value["reports"].as_array().cloned().unwrap_or_default()
})
.collect()
}
async fn v2_report_search(RawQuery(query): RawQuery) -> Response {
let query = query.unwrap_or_default();
let filters = url::form_urlencoded::parse(query.as_bytes())
.filter(|(key, value)| {
matches!(
key.as_ref(),
"reason" | "reporter_id" | "reported_webhook_id"
) && !value.is_empty()
})
.map(|(key, value)| (key.into_owned(), value.into_owned()))
.collect::<Vec<_>>();
let reports = v2_reports()
.into_iter()
.filter(|report| {
filters
.iter()
.all(|(key, value)| report[key.as_str()].as_str() == Some(value.as_str()))
})
.collect::<Vec<_>>();
Json(json!({"reports": reports, "total": reports.len(), "offset": 0, "limit": 25}))
.into_response()
}
async fn v2_report_detail(Path(report_id): Path<String>) -> Response {
if report_id == WEBHOOK_REPORT_ID {
return fixture_json(REPORT_WEBHOOK_DETAIL);
}
if report_id == EVIDENCE_REPORT_ID {
return fixture_json(REPORT_EVIDENCE_DETAIL);
}
match v2_reports()
.into_iter()
.find(|report| report["report_id"].as_str() == Some(report_id.as_str()))
{
Some(report) => Json(report).into_response(),
None => StatusCode::NOT_FOUND.into_response(),
}
}
fn test_error(message: String) -> Box<dyn Error> {
Box::new(io::Error::other(message))
}
-172
View File
@@ -1,172 +0,0 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
use axum::{
Router,
body::Body,
extract::State,
http::{HeaderName, HeaderValue, Request, StatusCode, header},
response::{IntoResponse, Response},
routing::any,
};
use serde::Deserialize;
use std::{
collections::HashMap,
path::{Path, PathBuf},
sync::Arc,
};
use tokio::{net::TcpListener, task::JoinHandle};
#[derive(Clone, Debug)]
struct ApiFixtureRoute {
method: String,
path: String,
status: u16,
content_type: String,
headers: HashMap<String, String>,
body: String,
}
#[derive(Clone)]
struct ApiFixtureSet {
routes: Arc<Vec<ApiFixtureRoute>>,
}
#[derive(Deserialize)]
struct ApiFixtureManifest {
routes: Vec<ApiFixtureManifestRoute>,
}
#[derive(Deserialize)]
struct ApiFixtureManifestRoute {
method: String,
path: String,
status: Option<u16>,
content_type: Option<String>,
headers: Option<HashMap<String, String>>,
body: Option<String>,
body_file: Option<String>,
}
pub struct ApiFixtureServer {
base_url: String,
handle: JoinHandle<()>,
}
impl ApiFixtureServer {
pub async fn start_default() -> Result<Self, String> {
let fixture_set = ApiFixtureSet::from_default_manifest()?;
let listener = TcpListener::bind("127.0.0.1:0")
.await
.map_err(|error| format!("failed to bind fixture API server: {error}"))?;
let port = listener
.local_addr()
.map_err(|error| format!("failed to read fixture API address: {error}"))?
.port();
let app = Router::new()
.fallback(any(handle_fixture_request))
.with_state(fixture_set.routes);
let handle = tokio::spawn(async move {
let _ = axum::serve(listener, app).await;
});
Ok(Self {
base_url: format!("http://127.0.0.1:{port}"),
handle,
})
}
pub fn base_url(&self) -> &str {
&self.base_url
}
}
impl Drop for ApiFixtureServer {
fn drop(&mut self) {
self.handle.abort();
}
}
impl ApiFixtureSet {
fn from_default_manifest() -> Result<Self, String> {
let manifest: ApiFixtureManifest =
serde_json::from_str(include_str!("fixtures/api_routes.json"))
.map_err(|error| format!("failed to parse API fixture manifest: {error}"))?;
let root = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/parity/fixtures/api");
let mut routes = Vec::with_capacity(manifest.routes.len());
for route in manifest.routes {
routes.push(ApiFixtureRoute {
method: route.method.to_ascii_uppercase(),
path: route.path,
status: route.status.unwrap_or(200),
content_type: route
.content_type
.unwrap_or_else(|| "application/json; charset=utf-8".to_owned()),
headers: route.headers.unwrap_or_default(),
body: load_body(&root, route.body, route.body_file)?,
});
}
Ok(Self {
routes: Arc::new(routes),
})
}
}
async fn handle_fixture_request(
State(routes): State<Arc<Vec<ApiFixtureRoute>>>,
request: Request<Body>,
) -> Response {
let method = request.method().as_str();
let path = request.uri().path();
let fixture = routes
.iter()
.find(|route| route.method == method && route.path == path)
.or_else(|| {
routes
.iter()
.find(|route| route.method == "ANY" && route.path == path)
});
match fixture {
Some(fixture) => fixture_response(fixture),
None => (
StatusCode::NOT_FOUND,
[(
header::CONTENT_TYPE,
HeaderValue::from_static("application/json; charset=utf-8"),
)],
format!(r#"{{"error":"missing fixture","method":"{method}","path":"{path}"}}"#),
)
.into_response(),
}
}
fn fixture_response(fixture: &ApiFixtureRoute) -> Response {
let status = StatusCode::from_u16(fixture.status).unwrap_or(StatusCode::OK);
let mut response = (status, fixture.body.clone()).into_response();
if let Ok(value) = HeaderValue::from_str(&fixture.content_type) {
response.headers_mut().insert(header::CONTENT_TYPE, value);
}
for (name, value) in &fixture.headers {
if let (Ok(name), Ok(value)) = (
HeaderName::from_bytes(name.as_bytes()),
HeaderValue::from_str(value),
) {
response.headers_mut().insert(name, value);
}
}
response
}
fn load_body(
root: &Path,
body: Option<String>,
body_file: Option<String>,
) -> Result<String, String> {
if let Some(body) = body {
return Ok(body);
}
let Some(body_file) = body_file else {
return Ok(String::new());
};
let path = root.join(body_file);
std::fs::read_to_string(&path)
.map_err(|error| format!("failed to read fixture {}: {error}", path.display()))
}
-57
View File
@@ -1,57 +0,0 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
use super::html_normalizer;
use reqwest::{Client, redirect::Policy};
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct NormalizedResponse {
pub status: u16,
pub content_type: Option<String>,
pub location: Option<String>,
pub body: String,
}
pub fn capture_client() -> Result<Client, String> {
Client::builder()
.redirect(Policy::none())
.build()
.map_err(|error| format!("failed to build capture client: {error}"))
}
pub async fn fetch_route(
client: &Client,
base_url: &str,
route: &str,
cookie: Option<&str>,
) -> Result<NormalizedResponse, String> {
let url = format!("{}{}", base_url.trim_end_matches('/'), route);
let mut request = client.get(&url);
if let Some(cookie) = cookie {
request = request.header(reqwest::header::COOKIE, cookie);
}
let response = request
.send()
.await
.map_err(|error| format!("failed to fetch {url}: {error}"))?;
let status = response.status().as_u16();
let headers = response.headers().clone();
let content_type = headers
.get(reqwest::header::CONTENT_TYPE)
.and_then(|value| value.to_str().ok())
.map(|value| html_normalizer::normalize_header_value("content-type", value));
let location = headers
.get(reqwest::header::LOCATION)
.and_then(|value| value.to_str().ok())
.map(|value| html_normalizer::normalize_header_value("location", value));
let raw_body = response
.text()
.await
.map_err(|error| format!("failed to read body for {url}: {error}"))?;
let body = html_normalizer::normalize_body(content_type.as_deref(), &raw_body);
Ok(NormalizedResponse {
status,
content_type,
location,
body,
})
}
@@ -1,43 +0,0 @@
{
"user": {
"id": "1130650140672000000",
"username": "parity_admin",
"discriminator": 0,
"global_name": "Parity Admin",
"bot": false,
"system": false,
"flags": "0",
"premium_flags": 0,
"avatar": null,
"banner": null,
"bio": "Parity fixture admin user.",
"pronouns": null,
"accent_color": null,
"email": "[email protected]",
"email_verified": true,
"email_bounced": false,
"date_of_birth": "2000-01-01",
"locale": "en-US",
"premium_type": null,
"premium_since": null,
"premium_until": null,
"premium_grace_ends_at": null,
"premium_lifetime_sequence": null,
"temp_banned_until": null,
"pending_deletion_at": null,
"pending_bulk_message_deletion_at": null,
"deletion_reason_code": null,
"deletion_public_reason": null,
"deletion_audit_log_reason": null,
"deletion_scheduled_by": null,
"deletion_scheduled_at": null,
"acls": ["*"],
"traits": [],
"has_totp": true,
"authenticator_types": [1],
"last_active_at": "2026-05-26T15:00:00.000Z",
"last_active_ip": "127.0.0.1",
"last_active_ip_reverse": "localhost",
"last_active_location": "Local"
}
}
@@ -1,56 +0,0 @@
{
"guild": {
"id": "1600000000000000001",
"owner_id": "1508576042312688531",
"owner_username": "parity_user",
"owner_global_name": "Parity User",
"owner_discriminator": "4363",
"name": "Parity Guild",
"vanity_url_code": "parity",
"icon": null,
"banner": null,
"splash": null,
"embed_splash": null,
"features": ["COMMUNITY", "DISCOVERABLE"],
"verification_level": 1,
"mfa_level": 0,
"nsfw_level": 0,
"nsfw": false,
"content_warning_level": 0,
"content_warning_text": null,
"explicit_content_filter": 2,
"default_message_notifications": 1,
"afk_channel_id": null,
"afk_timeout": 300,
"system_channel_id": null,
"system_channel_flags": 0,
"rules_channel_id": null,
"disabled_operations": 0,
"member_count": 128,
"channels": [
{
"id": "1600000000000000101",
"name": "general",
"type": 0,
"position": 0,
"parent_id": null,
"nsfw": false,
"nsfw_override": null,
"content_warning_level": 0,
"content_warning_text": null,
"url": null
}
],
"roles": [
{
"id": "1600000000000000001",
"name": "@everyone",
"color": 0,
"position": 0,
"permissions": "0",
"hoist": false,
"mentionable": false
}
]
}
}
@@ -1,45 +0,0 @@
{
"users": [
{
"id": "1508576042312688531",
"username": "parity_user",
"discriminator": 4363,
"global_name": "Parity User",
"bot": false,
"system": false,
"flags": "0",
"premium_flags": 0,
"avatar": null,
"banner": null,
"bio": null,
"pronouns": null,
"accent_color": null,
"email": "[email protected]",
"email_verified": true,
"email_bounced": false,
"date_of_birth": null,
"locale": "en-US",
"premium_type": null,
"premium_since": null,
"premium_until": null,
"premium_grace_ends_at": null,
"premium_lifetime_sequence": null,
"temp_banned_until": null,
"pending_deletion_at": null,
"pending_bulk_message_deletion_at": null,
"deletion_reason_code": null,
"deletion_public_reason": null,
"deletion_audit_log_reason": null,
"deletion_scheduled_by": null,
"deletion_scheduled_at": null,
"acls": [],
"traits": [],
"has_totp": false,
"authenticator_types": [],
"last_active_at": "2026-05-26T15:00:00.000Z",
"last_active_ip": "127.0.0.1",
"last_active_ip_reverse": "localhost",
"last_active_location": "Local"
}
]
}
@@ -1,4 +0,0 @@
{
"access_token": "parity-access-token",
"token_type": "Bearer"
}
@@ -1,3 +0,0 @@
{
"id": "1130650140672000000"
}
@@ -1,44 +0,0 @@
{
"report_id": "1700000000000000001",
"reporter_id": "1508576042312688531",
"reporter_tag": "parity_user#4363",
"reporter_username": "parity_user",
"reporter_global_name": "Parity User",
"reporter_discriminator": "4363",
"reporter_email": "[email protected]",
"reporter_full_legal_name": null,
"reporter_country_of_residence": null,
"reported_at": "2026-05-26T15:10:45.134Z",
"status": 0,
"report_type": 0,
"category": "nsfw_violation",
"additional_info": "Parity fixture report.",
"reported_user_id": "1508576042312688531",
"reported_user_tag": "parity_user#4363",
"reported_user_username": "parity_user",
"reported_user_global_name": "Parity User",
"reported_user_discriminator": "4363",
"reported_user_avatar_hash": null,
"reported_guild_id": "1600000000000000001",
"reported_guild_name": "Parity Guild",
"reported_message_id": "1800000000000000001",
"reported_channel_id": "1600000000000000101",
"reported_channel_name": "general",
"reported_channel_nsfw": false,
"reported_guild_invite_code": null,
"reported_guild_nsfw_level": 0,
"reported_guild_nsfw": false,
"reported_guild_content_warning_level": null,
"reported_guild_content_warning_text": null,
"reported_channel_nsfw_override": null,
"reported_channel_content_warning_level": null,
"reported_channel_content_warning_text": null,
"reported_channel_effective_nsfw": false,
"reported_channel_effective_content_warning_level": null,
"reported_channel_effective_content_warning_text": null,
"resolved_at": null,
"resolved_by_admin_id": null,
"public_comment": null,
"mutual_dm_channel_id": null,
"message_context": []
}
@@ -1,614 +0,0 @@
{
"report_id": "1556352159220498613",
"reporter_id": "1556352159077892243",
"reporter_tag": "target_a91da2c28aae#2089",
"reporter_username": "target_a91da2c28aae",
"reporter_global_name": "Jordan Target",
"reporter_discriminator": "2089",
"reporter_email": "[email protected]",
"reporter_full_legal_name": null,
"reporter_country_of_residence": null,
"reported_at": "2026-10-04T17:12:47.653Z",
"status": 0,
"report_type": 0,
"category": "spam",
"additional_info": null,
"reported_user_id": "1556352159149195428",
"reported_user_tag": "Harbor_Helper#3966",
"reported_user_username": "Harbor_Helper",
"reported_user_global_name": null,
"reported_user_discriminator": "3966",
"reported_user_avatar_hash": null,
"reported_user_bot": true,
"reported_webhook_id": null,
"reported_webhook_name": null,
"reported_webhook_avatar_hash": null,
"reported_webhook_default_name": null,
"reported_webhook_default_avatar_hash": null,
"reported_webhook_type": null,
"reported_webhook_application_id": null,
"reported_webhook_channel_id": null,
"reported_webhook_guild_id": null,
"reported_webhook_created_at": null,
"reported_webhook_creator_id": null,
"reported_webhook_creator_tag": null,
"reported_webhook_creator_username": null,
"reported_webhook_creator_global_name": null,
"reported_webhook_creator_discriminator": null,
"reported_webhook_creator_avatar_hash": null,
"reported_guild_id": "1556352159090475158",
"reported_guild_name": "Harbor Commons",
"reported_guild_icon_hash": null,
"reported_message_id": "1556352159161778343",
"reported_channel_id": "1556352159103058073",
"reported_channel_name": "general",
"reported_channel_nsfw": false,
"reported_guild_invite_code": null,
"reported_guild_nsfw_level": 0,
"reported_guild_nsfw": false,
"reported_guild_content_warning_level": 0,
"reported_guild_content_warning_text": null,
"reported_channel_nsfw_override": null,
"reported_channel_content_warning_level": 0,
"reported_channel_content_warning_text": null,
"reported_channel_effective_nsfw": false,
"reported_channel_effective_content_warning_level": 0,
"reported_channel_effective_content_warning_text": null,
"resolved_at": null,
"resolved_by_admin_id": null,
"public_comment": null,
"mutual_dm_channel_id": null,
"message_context": [
{
"id": "1556352159115640988",
"channel_id": "1556352159103058073",
"channel_nsfw": false,
"channel_content_warning_level": null,
"channel_content_warning_text": null,
"guild_id": "1556352159090475158",
"guild_nsfw_level": 0,
"guild_nsfw": null,
"guild_content_warning_level": null,
"guild_content_warning_text": null,
"content": "",
"timestamp": "2026-10-04T17:07:50.358Z",
"attachments": [],
"author_id": "1556352159069503633",
"author_username": "reporter_c03a2ee3db86",
"author_global_name": null,
"author_discriminator": "2642",
"author_avatar": null,
"webhook_id": null,
"author_bot": false,
"missing_attachments": []
},
{
"id": "1556352159119835293",
"channel_id": "1556352159103058073",
"channel_nsfw": false,
"channel_content_warning_level": null,
"channel_content_warning_text": null,
"guild_id": "1556352159090475158",
"guild_nsfw_level": 0,
"guild_nsfw": null,
"guild_content_warning_level": null,
"guild_content_warning_text": null,
"content": "",
"timestamp": "2026-10-04T17:07:50.359Z",
"attachments": [],
"author_id": "1556352159073697938",
"author_username": "unverified_7d9456caf467",
"author_global_name": null,
"author_discriminator": "4822",
"author_avatar": null,
"webhook_id": null,
"author_bot": false,
"missing_attachments": []
},
{
"id": "1556352159124029598",
"channel_id": "1556352159103058073",
"channel_nsfw": false,
"channel_content_warning_level": null,
"channel_content_warning_text": null,
"guild_id": "1556352159090475158",
"guild_nsfw_level": 0,
"guild_nsfw": null,
"guild_content_warning_level": null,
"guild_content_warning_text": null,
"content": "",
"timestamp": "2026-10-04T17:07:50.360Z",
"attachments": [],
"author_id": "1556352159077892243",
"author_username": "target_a91da2c28aae",
"author_global_name": null,
"author_discriminator": "2089",
"author_avatar": "f829b914",
"webhook_id": null,
"author_bot": false,
"missing_attachments": []
},
{
"id": "1556352159128223903",
"channel_id": "1556352159103058073",
"channel_nsfw": false,
"channel_content_warning_level": null,
"channel_content_warning_text": null,
"guild_id": "1556352159090475158",
"guild_nsfw_level": 0,
"guild_nsfw": null,
"guild_content_warning_level": null,
"guild_content_warning_text": null,
"content": "Anyone up for a trade this weekend?",
"timestamp": "2026-10-04T17:07:50.361Z",
"attachments": [],
"author_id": "1556352159077892243",
"author_username": "target_a91da2c28aae",
"author_global_name": null,
"author_discriminator": "2089",
"author_avatar": "f829b914",
"webhook_id": null,
"author_bot": false,
"missing_attachments": []
},
{
"id": "1556352159140806818",
"channel_id": "1556352159103058073",
"channel_nsfw": false,
"channel_content_warning_level": null,
"channel_content_warning_text": null,
"guild_id": "1556352159090475158",
"guild_nsfw_level": 0,
"guild_nsfw": null,
"guild_content_warning_level": null,
"guild_content_warning_text": null,
"content": "Weekly bulletin: the garden swap starts Saturday.",
"timestamp": "2026-10-04T17:07:50.364Z",
"attachments": [],
"author_id": "1556352159132418208",
"author_username": "Harbor Bulletin",
"author_global_name": null,
"author_discriminator": "0000",
"author_avatar": null,
"webhook_id": "1556352159132418208",
"author_bot": null,
"missing_attachments": []
},
{
"id": "1556352159153389733",
"channel_id": "1556352159103058073",
"channel_nsfw": false,
"channel_content_warning_level": null,
"channel_content_warning_text": null,
"guild_id": "1556352159090475158",
"guild_nsfw_level": 0,
"guild_nsfw": null,
"guild_content_warning_level": null,
"guild_content_warning_text": null,
"content": "",
"timestamp": "2026-10-04T17:07:50.367Z",
"attachments": [],
"author_id": "1556352159149195428",
"author_username": "Harbor_Helper",
"author_global_name": null,
"author_discriminator": "3966",
"author_avatar": null,
"webhook_id": null,
"author_bot": true,
"missing_attachments": []
},
{
"id": "1556352159161778343",
"channel_id": "1556352159103058073",
"channel_nsfw": false,
"channel_content_warning_level": null,
"channel_content_warning_text": null,
"guild_id": "1556352159090475158",
"guild_nsfw_level": 0,
"guild_nsfw": null,
"guild_content_warning_level": null,
"guild_content_warning_text": null,
"content": "Reminder: the garden swap moved to 10 AM.",
"timestamp": "2026-10-04T17:07:50.369Z",
"attachments": [],
"author_id": "1556352159149195428",
"author_username": "Harbor_Helper",
"author_global_name": null,
"author_discriminator": "3966",
"author_avatar": null,
"webhook_id": null,
"author_bot": true,
"missing_attachments": []
},
{
"id": "1556352159212110003",
"channel_id": "1556352159103058073",
"channel_nsfw": false,
"channel_content_warning_level": null,
"channel_content_warning_text": null,
"guild_id": "1556352159090475158",
"guild_nsfw_level": 0,
"guild_nsfw": null,
"guild_content_warning_level": null,
"guild_content_warning_text": null,
"content": "Night relay: the swap tables are packed away.",
"timestamp": "2026-10-04T17:07:50.381Z",
"attachments": [],
"author_id": "1556352159203721393",
"author_username": "Night Relay",
"author_global_name": null,
"author_discriminator": "0000",
"author_avatar": null,
"webhook_id": "1556352159203721393",
"author_bot": null,
"missing_attachments": []
},
{
"id": "1556352159186944173",
"channel_id": "1556352159103058073",
"channel_nsfw": false,
"channel_content_warning_level": null,
"channel_content_warning_text": null,
"guild_id": "1556352159090475158",
"guild_nsfw_level": 0,
"guild_nsfw": null,
"guild_content_warning_level": null,
"guild_content_warning_text": null,
"content": "",
"timestamp": "2026-10-04T17:07:50.375Z",
"attachments": [],
"author_id": "1556352159216304308",
"author_username": "DeletedUser",
"author_global_name": null,
"author_discriminator": "0000",
"author_avatar": null,
"webhook_id": null,
"author_bot": null,
"missing_attachments": []
}
],
"message_responses": [
{
"id": "1556352159115640988",
"channel_id": "1556352159103058073",
"author": {
"id": "1556352159069503633",
"username": "reporter_c03a2ee3db86",
"discriminator": "2642",
"global_name": "Avery Reporter",
"avatar": null,
"avatar_color": null,
"flags": 0
},
"webhook_id": null,
"type": 7,
"flags": 0,
"content": "",
"timestamp": "2026-10-04T17:07:50.358Z",
"edited_timestamp": null,
"pinned": false,
"mention_everyone": false,
"tts": false,
"mentions": [],
"mention_roles": [],
"mention_channels": null,
"embeds": null,
"attachments": null,
"stickers": null,
"reactions": null,
"message_reference": null,
"message_snapshots": null,
"nonce": null,
"call": null
},
{
"id": "1556352159119835293",
"channel_id": "1556352159103058073",
"author": {
"id": "1556352159073697938",
"username": "unverified_7d9456caf467",
"discriminator": "4822",
"global_name": "Unverified Reporter",
"avatar": null,
"avatar_color": null,
"flags": 0
},
"webhook_id": null,
"type": 7,
"flags": 0,
"content": "",
"timestamp": "2026-10-04T17:07:50.359Z",
"edited_timestamp": null,
"pinned": false,
"mention_everyone": false,
"tts": false,
"mentions": [],
"mention_roles": [],
"mention_channels": null,
"embeds": null,
"attachments": null,
"stickers": null,
"reactions": null,
"message_reference": null,
"message_snapshots": null,
"nonce": null,
"call": null
},
{
"id": "1556352159124029598",
"channel_id": "1556352159103058073",
"author": {
"id": "1556352159077892243",
"username": "target_a91da2c28aae",
"discriminator": "2089",
"global_name": "Jordan Target",
"avatar": "f829b914",
"avatar_color": 4604377,
"flags": 0
},
"webhook_id": null,
"type": 7,
"flags": 0,
"content": "",
"timestamp": "2026-10-04T17:07:50.360Z",
"edited_timestamp": null,
"pinned": false,
"mention_everyone": false,
"tts": false,
"mentions": [],
"mention_roles": [],
"mention_channels": null,
"embeds": null,
"attachments": null,
"stickers": null,
"reactions": null,
"message_reference": null,
"message_snapshots": null,
"nonce": null,
"call": null
},
{
"id": "1556352159128223903",
"channel_id": "1556352159103058073",
"author": {
"id": "1556352159077892243",
"username": "target_a91da2c28aae",
"discriminator": "2089",
"global_name": "Jordan Target",
"avatar": "f829b914",
"avatar_color": 4604377,
"flags": 0
},
"webhook_id": null,
"type": 0,
"flags": 0,
"content": "Anyone up for a trade this weekend?",
"timestamp": "2026-10-04T17:07:50.361Z",
"edited_timestamp": null,
"pinned": false,
"mention_everyone": false,
"tts": false,
"mentions": [],
"mention_roles": [],
"mention_channels": null,
"embeds": null,
"attachments": null,
"stickers": null,
"reactions": null,
"message_reference": null,
"message_snapshots": null,
"nonce": null,
"call": null
},
{
"id": "1556352159140806818",
"channel_id": "1556352159103058073",
"author": {
"id": "1556352159132418208",
"username": "Harbor Bulletin",
"discriminator": "0000",
"global_name": "Harbor Bulletin",
"avatar": null,
"avatar_color": null,
"bot": true,
"flags": 0
},
"webhook_id": "1556352159132418208",
"type": 0,
"flags": 0,
"content": "Weekly bulletin: the garden swap starts Saturday.",
"timestamp": "2026-10-04T17:07:50.364Z",
"edited_timestamp": null,
"pinned": false,
"mention_everyone": false,
"tts": false,
"mentions": [],
"mention_roles": [],
"mention_channels": null,
"embeds": null,
"attachments": null,
"stickers": null,
"reactions": null,
"message_reference": null,
"message_snapshots": null,
"nonce": null,
"call": null
},
{
"id": "1556352159153389733",
"channel_id": "1556352159103058073",
"author": {
"id": "1556352159149195428",
"username": "Harbor_Helper",
"discriminator": "3966",
"global_name": null,
"avatar": null,
"avatar_color": null,
"bot": true,
"flags": 0
},
"webhook_id": null,
"type": 7,
"flags": 0,
"content": "",
"timestamp": "2026-10-04T17:07:50.367Z",
"edited_timestamp": null,
"pinned": false,
"mention_everyone": false,
"tts": false,
"mentions": [],
"mention_roles": [],
"mention_channels": null,
"embeds": null,
"attachments": null,
"stickers": null,
"reactions": null,
"message_reference": null,
"message_snapshots": null,
"nonce": null,
"call": null
},
{
"id": "1556352159161778343",
"channel_id": "1556352159103058073",
"author": {
"id": "1556352159149195428",
"username": "Harbor_Helper",
"discriminator": "3966",
"global_name": null,
"avatar": null,
"avatar_color": null,
"bot": true,
"flags": 0
},
"webhook_id": null,
"type": 0,
"flags": 0,
"content": "Reminder: the garden swap moved to 10 AM.",
"timestamp": "2026-10-04T17:07:50.369Z",
"edited_timestamp": null,
"pinned": false,
"mention_everyone": false,
"tts": false,
"mentions": [],
"mention_roles": [],
"mention_channels": null,
"embeds": null,
"attachments": null,
"stickers": null,
"reactions": null,
"message_reference": null,
"message_snapshots": null,
"nonce": null,
"call": null
},
{
"id": "1556352159212110003",
"channel_id": "1556352159103058073",
"author": {
"id": "1556352159203721393",
"username": "Night Relay",
"discriminator": "0000",
"global_name": "Night Relay",
"avatar": null,
"avatar_color": null,
"bot": true,
"flags": 0
},
"webhook_id": "1556352159203721393",
"type": 0,
"flags": 0,
"content": "Night relay: the swap tables are packed away.",
"timestamp": "2026-10-04T17:07:50.381Z",
"edited_timestamp": null,
"pinned": false,
"mention_everyone": false,
"tts": false,
"mentions": [],
"mention_roles": [],
"mention_channels": null,
"embeds": null,
"attachments": null,
"stickers": null,
"reactions": null,
"message_reference": null,
"message_snapshots": null,
"nonce": null,
"call": null
},
{
"id": "1556352159186944173",
"channel_id": "1556352159103058073",
"author": {
"id": "1556352159216304308",
"username": "DeletedUser",
"discriminator": "0000",
"global_name": "Deleted User",
"avatar": null,
"avatar_color": null,
"flags": 0
},
"webhook_id": null,
"type": 7,
"flags": 0,
"content": "",
"timestamp": "2026-10-04T17:07:50.375Z",
"edited_timestamp": null,
"pinned": false,
"mention_everyone": false,
"tts": false,
"mentions": [],
"mention_roles": [],
"mention_channels": null,
"embeds": null,
"attachments": null,
"stickers": null,
"reactions": null,
"message_reference": null,
"message_snapshots": null,
"nonce": null,
"call": null
}
],
"reported_profile_snapshot": {
"captured_at": "2026-10-04T17:12:47.653Z",
"user": {
"id": "1556352159149195428",
"username": "Harbor_Helper",
"discriminator": "3966",
"global_name": null,
"bio": null,
"pronouns": null,
"avatar": null,
"banner": null
},
"member": {
"guild_id": "1556352159090475158",
"nick": null,
"bio": null,
"pronouns": null,
"joined_at": "2026-10-04T17:12:47.614Z",
"avatar": null,
"banner": null
},
"guild": null
},
"reason": "spam",
"reason_label": "Spam",
"reason_highest_priority": false,
"flow": {
"revision_hash": "16781a19a797f23f",
"surface": "in_app",
"locale": "en-US",
"steps": [
{
"screen_id": "root_message",
"screen_title": "Report message",
"option_id": "spam",
"option_label": "Spam or unwanted ads",
"items": []
}
]
},
"reporter_good_faith_confirmed": null
}
@@ -1,572 +0,0 @@
{
"report_id": "1556114449264280806",
"reporter_id": "1556114449113285826",
"reporter_tag": "reporter_010908032cb3#7915",
"reporter_username": "reporter_010908032cb3",
"reporter_global_name": "Avery Reporter",
"reporter_discriminator": "7915",
"reporter_email": "[email protected]",
"reporter_full_legal_name": null,
"reporter_country_of_residence": null,
"reported_at": "2026-10-04T01:30:41.372Z",
"status": 0,
"report_type": 0,
"category": "spam",
"additional_info": null,
"reported_user_id": null,
"reported_user_tag": null,
"reported_user_username": null,
"reported_user_global_name": null,
"reported_user_discriminator": null,
"reported_user_avatar_hash": null,
"reported_webhook_id": "1556114449176200401",
"reported_webhook_name": "Harbor Bulletin",
"reported_webhook_avatar_hash": null,
"reported_webhook_default_name": "Harbor Bulletin",
"reported_webhook_default_avatar_hash": null,
"reported_webhook_type": 1,
"reported_webhook_application_id": null,
"reported_webhook_channel_id": "1556114449146840266",
"reported_webhook_guild_id": "1556114449134257351",
"reported_webhook_created_at": "2026-10-04T01:23:15.892Z",
"reported_webhook_creator_id": "1556114449125868741",
"reported_webhook_creator_tag": "owner_aa696aafce86#7021",
"reported_webhook_creator_username": "owner_aa696aafce86",
"reported_webhook_creator_global_name": "Morgan Owner",
"reported_webhook_creator_discriminator": "7021",
"reported_webhook_creator_avatar_hash": null,
"reported_guild_id": "1556114449134257351",
"reported_guild_name": "Harbor Commons",
"reported_guild_icon_hash": null,
"reported_message_id": "1556114449184589011",
"reported_channel_id": "1556114449146840266",
"reported_channel_name": "general",
"reported_channel_nsfw": false,
"reported_guild_invite_code": null,
"reported_guild_nsfw_level": 0,
"reported_guild_nsfw": false,
"reported_guild_content_warning_level": 0,
"reported_guild_content_warning_text": null,
"reported_channel_nsfw_override": null,
"reported_channel_content_warning_level": 0,
"reported_channel_content_warning_text": null,
"reported_channel_effective_nsfw": false,
"reported_channel_effective_content_warning_level": 0,
"reported_channel_effective_content_warning_text": null,
"resolved_at": null,
"resolved_by_admin_id": null,
"public_comment": null,
"mutual_dm_channel_id": null,
"message_context": [
{
"id": "1556114449159423181",
"channel_id": "1556114449146840266",
"channel_nsfw": false,
"channel_content_warning_level": null,
"channel_content_warning_text": null,
"guild_id": "1556114449134257351",
"guild_nsfw_level": 0,
"guild_nsfw": null,
"guild_content_warning_level": null,
"guild_content_warning_text": null,
"content": "",
"timestamp": "2026-10-04T01:23:15.888Z",
"attachments": [],
"author_id": "1556114449113285826",
"author_username": "reporter_010908032cb3",
"author_global_name": null,
"author_discriminator": "7915",
"author_avatar": null,
"webhook_id": null
},
{
"id": "1556114449163617486",
"channel_id": "1556114449146840266",
"channel_nsfw": false,
"channel_content_warning_level": null,
"channel_content_warning_text": null,
"guild_id": "1556114449134257351",
"guild_nsfw_level": 0,
"guild_nsfw": null,
"guild_content_warning_level": null,
"guild_content_warning_text": null,
"content": "",
"timestamp": "2026-10-04T01:23:15.889Z",
"attachments": [],
"author_id": "1556114449117480131",
"author_username": "unverified_b8bab8c7a8c5",
"author_global_name": null,
"author_discriminator": "0943",
"author_avatar": null,
"webhook_id": null
},
{
"id": "1556114449167811791",
"channel_id": "1556114449146840266",
"channel_nsfw": false,
"channel_content_warning_level": null,
"channel_content_warning_text": null,
"guild_id": "1556114449134257351",
"guild_nsfw_level": 0,
"guild_nsfw": null,
"guild_content_warning_level": null,
"guild_content_warning_text": null,
"content": "",
"timestamp": "2026-10-04T01:23:15.890Z",
"attachments": [],
"author_id": "1556114449121674436",
"author_username": "target_8b1ee6a349df",
"author_global_name": null,
"author_discriminator": "9014",
"author_avatar": "f829b914",
"webhook_id": null
},
{
"id": "1556114449172006096",
"channel_id": "1556114449146840266",
"channel_nsfw": false,
"channel_content_warning_level": null,
"channel_content_warning_text": null,
"guild_id": "1556114449134257351",
"guild_nsfw_level": 0,
"guild_nsfw": null,
"guild_content_warning_level": null,
"guild_content_warning_text": null,
"content": "Anyone up for a trade this weekend?",
"timestamp": "2026-10-04T01:23:15.891Z",
"attachments": [],
"author_id": "1556114449121674436",
"author_username": "target_8b1ee6a349df",
"author_global_name": null,
"author_discriminator": "9014",
"author_avatar": "f829b914",
"webhook_id": null
},
{
"id": "1556114449184589011",
"channel_id": "1556114449146840266",
"channel_nsfw": false,
"channel_content_warning_level": null,
"channel_content_warning_text": null,
"guild_id": "1556114449134257351",
"guild_nsfw_level": 0,
"guild_nsfw": null,
"guild_content_warning_level": null,
"guild_content_warning_text": null,
"content": "Weekly bulletin: the garden swap starts Saturday.",
"timestamp": "2026-10-04T01:23:15.894Z",
"attachments": [],
"author_id": "1556114449176200401",
"author_username": "Harbor Bulletin",
"author_global_name": null,
"author_discriminator": "0000",
"author_avatar": null,
"webhook_id": "1556114449176200401"
},
{
"id": "1556114449255892196",
"channel_id": "1556114449146840266",
"channel_nsfw": false,
"channel_content_warning_level": null,
"channel_content_warning_text": null,
"guild_id": "1556114449134257351",
"guild_nsfw_level": 0,
"guild_nsfw": null,
"guild_content_warning_level": null,
"guild_content_warning_text": null,
"content": "Night relay: the swap tables are packed away.",
"timestamp": "2026-10-04T01:23:15.911Z",
"attachments": [],
"author_id": "1556114449247503586",
"author_username": "Night Relay",
"author_global_name": null,
"author_discriminator": "0000",
"author_avatar": null,
"webhook_id": "1556114449247503586"
},
{
"id": "1556114449230726366",
"channel_id": "1556114449146840266",
"channel_nsfw": false,
"channel_content_warning_level": null,
"channel_content_warning_text": null,
"guild_id": "1556114449134257351",
"guild_nsfw_level": 0,
"guild_nsfw": null,
"guild_content_warning_level": null,
"guild_content_warning_text": null,
"content": "",
"timestamp": "2026-10-04T01:23:15.905Z",
"attachments": [],
"author_id": "1556114449260086501",
"author_username": "DeletedUser",
"author_global_name": null,
"author_discriminator": "0000",
"author_avatar": null,
"webhook_id": null
},
{
"id": "1556114449205560536",
"channel_id": "1556114449146840266",
"channel_nsfw": false,
"channel_content_warning_level": null,
"channel_content_warning_text": null,
"guild_id": "1556114449134257351",
"guild_nsfw_level": 0,
"guild_nsfw": null,
"guild_content_warning_level": null,
"guild_content_warning_text": null,
"content": "Reminder: the garden swap moved to 10 AM.",
"timestamp": "2026-10-04T01:23:15.899Z",
"attachments": [],
"author_id": "1556114449192977621",
"author_username": "Harbor_Helper",
"author_global_name": null,
"author_discriminator": "1238",
"author_avatar": null,
"webhook_id": null
},
{
"id": "1556114449197171926",
"channel_id": "1556114449146840266",
"channel_nsfw": false,
"channel_content_warning_level": null,
"channel_content_warning_text": null,
"guild_id": "1556114449134257351",
"guild_nsfw_level": 0,
"guild_nsfw": null,
"guild_content_warning_level": null,
"guild_content_warning_text": null,
"content": "",
"timestamp": "2026-10-04T01:23:15.897Z",
"attachments": [],
"author_id": "1556114449192977621",
"author_username": "Harbor_Helper",
"author_global_name": null,
"author_discriminator": "1238",
"author_avatar": null,
"webhook_id": null
}
],
"message_responses": [
{
"id": "1556114449159423181",
"channel_id": "1556114449146840266",
"author": {
"id": "1556114449113285826",
"username": "reporter_010908032cb3",
"discriminator": "7915",
"global_name": "Avery Reporter",
"avatar": null,
"avatar_color": null,
"flags": 0
},
"webhook_id": null,
"type": 7,
"flags": 0,
"content": "",
"timestamp": "2026-10-04T01:23:15.888Z",
"edited_timestamp": null,
"pinned": false,
"mention_everyone": false,
"tts": false,
"mentions": [],
"mention_roles": [],
"mention_channels": null,
"embeds": null,
"attachments": null,
"stickers": null,
"reactions": null,
"message_reference": null,
"message_snapshots": null,
"nonce": null,
"call": null
},
{
"id": "1556114449163617486",
"channel_id": "1556114449146840266",
"author": {
"id": "1556114449117480131",
"username": "unverified_b8bab8c7a8c5",
"discriminator": "0943",
"global_name": "Unverified Reporter",
"avatar": null,
"avatar_color": null,
"flags": 0
},
"webhook_id": null,
"type": 7,
"flags": 0,
"content": "",
"timestamp": "2026-10-04T01:23:15.889Z",
"edited_timestamp": null,
"pinned": false,
"mention_everyone": false,
"tts": false,
"mentions": [],
"mention_roles": [],
"mention_channels": null,
"embeds": null,
"attachments": null,
"stickers": null,
"reactions": null,
"message_reference": null,
"message_snapshots": null,
"nonce": null,
"call": null
},
{
"id": "1556114449167811791",
"channel_id": "1556114449146840266",
"author": {
"id": "1556114449121674436",
"username": "target_8b1ee6a349df",
"discriminator": "9014",
"global_name": "Jordan Target",
"avatar": "f829b914",
"avatar_color": 4604377,
"flags": 0
},
"webhook_id": null,
"type": 7,
"flags": 0,
"content": "",
"timestamp": "2026-10-04T01:23:15.890Z",
"edited_timestamp": null,
"pinned": false,
"mention_everyone": false,
"tts": false,
"mentions": [],
"mention_roles": [],
"mention_channels": null,
"embeds": null,
"attachments": null,
"stickers": null,
"reactions": null,
"message_reference": null,
"message_snapshots": null,
"nonce": null,
"call": null
},
{
"id": "1556114449172006096",
"channel_id": "1556114449146840266",
"author": {
"id": "1556114449121674436",
"username": "target_8b1ee6a349df",
"discriminator": "9014",
"global_name": "Jordan Target",
"avatar": "f829b914",
"avatar_color": 4604377,
"flags": 0
},
"webhook_id": null,
"type": 0,
"flags": 0,
"content": "Anyone up for a trade this weekend?",
"timestamp": "2026-10-04T01:23:15.891Z",
"edited_timestamp": null,
"pinned": false,
"mention_everyone": false,
"tts": false,
"mentions": [],
"mention_roles": [],
"mention_channels": null,
"embeds": null,
"attachments": null,
"stickers": null,
"reactions": null,
"message_reference": null,
"message_snapshots": null,
"nonce": null,
"call": null
},
{
"id": "1556114449184589011",
"channel_id": "1556114449146840266",
"author": {
"id": "1556114449176200401",
"username": "Harbor Bulletin",
"discriminator": "0000",
"global_name": "Harbor Bulletin",
"avatar": null,
"avatar_color": null,
"bot": true,
"flags": 0
},
"webhook_id": "1556114449176200401",
"type": 0,
"flags": 0,
"content": "Weekly bulletin: the garden swap starts Saturday.",
"timestamp": "2026-10-04T01:23:15.894Z",
"edited_timestamp": null,
"pinned": false,
"mention_everyone": false,
"tts": false,
"mentions": [],
"mention_roles": [],
"mention_channels": null,
"embeds": null,
"attachments": null,
"stickers": null,
"reactions": null,
"message_reference": null,
"message_snapshots": null,
"nonce": null,
"call": null
},
{
"id": "1556114449255892196",
"channel_id": "1556114449146840266",
"author": {
"id": "1556114449247503586",
"username": "Night Relay",
"discriminator": "0000",
"global_name": "Night Relay",
"avatar": null,
"avatar_color": null,
"bot": true,
"flags": 0
},
"webhook_id": "1556114449247503586",
"type": 0,
"flags": 0,
"content": "Night relay: the swap tables are packed away.",
"timestamp": "2026-10-04T01:23:15.911Z",
"edited_timestamp": null,
"pinned": false,
"mention_everyone": false,
"tts": false,
"mentions": [],
"mention_roles": [],
"mention_channels": null,
"embeds": null,
"attachments": null,
"stickers": null,
"reactions": null,
"message_reference": null,
"message_snapshots": null,
"nonce": null,
"call": null
},
{
"id": "1556114449230726366",
"channel_id": "1556114449146840266",
"author": {
"id": "1556114449260086501",
"username": "DeletedUser",
"discriminator": "0000",
"global_name": "Deleted User",
"avatar": null,
"avatar_color": null,
"flags": 0
},
"webhook_id": null,
"type": 7,
"flags": 0,
"content": "",
"timestamp": "2026-10-04T01:23:15.905Z",
"edited_timestamp": null,
"pinned": false,
"mention_everyone": false,
"tts": false,
"mentions": [],
"mention_roles": [],
"mention_channels": null,
"embeds": null,
"attachments": null,
"stickers": null,
"reactions": null,
"message_reference": null,
"message_snapshots": null,
"nonce": null,
"call": null
},
{
"id": "1556114449205560536",
"channel_id": "1556114449146840266",
"author": {
"id": "1556114449192977621",
"username": "Harbor_Helper",
"discriminator": "1238",
"global_name": null,
"avatar": null,
"avatar_color": null,
"bot": true,
"flags": 0
},
"webhook_id": null,
"type": 0,
"flags": 0,
"content": "Reminder: the garden swap moved to 10 AM.",
"timestamp": "2026-10-04T01:23:15.899Z",
"edited_timestamp": null,
"pinned": false,
"mention_everyone": false,
"tts": false,
"mentions": [],
"mention_roles": [],
"mention_channels": null,
"embeds": null,
"attachments": null,
"stickers": null,
"reactions": null,
"message_reference": null,
"message_snapshots": null,
"nonce": null,
"call": null
},
{
"id": "1556114449197171926",
"channel_id": "1556114449146840266",
"author": {
"id": "1556114449192977621",
"username": "Harbor_Helper",
"discriminator": "1238",
"global_name": null,
"avatar": null,
"avatar_color": null,
"bot": true,
"flags": 0
},
"webhook_id": null,
"type": 7,
"flags": 0,
"content": "",
"timestamp": "2026-10-04T01:23:15.897Z",
"edited_timestamp": null,
"pinned": false,
"mention_everyone": false,
"tts": false,
"mentions": [],
"mention_roles": [],
"mention_channels": null,
"embeds": null,
"attachments": null,
"stickers": null,
"reactions": null,
"message_reference": null,
"message_snapshots": null,
"nonce": null,
"call": null
}
],
"reason": "spam",
"reason_label": "Spam",
"reason_highest_priority": false,
"flow": {
"revision_hash": "16781a19a797f23f",
"surface": "in_app",
"locale": "en-US",
"steps": [
{
"screen_id": "root_message",
"screen_title": "Report message",
"option_id": "spam",
"option_label": "Spam or unwanted ads",
"items": []
}
]
},
"reporter_good_faith_confirmed": null
}
@@ -1,21 +0,0 @@
{
"guilds": [
{
"id": "1600000000000000001",
"name": "Parity Guild",
"icon": null,
"banner": null,
"owner_id": "1508576042312688531",
"owner_username": "parity_user",
"owner_global_name": "Parity User",
"owner_discriminator": "4363",
"member_count": 128,
"features": ["COMMUNITY", "DISCOVERABLE"],
"nsfw_level": 0,
"nsfw": false,
"content_warning_level": 0,
"content_warning_text": null
}
],
"total": 1
}
@@ -1,51 +0,0 @@
{
"reports": [
{
"report_id": "1700000000000000001",
"reporter_id": "1508576042312688531",
"reporter_tag": "parity_user#4363",
"reporter_username": "parity_user",
"reporter_global_name": "Parity User",
"reporter_discriminator": "4363",
"reporter_email": "[email protected]",
"reporter_full_legal_name": null,
"reporter_country_of_residence": null,
"reported_at": "2026-05-26T15:10:45.134Z",
"status": 0,
"report_type": 0,
"category": "nsfw_violation",
"additional_info": "Parity fixture report.",
"reported_user_id": "1508576042312688531",
"reported_user_tag": "parity_user#4363",
"reported_user_username": "parity_user",
"reported_user_global_name": "Parity User",
"reported_user_discriminator": "4363",
"reported_user_avatar_hash": null,
"reported_guild_id": "1600000000000000001",
"reported_guild_name": "Parity Guild",
"reported_message_id": "1800000000000000001",
"reported_channel_id": "1600000000000000101",
"reported_channel_name": "general",
"reported_channel_nsfw": false,
"reported_guild_invite_code": null,
"reported_guild_nsfw_level": 0,
"reported_guild_nsfw": false,
"reported_guild_content_warning_level": null,
"reported_guild_content_warning_text": null,
"reported_channel_nsfw_override": null,
"reported_channel_content_warning_level": null,
"reported_channel_content_warning_text": null,
"reported_channel_effective_nsfw": false,
"reported_channel_effective_content_warning_level": null,
"reported_channel_effective_content_warning_text": null,
"resolved_at": null,
"resolved_by_admin_id": null,
"public_comment": null,
"mutual_dm_channel_id": null,
"message_context": []
}
],
"total": 1,
"offset": 0,
"limit": 25
}
@@ -1,228 +0,0 @@
{
"reports": [
{
"report_id": "1556008115701286089",
"reporter_id": "1556008115617399989",
"reporter_tag": "reporter_f189c9468c17#0896",
"reporter_username": "reporter_f189c9468c17",
"reporter_global_name": "Avery Reporter",
"reporter_discriminator": "0896",
"reporter_email": "[email protected]",
"reporter_full_legal_name": null,
"reporter_country_of_residence": null,
"reported_at": "2026-10-03T18:30:33.848Z",
"status": 0,
"report_type": 0,
"category": "child_safety",
"additional_info": null,
"reported_user_id": "1556008115625788599",
"reported_user_tag": "target_4a5a5ce687ff#6741",
"reported_user_username": "target_4a5a5ce687ff",
"reported_user_global_name": "Jordan Target",
"reported_user_discriminator": "6741",
"reported_user_avatar_hash": "f829b914",
"reported_guild_id": null,
"reported_guild_name": null,
"reported_guild_icon_hash": null,
"reported_message_id": "1556008115697091784",
"reported_channel_id": "1556008115692897479",
"reported_channel_name": null,
"reported_channel_nsfw": false,
"reported_guild_invite_code": null,
"reported_guild_nsfw_level": null,
"reported_guild_nsfw": null,
"reported_guild_content_warning_level": null,
"reported_guild_content_warning_text": null,
"reported_channel_nsfw_override": null,
"reported_channel_content_warning_level": 0,
"reported_channel_content_warning_text": null,
"reported_channel_effective_nsfw": null,
"reported_channel_effective_content_warning_level": null,
"reported_channel_effective_content_warning_text": null,
"resolved_at": null,
"resolved_by_admin_id": null,
"public_comment": null,
"reason": "csam",
"reason_label": "Child sexual abuse material",
"reason_highest_priority": true,
"flow": {
"revision_hash": "16781a19a797f23f",
"surface": "in_app",
"locale": "en-US",
"steps": [
{
"screen_id": "root_message",
"screen_title": "Report message",
"option_id": "abuse",
"option_label": "Abusive or harmful content",
"items": []
},
{
"screen_id": "abuse",
"screen_title": "What does it involve?",
"option_id": "sexual",
"option_label": "Sexual content",
"items": []
},
{
"screen_id": "sexual",
"screen_title": "Tell us about the sexual content",
"option_id": "minor_sexual",
"option_label": "Sexualizing a minor",
"items": []
},
{
"screen_id": "minor_sexual",
"screen_title": "What did you see involving a minor?",
"option_id": "csam",
"option_label": "Real or AI-generated images or videos of child sexual abuse",
"items": []
}
]
},
"reporter_good_faith_confirmed": null
},
{
"report_id": "1556008115705480394",
"reporter_id": "1556008115617399989",
"reporter_tag": "reporter_f189c9468c17#0896",
"reporter_username": "reporter_f189c9468c17",
"reporter_global_name": "Avery Reporter",
"reporter_discriminator": "0896",
"reporter_email": "[email protected]",
"reporter_full_legal_name": null,
"reporter_country_of_residence": null,
"reported_at": "2026-10-03T18:30:33.852Z",
"status": 0,
"report_type": 1,
"category": "harassment",
"additional_info": null,
"reported_user_id": "1556008115625788599",
"reported_user_tag": "target_4a5a5ce687ff#6741",
"reported_user_username": "target_4a5a5ce687ff",
"reported_user_global_name": "Jordan Target",
"reported_user_discriminator": "6741",
"reported_user_avatar_hash": "f829b914",
"reported_guild_id": null,
"reported_guild_name": null,
"reported_guild_icon_hash": null,
"reported_message_id": null,
"reported_channel_id": null,
"reported_channel_name": null,
"reported_channel_nsfw": null,
"reported_guild_invite_code": null,
"reported_guild_nsfw_level": null,
"reported_guild_nsfw": null,
"reported_guild_content_warning_level": null,
"reported_guild_content_warning_text": null,
"reported_channel_nsfw_override": null,
"reported_channel_content_warning_level": null,
"reported_channel_content_warning_text": null,
"reported_channel_effective_nsfw": null,
"reported_channel_effective_content_warning_level": null,
"reported_channel_effective_content_warning_text": null,
"resolved_at": null,
"resolved_by_admin_id": null,
"public_comment": null,
"reason": "harassment",
"reason_label": "Harassment or bullying",
"reason_highest_priority": false,
"flow": {
"revision_hash": "22a1e877519c31ca",
"surface": "in_app",
"locale": "de",
"steps": [
{
"screen_id": "profile_intro",
"screen_title": "Report profile",
"option_id": null,
"option_label": null,
"items": []
},
{
"screen_id": "profile_parts",
"screen_title": "Which parts of their profile are a problem?",
"option_id": null,
"option_label": null,
"items": [
{
"id": "photo",
"label": "Pictures"
},
{
"id": "profile_text",
"label": "Profile text"
}
]
},
{
"screen_id": "root_user",
"screen_title": "What's wrong with their profile?",
"option_id": "abuse",
"option_label": "Abusive or harmful content",
"items": []
},
{
"screen_id": "profile_abuse",
"screen_title": "What's harmful about their profile?",
"option_id": "harassment",
"option_label": "Their profile harasses or targets me or someone else",
"items": []
}
]
},
"reporter_good_faith_confirmed": null
},
{
"report_id": "1556008115709674699",
"reporter_id": "1556008115617399989",
"reporter_tag": "reporter_f189c9468c17#0896",
"reporter_username": "reporter_f189c9468c17",
"reporter_global_name": "Avery Reporter",
"reporter_discriminator": "0896",
"reporter_email": "[email protected]",
"reporter_full_legal_name": null,
"reporter_country_of_residence": null,
"reported_at": "2026-10-03T18:30:33.856Z",
"status": 0,
"report_type": 0,
"category": "spam",
"additional_info": null,
"reported_user_id": "1556008115625788599",
"reported_user_tag": "target_4a5a5ce687ff#6741",
"reported_user_username": "target_4a5a5ce687ff",
"reported_user_global_name": "Jordan Target",
"reported_user_discriminator": "6741",
"reported_user_avatar_hash": "f829b914",
"reported_guild_id": "1556008115638371514",
"reported_guild_name": "Harbor Commons",
"reported_guild_icon_hash": null,
"reported_message_id": "1556008115676120259",
"reported_channel_id": "1556008115650954429",
"reported_channel_name": "general",
"reported_channel_nsfw": false,
"reported_guild_invite_code": null,
"reported_guild_nsfw_level": 0,
"reported_guild_nsfw": false,
"reported_guild_content_warning_level": 0,
"reported_guild_content_warning_text": null,
"reported_channel_nsfw_override": null,
"reported_channel_content_warning_level": 0,
"reported_channel_content_warning_text": null,
"reported_channel_effective_nsfw": false,
"reported_channel_effective_content_warning_level": 0,
"reported_channel_effective_content_warning_text": null,
"resolved_at": null,
"resolved_by_admin_id": null,
"public_comment": null,
"reason": null,
"reason_label": null,
"reason_highest_priority": null,
"flow": null,
"reporter_good_faith_confirmed": null
}
],
"total": 3,
"offset": 0,
"limit": 25
}
@@ -1,161 +0,0 @@
{
"reports": [
{
"report_id": "1556114449264280806",
"reporter_id": "1556114449113285826",
"reporter_tag": "reporter_010908032cb3#7915",
"reporter_username": "reporter_010908032cb3",
"reporter_global_name": "Avery Reporter",
"reporter_discriminator": "7915",
"reporter_email": "[email protected]",
"reporter_full_legal_name": null,
"reporter_country_of_residence": null,
"reported_at": "2026-10-04T01:30:41.372Z",
"status": 0,
"report_type": 0,
"category": "spam",
"additional_info": null,
"reported_user_id": null,
"reported_user_tag": null,
"reported_user_username": null,
"reported_user_global_name": null,
"reported_user_discriminator": null,
"reported_user_avatar_hash": null,
"reported_webhook_id": "1556114449176200401",
"reported_webhook_name": "Harbor Bulletin",
"reported_webhook_avatar_hash": null,
"reported_webhook_default_name": "Harbor Bulletin",
"reported_webhook_default_avatar_hash": null,
"reported_webhook_type": 1,
"reported_webhook_application_id": null,
"reported_webhook_channel_id": "1556114449146840266",
"reported_webhook_guild_id": "1556114449134257351",
"reported_webhook_created_at": "2026-10-04T01:23:15.892Z",
"reported_webhook_creator_id": "1556114449125868741",
"reported_webhook_creator_tag": "owner_aa696aafce86#7021",
"reported_webhook_creator_username": "owner_aa696aafce86",
"reported_webhook_creator_global_name": "Morgan Owner",
"reported_webhook_creator_discriminator": "7021",
"reported_webhook_creator_avatar_hash": null,
"reported_guild_id": "1556114449134257351",
"reported_guild_name": "Harbor Commons",
"reported_guild_icon_hash": null,
"reported_message_id": "1556114449184589011",
"reported_channel_id": "1556114449146840266",
"reported_channel_name": "general",
"reported_channel_nsfw": false,
"reported_guild_invite_code": null,
"reported_guild_nsfw_level": 0,
"reported_guild_nsfw": false,
"reported_guild_content_warning_level": 0,
"reported_guild_content_warning_text": null,
"reported_channel_nsfw_override": null,
"reported_channel_content_warning_level": 0,
"reported_channel_content_warning_text": null,
"reported_channel_effective_nsfw": false,
"reported_channel_effective_content_warning_level": 0,
"reported_channel_effective_content_warning_text": null,
"resolved_at": null,
"resolved_by_admin_id": null,
"public_comment": null,
"reason": "spam",
"reason_label": "Spam",
"reason_highest_priority": false,
"flow": {
"revision_hash": "16781a19a797f23f",
"surface": "in_app",
"locale": "en-US",
"steps": [
{
"screen_id": "root_message",
"screen_title": "Report message",
"option_id": "spam",
"option_label": "Spam or unwanted ads",
"items": []
}
]
},
"reporter_good_faith_confirmed": null
},
{
"report_id": "1556114449268475111",
"reporter_id": "1556114449113285826",
"reporter_tag": "reporter_010908032cb3#7915",
"reporter_username": "reporter_010908032cb3",
"reporter_global_name": "Avery Reporter",
"reporter_discriminator": "7915",
"reporter_email": "[email protected]",
"reporter_full_legal_name": null,
"reporter_country_of_residence": null,
"reported_at": "2026-10-04T01:30:41.375Z",
"status": 0,
"report_type": 0,
"category": "spam",
"additional_info": null,
"reported_user_id": "1556114449192977621",
"reported_user_tag": "Harbor_Helper#1238",
"reported_user_username": "Harbor_Helper",
"reported_user_global_name": null,
"reported_user_discriminator": "1238",
"reported_user_avatar_hash": null,
"reported_webhook_id": null,
"reported_webhook_name": null,
"reported_webhook_avatar_hash": null,
"reported_webhook_default_name": null,
"reported_webhook_default_avatar_hash": null,
"reported_webhook_type": null,
"reported_webhook_application_id": null,
"reported_webhook_channel_id": null,
"reported_webhook_guild_id": null,
"reported_webhook_created_at": null,
"reported_webhook_creator_id": null,
"reported_webhook_creator_tag": null,
"reported_webhook_creator_username": null,
"reported_webhook_creator_global_name": null,
"reported_webhook_creator_discriminator": null,
"reported_webhook_creator_avatar_hash": null,
"reported_guild_id": "1556114449134257351",
"reported_guild_name": "Harbor Commons",
"reported_guild_icon_hash": null,
"reported_message_id": "1556114449205560536",
"reported_channel_id": "1556114449146840266",
"reported_channel_name": "general",
"reported_channel_nsfw": false,
"reported_guild_invite_code": null,
"reported_guild_nsfw_level": 0,
"reported_guild_nsfw": false,
"reported_guild_content_warning_level": 0,
"reported_guild_content_warning_text": null,
"reported_channel_nsfw_override": null,
"reported_channel_content_warning_level": 0,
"reported_channel_content_warning_text": null,
"reported_channel_effective_nsfw": false,
"reported_channel_effective_content_warning_level": 0,
"reported_channel_effective_content_warning_text": null,
"resolved_at": null,
"resolved_by_admin_id": null,
"public_comment": null,
"reason": "spam",
"reason_label": "Spam",
"reason_highest_priority": false,
"flow": {
"revision_hash": "16781a19a797f23f",
"surface": "in_app",
"locale": "en-US",
"steps": [
{
"screen_id": "root_message",
"screen_title": "Report message",
"option_id": "spam",
"option_label": "Spam or unwanted ads",
"items": []
}
]
},
"reporter_good_faith_confirmed": null
}
],
"total": 2,
"offset": 0,
"limit": 50
}
@@ -1,46 +0,0 @@
{
"users": [
{
"id": "1508576042312688531",
"username": "parity_user",
"discriminator": 4363,
"global_name": "Parity User",
"bot": false,
"system": false,
"flags": "0",
"premium_flags": 0,
"avatar": null,
"banner": null,
"bio": null,
"pronouns": null,
"accent_color": null,
"email": "[email protected]",
"email_verified": true,
"email_bounced": false,
"date_of_birth": null,
"locale": "en-US",
"premium_type": null,
"premium_since": null,
"premium_until": null,
"premium_grace_ends_at": null,
"premium_lifetime_sequence": null,
"temp_banned_until": null,
"pending_deletion_at": null,
"pending_bulk_message_deletion_at": null,
"deletion_reason_code": null,
"deletion_public_reason": null,
"deletion_audit_log_reason": null,
"deletion_scheduled_by": null,
"deletion_scheduled_at": null,
"acls": [],
"traits": [],
"has_totp": false,
"authenticator_types": [],
"last_active_at": "2026-05-26T15:00:00.000Z",
"last_active_ip": "127.0.0.1",
"last_active_ip_reverse": "localhost",
"last_active_location": "Local"
}
],
"total": 1
}
@@ -1,59 +0,0 @@
{
"routes": [
{
"method": "GET",
"path": "/admin/users/@me",
"body_file": "admin_user_me.json"
},
{
"method": "GET",
"path": "/users/@me",
"body_file": "oauth_user_me.json"
},
{
"method": "POST",
"path": "/oauth2/token",
"body_file": "oauth_token.json"
},
{
"method": "POST",
"path": "/oauth2/token/revoke",
"body": "{}"
},
{
"method": "GET",
"path": "/admin/users",
"body_file": "search_users.json"
},
{
"method": "GET",
"path": "/admin/users/1508576042312688531",
"body_file": "lookup_user.json"
},
{
"method": "GET",
"path": "/admin/guilds",
"body_file": "search_guilds.json"
},
{
"method": "GET",
"path": "/admin/guilds/1600000000000000001",
"body_file": "lookup_guild.json"
},
{
"method": "GET",
"path": "/admin/reports",
"body_file": "search_reports.json"
},
{
"method": "GET",
"path": "/admin/reports/1700000000000000001",
"body_file": "report_detail.json"
},
{
"method": "GET",
"path": "/admin/api-keys",
"body": "[]"
}
]
}
@@ -1,648 +0,0 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
use serde_json::Value;
#[derive(Clone, Debug, Eq, PartialEq)]
struct Attribute {
name: String,
value: Option<String>,
}
pub fn normalize_body(content_type: Option<&str>, body: &str) -> String {
let content_type = content_type.unwrap_or("").to_ascii_lowercase();
if content_type.contains("css") {
return normalize_css_body(body);
}
if content_type.contains("html") {
return normalize_html(body);
}
if content_type.contains("json")
&& let Ok(value) = serde_json::from_str::<Value>(body)
{
return serde_json::to_string(&value).unwrap_or_else(|_| normalize_text(body));
}
normalize_text(body)
}
fn normalize_css_body(body: &str) -> String {
if body.trim().is_empty() || body.contains("not available") {
"__CSS_EMPTY__".to_owned()
} else {
"__CSS_OK__".to_owned()
}
}
pub fn normalize_html(input: &str) -> String {
let input = normalize_text(input);
let mut output = String::with_capacity(input.len());
let mut cursor = 0;
while let Some(relative_start) = input[cursor..].find('<') {
let start = cursor + relative_start;
output.push_str(&input[cursor..start]);
let Some(end) = find_tag_end(&input, start) else {
output.push_str(&input[start..]);
return finish_html(output);
};
let raw_tag = &input[start..=end];
output.push_str(&normalize_tag(raw_tag));
cursor = end + 1;
}
output.push_str(&input[cursor..]);
finish_html(output)
}
pub fn normalize_text(input: &str) -> String {
let mut value = input.replace("\r\n", "\n").replace('\r', "\n");
value = normalize_host_ports(&value);
value = strip_static_asset_queries(&value);
value = strip_htmx_script_tags(&value);
value = strip_rust_runtime_script_blocks(&value);
value = normalize_intentional_rust_improvements(&value);
value = replace_query_parameter_values(&value, "state", "__OAUTH_STATE__");
value = replace_query_parameter_values(&value, "_csrf", "__CSRF_TOKEN__");
value = replace_script_csrf_values(&value);
value = normalize_cookie_tokens(&value);
value.trim().to_owned()
}
pub fn normalize_header_value(name: &str, value: &str) -> String {
match name.to_ascii_lowercase().as_str() {
"content-type" => normalize_content_type(value),
"location" => normalize_text(value),
"cookie" | "set-cookie" => normalize_cookie_header(value),
_ => normalize_text(value),
}
}
pub fn normalize_content_type(value: &str) -> String {
value
.split(';')
.map(|part| part.trim().to_ascii_lowercase())
.filter(|part| !part.is_empty())
.collect::<Vec<_>>()
.join("; ")
}
pub fn normalize_cookie_header(value: &str) -> String {
normalize_cookie_tokens(value)
.split(';')
.map(str::trim)
.filter(|part| !part.is_empty())
.collect::<Vec<_>>()
.join("; ")
}
fn replace_script_csrf_values(input: &str) -> String {
let pattern = "var csrf=\"";
let mut output = String::with_capacity(input.len());
let mut cursor = 0;
while let Some(relative_start) = input[cursor..].find(pattern) {
let start = cursor + relative_start;
let token_start = start + pattern.len();
output.push_str(&input[cursor..token_start]);
let Some(relative_end) = input[token_start..].find('"') else {
cursor = token_start;
break;
};
let token_end = token_start + relative_end;
let token = &input[token_start..token_end];
if token.len() == 64 && token.chars().all(|ch| ch.is_ascii_hexdigit()) {
output.push_str("__CSRF_TOKEN__");
} else {
output.push_str(token);
}
cursor = token_end;
}
output.push_str(&input[cursor..]);
output
}
fn finish_html(output: String) -> String {
let output = remove_empty_flash_container(&output);
let output = decode_html_entities(&output);
remove_between_tag_whitespace(&output).trim().to_owned()
}
fn remove_empty_flash_container(input: &str) -> String {
input
.replace(
r#"<div class="empty:hidden" id="flash-container"></div>"#,
"",
)
.replace(
r#"<div id="flash-container" class="empty:hidden"></div>"#,
"",
)
}
fn find_tag_end(input: &str, start: usize) -> Option<usize> {
let mut quote: Option<char> = None;
for (offset, ch) in input[start + 1..].char_indices() {
match (quote, ch) {
(Some(active), current) if current == active => quote = None,
(None, '"' | '\'') => quote = Some(ch),
(None, '>') => return Some(start + 1 + offset),
_ => {}
}
}
None
}
fn normalize_tag(raw: &str) -> String {
if raw.starts_with("<!--") {
return String::new();
}
if raw.len() < 3 {
return raw.to_owned();
}
let mut inner = raw[1..raw.len() - 1].trim();
let self_closing = inner.ends_with('/');
if self_closing {
inner = inner[..inner.len() - 1].trim_end();
}
if inner.eq_ignore_ascii_case("!doctype html") {
return String::new();
}
if inner.starts_with('!') || inner.starts_with('?') {
return format!("<{}>", collapse_ascii_whitespace(inner));
}
if let Some(rest) = inner.strip_prefix('/') {
return format!("</{}>", rest.trim().to_ascii_lowercase());
}
let (tag_name, rest) = split_tag_name(inner);
let tag_name = tag_name.to_ascii_lowercase();
let mut attributes = parse_attributes(rest);
normalize_csrf_input_value(&tag_name, &mut attributes);
let tag_attributes = attributes.clone();
attributes.retain(|attribute| {
!is_ignored_rust_runtime_attribute(&tag_name, &tag_attributes, attribute)
});
for attribute in &mut attributes {
if let Some(value) = attribute.value.take() {
attribute.value = Some(normalize_text(&decode_html_entities(&value)));
}
attribute.name = attribute.name.to_ascii_lowercase();
if attribute.name == "class"
&& let Some(ref mut value) = attribute.value
{
let mut tokens: Vec<&str> = value.split_whitespace().collect();
tokens.retain(|token| *token != "font-normal");
tokens.sort();
*value = tokens.join(" ");
}
if attribute.value.as_deref() == Some("") && is_boolean_attribute(&attribute.name) {
attribute.value = None;
}
}
attributes.sort_by(|left, right| {
left.name
.cmp(&right.name)
.then_with(|| left.value.cmp(&right.value))
});
let mut normalized = format!("<{tag_name}");
for attribute in attributes {
normalized.push(' ');
normalized.push_str(&attribute.name);
if let Some(value) = attribute.value {
normalized.push_str("=\"");
normalized.push_str(&escape_attr(&value));
normalized.push('"');
}
}
normalized.push('>');
if self_closing && !is_void_element(&tag_name) {
normalized.push_str("</");
normalized.push_str(&tag_name);
normalized.push('>');
}
normalized
}
fn split_tag_name(inner: &str) -> (&str, &str) {
let split_at = inner
.find(|ch: char| ch.is_ascii_whitespace())
.unwrap_or(inner.len());
let name = &inner[..split_at];
let rest = inner[split_at..].trim_start();
(name, rest)
}
fn parse_attributes(input: &str) -> Vec<Attribute> {
let bytes = input.as_bytes();
let mut attributes = Vec::new();
let mut cursor = 0;
while cursor < bytes.len() {
while cursor < bytes.len() && bytes[cursor].is_ascii_whitespace() {
cursor += 1;
}
if cursor >= bytes.len() {
break;
}
let name_start = cursor;
while cursor < bytes.len() && !bytes[cursor].is_ascii_whitespace() && bytes[cursor] != b'='
{
cursor += 1;
}
if name_start == cursor {
cursor += 1;
continue;
}
let name = input[name_start..cursor].to_owned();
while cursor < bytes.len() && bytes[cursor].is_ascii_whitespace() {
cursor += 1;
}
if cursor >= bytes.len() || bytes[cursor] != b'=' {
attributes.push(Attribute { name, value: None });
continue;
}
cursor += 1;
while cursor < bytes.len() && bytes[cursor].is_ascii_whitespace() {
cursor += 1;
}
if cursor >= bytes.len() {
attributes.push(Attribute {
name,
value: Some(String::new()),
});
break;
}
let value = if bytes[cursor] == b'"' || bytes[cursor] == b'\'' {
let quote = bytes[cursor];
cursor += 1;
let value_start = cursor;
while cursor < bytes.len() && bytes[cursor] != quote {
cursor += 1;
}
let value = input[value_start..cursor].to_owned();
if cursor < bytes.len() {
cursor += 1;
}
value
} else {
let value_start = cursor;
while cursor < bytes.len() && !bytes[cursor].is_ascii_whitespace() {
cursor += 1;
}
input[value_start..cursor].to_owned()
};
attributes.push(Attribute {
name,
value: Some(value),
});
}
attributes
}
fn normalize_csrf_input_value(tag_name: &str, attributes: &mut [Attribute]) {
if tag_name != "input" {
return;
}
let is_csrf = attributes.iter().any(|attribute| {
attribute.name.eq_ignore_ascii_case("name") && attribute.value.as_deref() == Some("_csrf")
});
if !is_csrf {
return;
}
for attribute in attributes {
if attribute.name.eq_ignore_ascii_case("value") {
attribute.value = Some("__CSRF_TOKEN__".to_owned());
}
}
}
fn is_ignored_rust_runtime_attribute(
tag_name: &str,
tag_attributes: &[Attribute],
attribute: &Attribute,
) -> bool {
let name = attribute.name.to_ascii_lowercase();
if name.starts_with("hx-") {
return true;
}
if matches!(
name.as_str(),
"popovertarget" | "popovertargetaction" | "popover"
) {
return true;
}
let is_drawer_panel = tag_name == "aside"
&& tag_attributes
.iter()
.any(|attr| attr.name.eq_ignore_ascii_case("data-drawer-panel"));
if is_drawer_panel && matches!(name.as_str(), "id" | "aria-hidden") {
return true;
}
let is_drawer_body = tag_attributes
.iter()
.any(|attr| attr.name.eq_ignore_ascii_case("data-drawer-body"));
if is_drawer_body && name == "id" {
return true;
}
if name != "id" {
return false;
}
matches!(
attribute.value.as_deref(),
Some(
"users-results"
| "guilds-results"
| "applications-results"
| "reports-results"
| "user-peek"
| "guild-peek"
| "report-peek"
| "guild-tab-content"
| "user-tab-content"
| "jobs-results"
)
)
}
fn is_boolean_attribute(name: &str) -> bool {
if name.starts_with("data-") {
return true;
}
matches!(
name,
"allowfullscreen"
| "async"
| "autofocus"
| "autoplay"
| "checked"
| "controls"
| "defer"
| "disabled"
| "hidden"
| "loop"
| "multiple"
| "muted"
| "open"
| "readonly"
| "required"
| "selected"
)
}
fn is_void_element(name: &str) -> bool {
matches!(
name,
"area"
| "base"
| "br"
| "col"
| "embed"
| "hr"
| "img"
| "input"
| "link"
| "meta"
| "param"
| "source"
| "track"
| "wbr"
)
}
fn escape_attr(value: &str) -> String {
value
.replace('&', "&amp;")
.replace('"', "&quot;")
.replace('<', "&lt;")
.replace('>', "&gt;")
}
fn decode_html_entities(value: &str) -> String {
value
.replace("&amp;", "&")
.replace("&#39;", "'")
.replace("&#x27;", "'")
.replace("&apos;", "'")
.replace("&quot;", "\"")
.replace("&larr;", "\u{2190}")
.replace("&rarr;", "\u{2192}")
.replace("&mdash;", "\u{2014}")
.replace("&ndash;", "\u{2013}")
.replace("&lt;", "<")
.replace("&gt;", ">")
}
fn collapse_ascii_whitespace(value: &str) -> String {
value.split_whitespace().collect::<Vec<_>>().join(" ")
}
fn remove_between_tag_whitespace(input: &str) -> String {
let chars = input.chars().collect::<Vec<_>>();
let mut output = String::with_capacity(input.len());
let mut cursor = 0;
while cursor < chars.len() {
output.push(chars[cursor]);
if chars[cursor] == '>' {
cursor += 1;
let whitespace_start = cursor;
while cursor < chars.len() && chars[cursor].is_whitespace() {
cursor += 1;
}
if cursor < chars.len() && chars[cursor] == '<' {
continue;
}
if cursor > whitespace_start {
output.push(' ');
}
continue;
}
cursor += 1;
}
output
}
fn normalize_host_ports(input: &str) -> String {
let value = replace_host_port(input, "127.0.0.1");
replace_host_port(&value, "localhost")
}
fn replace_host_port(input: &str, host: &str) -> String {
let needle = format!("{host}:");
let mut output = String::with_capacity(input.len());
let mut cursor = 0;
while let Some(relative) = input[cursor..].find(&needle) {
let start = cursor + relative;
output.push_str(&input[cursor..start]);
output.push_str(host);
output.push_str(":__PORT__");
cursor = start + needle.len();
while cursor < input.len() && input.as_bytes()[cursor].is_ascii_digit() {
cursor += 1;
}
}
output.push_str(&input[cursor..]);
output
}
fn strip_static_asset_queries(input: &str) -> String {
let mut value = strip_query_after_path(input, "/static/app.css");
value = strip_query_after_path(&value, "/static/htmx.min.js");
value
}
fn strip_htmx_script_tags(input: &str) -> String {
let mut output = String::with_capacity(input.len());
let mut cursor = 0;
while let Some(relative_start) = input[cursor..].find("<script") {
let start = cursor + relative_start;
let Some(tag_end) = find_tag_end(input, start) else {
break;
};
let tag = &input[start..=tag_end];
if tag.to_ascii_lowercase().contains("/static/htmx.min.js") {
output.push_str(&input[cursor..start]);
let after_tag = tag_end + 1;
if input[after_tag..].starts_with("</script>") {
cursor = after_tag + "</script>".len();
} else {
cursor = after_tag;
}
continue;
}
output.push_str(&input[cursor..=tag_end]);
cursor = tag_end + 1;
}
output.push_str(&input[cursor..]);
output
}
fn strip_rust_runtime_script_blocks(input: &str) -> String {
let mut value =
strip_script_blocks_containing(input, "document.body.addEventListener('showFlash'");
value = strip_script_blocks_containing(&value, "__fluxerAdminHtmxScrollPreserver");
value = strip_script_blocks_containing(&value, "window.__adminCopyToClipboard");
value = strip_script_blocks_containing(&value, "window.__fluxerDrawerInit");
value
}
fn strip_script_blocks_containing(input: &str, needle: &str) -> String {
let mut output = String::with_capacity(input.len());
let mut cursor = 0;
while let Some(relative_start) = input[cursor..].find("<script") {
let start = cursor + relative_start;
let Some(tag_end) = find_tag_end(input, start) else {
break;
};
let content_start = tag_end + 1;
let Some(relative_end) = input[content_start..].find("</script>") else {
break;
};
let end = content_start + relative_end + "</script>".len();
let block = &input[start..end];
if block.contains(needle) {
output.push_str(&input[cursor..start]);
cursor = end;
continue;
}
output.push_str(&input[cursor..end]);
cursor = end;
}
output.push_str(&input[cursor..]);
output
}
fn normalize_intentional_rust_improvements(input: &str) -> String {
input.replace(
"flex flex-col gap-8 items-center",
"flex flex-col gap-8 items-stretch",
)
}
fn strip_query_after_path(input: &str, path: &str) -> String {
let mut output = String::with_capacity(input.len());
let mut cursor = 0;
while let Some(relative) = input[cursor..].find(path) {
let start = cursor + relative;
output.push_str(&input[cursor..start]);
output.push_str(path);
cursor = start + path.len();
if input[cursor..].starts_with('?') {
cursor += 1;
while cursor < input.len() && !is_url_delimiter(input.as_bytes()[cursor]) {
cursor += 1;
}
}
}
output.push_str(&input[cursor..]);
output
}
fn replace_query_parameter_values(input: &str, name: &str, replacement: &str) -> String {
let mut output = String::with_capacity(input.len());
let mut cursor = 0;
while cursor < input.len() {
let remaining = &input[cursor..];
let query_needle = format!("?{name}=");
let amp_needle = format!("&{name}=");
let query_pos = remaining.find(&query_needle);
let amp_pos = remaining.find(&amp_needle);
let Some((relative, needle_len)) =
closest_match(query_pos, query_needle.len(), amp_pos, amp_needle.len())
else {
output.push_str(remaining);
break;
};
let start = cursor + relative;
output.push_str(&input[cursor..start + needle_len]);
output.push_str(replacement);
cursor = start + needle_len;
while cursor < input.len() && !is_url_delimiter(input.as_bytes()[cursor]) {
cursor += 1;
}
}
output
}
fn closest_match(
left: Option<usize>,
left_len: usize,
right: Option<usize>,
right_len: usize,
) -> Option<(usize, usize)> {
match (left, right) {
(Some(left), Some(right)) if left <= right => Some((left, left_len)),
(Some(_left), Some(right)) => Some((right, right_len)),
(Some(left), None) => Some((left, left_len)),
(None, Some(right)) => Some((right, right_len)),
(None, None) => None,
}
}
fn normalize_cookie_tokens(input: &str) -> String {
let mut value = replace_prefixed_value(input, "admin_session=", "__SESSION__");
value = replace_prefixed_value(&value, "session=", "__SESSION__");
value = replace_prefixed_value(&value, "csrf_token=", "__CSRF_COOKIE__");
value = replace_prefixed_value(&value, "oauth_state=", "__OAUTH_STATE__");
replace_prefixed_value(&value, "flash=", "__FLASH__")
}
fn replace_prefixed_value(input: &str, prefix: &str, replacement: &str) -> String {
let mut output = String::with_capacity(input.len());
let mut cursor = 0;
while let Some(relative) = input[cursor..].find(prefix) {
let start = cursor + relative;
output.push_str(&input[cursor..start + prefix.len()]);
output.push_str(replacement);
cursor = start + prefix.len();
while cursor < input.len() && !is_cookie_delimiter(input.as_bytes()[cursor]) {
cursor += 1;
}
}
output.push_str(&input[cursor..]);
output
}
fn is_url_delimiter(byte: u8) -> bool {
matches!(
byte,
b'&' | b'"' | b'\'' | b'<' | b'>' | b')' | b' ' | b'\n' | b'\t'
)
}
fn is_cookie_delimiter(byte: u8) -> bool {
matches!(byte, b';' | b'"' | b'\'' | b' ' | b'\n' | b'\t')
}
-10
View File
@@ -1,10 +0,0 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
pub mod api_fixtures;
pub mod capture;
pub mod html_normalizer;
pub mod rust_server;
pub const TEST_ADMIN_SECRET: &str = "test-admin-secret";
pub const TEST_ADMIN_USER_ID: &str = "1130650140672000000";
pub const TEST_ACCESS_TOKEN: &str = "parity-access-token";
-88
View File
@@ -1,88 +0,0 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
use super::TEST_ADMIN_SECRET;
use fluxer_admin::{
build_router,
config::{AdminConfig, ProxyConfig, RuntimeEnv},
};
use std::time::{Duration, Instant};
use tokio::{net::TcpListener, task::JoinHandle, time::sleep};
pub struct RunningRustAdmin {
base_url: String,
handle: JoinHandle<()>,
}
impl RunningRustAdmin {
pub fn base_url(&self) -> &str {
&self.base_url
}
}
impl Drop for RunningRustAdmin {
fn drop(&mut self) {
self.handle.abort();
}
}
pub async fn start(api_endpoint: &str) -> Result<RunningRustAdmin, String> {
let listener = TcpListener::bind("127.0.0.1:0")
.await
.map_err(|error| format!("failed to bind Rust admin server: {error}"))?;
let port = listener
.local_addr()
.map_err(|error| format!("failed to read Rust admin address: {error}"))?
.port();
let base_url = format!("http://127.0.0.1:{port}");
let config = admin_config(port, api_endpoint, &base_url);
let router = build_router(config);
let handle = tokio::spawn(async move {
let _ = axum::serve(listener, router).await;
});
wait_for_health(&base_url).await?;
Ok(RunningRustAdmin { base_url, handle })
}
fn admin_config(port: u16, api_endpoint: &str, admin_endpoint: &str) -> AdminConfig {
AdminConfig {
env: RuntimeEnv::Test,
host: "127.0.0.1".to_owned(),
port,
secret_key_base: TEST_ADMIN_SECRET.to_owned(),
base_path: String::new(),
api_endpoint: api_endpoint.to_owned(),
media_endpoint: format!("{api_endpoint}/media"),
static_cdn_endpoint: "https://static.example.test".to_owned(),
reports_bucket_origin: "https://reports.example.test".to_owned(),
admin_endpoint: admin_endpoint.to_owned(),
web_app_endpoint: "http://127.0.0.1:8088".to_owned(),
oauth_client_id: "1234567890123456789".to_owned(),
oauth_client_secret: "test-admin-oauth-secret".to_owned(),
oauth_redirect_uri: format!("{admin_endpoint}/oauth2_callback"),
build_version: "parity".to_owned(),
self_hosted: false,
proxy: ProxyConfig {
trust_client_ip_header: false,
client_ip_header_name: "x-forwarded-for".to_owned(),
},
}
}
async fn wait_for_health(base_url: &str) -> Result<(), String> {
let client = reqwest::Client::builder()
.redirect(reqwest::redirect::Policy::none())
.build()
.map_err(|error| format!("failed to build health client: {error}"))?;
let deadline = Instant::now() + Duration::from_secs(30);
let url = format!("{}/_health", base_url.trim_end_matches('/'));
let mut last_error = String::new();
while Instant::now() < deadline {
match client.get(&url).send().await {
Ok(response) if response.status().is_success() => return Ok(()),
Ok(response) => last_error = format!("health returned {}", response.status()),
Err(error) => last_error = error.to_string(),
}
sleep(Duration::from_millis(200)).await;
}
Err(format!("timed out waiting for {url}: {last_error}"))
}
+1 -82
View File
@@ -11,7 +11,7 @@ use axum::{
};
use fluxer_admin::{
build_router,
config::{AdminConfig, ProxyConfig, RuntimeEnv},
config::{AdminConfig, ProxyConfig},
session,
};
use serde_json::{Value, json};
@@ -31,30 +31,6 @@ struct MockApi {
requests: Arc<Mutex<Vec<String>>>,
}
#[tokio::test]
async fn creating_a_reset_link_shows_the_url_once_with_a_copy_button() {
let app = setup(true, "username", vec!["*"]).await;
let csrf_token = csrf_token(&app).await;
let (status, body) = post_form(
&app,
&format!("/users/{TARGET_ID}?action=create_password_reset_link&tab=account"),
&format!("_csrf={csrf_token}"),
)
.await;
assert_eq!(status, StatusCode::OK);
assert!(app.saw(&format!(
"POST /admin/users/{TARGET_ID}/password-reset-link"
)));
assert!(body.contains("Copy this link now. It is shown only once."));
assert!(body.contains(&format!(r#"value="{RESET_URL}""#)));
assert!(body.contains(&format!(r#"data-copy-value="{RESET_URL}""#)));
assert!(body.contains("Copy Link"));
let page = get(&app, &format!("/users/{TARGET_ID}?tab=account")).await;
assert!(page.contains("Create Password Reset Link"));
assert!(!page.contains(RESET_URL));
}
#[tokio::test]
async fn an_htmx_reset_link_request_gets_only_the_result_fragment() {
let app = setup(true, "username", vec!["*"]).await;
@@ -133,16 +109,6 @@ async fn the_revoke_recovery_kit_action_needs_its_acl_and_a_username_instance()
assert!(!page.contains("Revoke Recovery Kit"));
}
#[tokio::test]
async fn username_instances_hide_email_actions_on_the_account_tab() {
let app = setup(true, "username", vec!["*"]).await;
let page = get(&app, &format!("/users/{TARGET_ID}?tab=account")).await;
assert!(page.contains("Create Password Reset Link"));
assert!(!page.contains("Send Password Reset"));
assert!(!page.contains("Change Email"));
assert!(!page.contains("Verify Email"));
}
#[tokio::test]
async fn the_reset_link_action_needs_its_acl() {
let app = setup(true, "username", vec!["admin:authenticate", "user:lookup"]).await;
@@ -152,52 +118,6 @@ async fn the_reset_link_action_needs_its_acl() {
assert!(!page.contains("Send Password Reset"));
}
#[tokio::test]
async fn email_instances_keep_the_email_actions() {
let app = setup(true, "email", vec!["*"]).await;
let page = get(&app, &format!("/users/{TARGET_ID}?tab=account")).await;
assert!(page.contains("Send Password Reset"));
assert!(page.contains("Change Email"));
assert!(page.contains("Verify Email"));
assert!(!page.contains("Create Password Reset Link"));
}
#[tokio::test]
async fn the_email_ban_notice_stays_after_a_ban_action_on_a_username_instance() {
let notice = "Accounts have no email address, so email bans have no effect.";
let username = setup(true, "username", vec!["*"]).await;
let username_csrf = csrf_token(&username).await;
let (status, body) = post_form(
&username,
"/email-bans?action=ban",
&format!("_csrf={username_csrf}&email="),
)
.await;
assert_eq!(status, StatusCode::OK);
assert!(body.contains("Value is required"));
assert!(body.contains(notice));
let email = setup(true, "email", vec!["*"]).await;
let email_csrf = csrf_token(&email).await;
let (_, body) = post_form(
&email,
"/email-bans?action=ban",
&format!("_csrf={email_csrf}&email="),
)
.await;
assert!(body.contains("Value is required"));
assert!(!body.contains(notice));
}
#[tokio::test]
async fn hosted_admin_never_asks_discovery_for_the_sign_in_method() {
let app = setup(false, "username", vec!["*"]).await;
let page = get(&app, &format!("/users/{TARGET_ID}?tab=account")).await;
assert!(page.contains("Send Password Reset"));
assert!(!page.contains("Create Password Reset Link"));
assert!(!app.saw("GET /.well-known/fluxer"));
}
struct TestApp {
router: Router,
session_cookie: String,
@@ -423,7 +343,6 @@ fn user(id: &str, username: &str, acls: &[&str]) -> Value {
fn test_config(api_endpoint: String, self_hosted: bool) -> AdminConfig {
AdminConfig {
env: RuntimeEnv::Test,
host: "127.0.0.1".to_owned(),
port: 0,
secret_key_base: SECRET_KEY.to_owned(),
-242
View File
@@ -1,242 +0,0 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
#![recursion_limit = "256"]
use axum::{
Json, Router,
body::{Body, to_bytes},
extract::State,
http::{Method, Request, StatusCode, Uri, header},
response::{IntoResponse, Response},
};
use fluxer_admin::{
build_router,
config::{AdminConfig, ProxyConfig, RuntimeEnv},
session,
};
use serde_json::{Value, json};
use tokio::net::TcpListener;
use tower::ServiceExt;
const SECRET_KEY: &str = "username-tags-test-secret";
const ADMIN_ID: &str = "1500000000000000000";
const TARGET_ID: &str = "1500000000000000042";
const DISCRIMINATOR_INPUT: &str = r#"name="discriminator""#;
#[derive(Clone)]
struct MockApi {
account_identity: &'static str,
unique_usernames: bool,
target: Value,
}
#[tokio::test]
async fn username_instances_show_humans_without_a_tag() {
let page = account_page(true, "username", user(TARGET_ID, "member", 0, false)).await;
assert!(page.contains(r#"<p class="break-words text-sm text-neutral-500">member</p>"#));
assert!(page.contains(r#"<div class="truncate text-neutral-500 text-xs">lilith</div>"#));
assert!(!page.contains("member#0000"));
assert!(!page.contains("lilith#0000"));
assert!(!page.contains(DISCRIMINATOR_INPUT));
}
#[tokio::test]
async fn email_instances_with_random_tags_show_tags_and_allow_tag_changes() {
let page =
account_page_with(true, "email", false, user(TARGET_ID, "member", 1234, false)).await;
assert!(page.contains("member#1234"));
assert!(page.contains(DISCRIMINATOR_INPUT));
}
#[tokio::test]
async fn email_instances_with_no_tags_show_humans_without_a_tag() {
let page = account_page_with(true, "email", true, user(TARGET_ID, "member", 0, false)).await;
assert!(page.contains(r#"<p class="break-words text-sm text-neutral-500">member</p>"#));
assert!(!page.contains("member#0000"));
assert!(!page.contains("lilith#0000"));
assert!(!page.contains(DISCRIMINATOR_INPUT));
assert!(page.contains("Send Password Reset"));
}
#[tokio::test]
async fn username_instances_never_show_tags_even_if_told_random() {
let page =
account_page_with(true, "username", false, user(TARGET_ID, "member", 0, false)).await;
assert!(!page.contains("member#0000"));
assert!(!page.contains(DISCRIMINATOR_INPUT));
}
#[tokio::test]
async fn username_instances_keep_bot_tags() {
let page = account_page(true, "username", user(TARGET_ID, "helper", 4363, true)).await;
assert!(page.contains("helper#4363"));
assert!(page.contains(DISCRIMINATOR_INPUT));
}
#[tokio::test]
async fn email_instances_keep_the_zero_tag() {
let page = account_page(true, "email", user(TARGET_ID, "member", 0, false)).await;
assert!(page.contains("member#0000"));
assert!(page.contains("lilith#0000"));
assert!(page.contains(DISCRIMINATOR_INPUT));
}
#[tokio::test]
async fn hosted_admin_keeps_the_zero_tag() {
let page = account_page(false, "username", user(TARGET_ID, "member", 0, false)).await;
assert!(page.contains("member#0000"));
assert!(page.contains(DISCRIMINATOR_INPUT));
}
async fn account_page(self_hosted: bool, account_identity: &'static str, target: Value) -> String {
account_page_with(
self_hosted,
account_identity,
account_identity == "username",
target,
)
.await
}
async fn account_page_with(
self_hosted: bool,
account_identity: &'static str,
unique_usernames: bool,
target: Value,
) -> String {
let api_endpoint = spawn_mock_api(MockApi {
account_identity,
unique_usernames,
target,
})
.await;
let router = build_router(test_config(api_endpoint, self_hosted));
let session_value = session::create_session(ADMIN_ID, "test-token", SECRET_KEY);
let response = router
.oneshot(
Request::builder()
.method(Method::GET)
.uri(format!("/users/{TARGET_ID}?tab=account"))
.header(
header::COOKIE,
format!("{}={session_value}", session::SESSION_COOKIE_NAME),
)
.body(Body::empty())
.unwrap(),
)
.await
.unwrap();
assert_eq!(response.status(), StatusCode::OK);
let bytes = to_bytes(response.into_body(), usize::MAX).await.unwrap();
String::from_utf8(bytes.to_vec()).unwrap()
}
async fn spawn_mock_api(mock: MockApi) -> String {
let listener = TcpListener::bind(("127.0.0.1", 0)).await.unwrap();
let addr = listener.local_addr().unwrap();
tokio::spawn(async move {
axum::serve(listener, Router::new().fallback(mock_api).with_state(mock))
.await
.unwrap();
});
format!("http://{addr}")
}
async fn mock_api(State(mock): State<MockApi>, method: Method, uri: Uri) -> Response {
let target_user = format!("/admin/users/{TARGET_ID}");
let target_sessions = format!("{target_user}/sessions");
let target_credentials = format!("{target_user}/webauthn-credentials");
match (method, uri.path()) {
(Method::GET, "/admin/users/@me") => Json(json!({
"user": user(ADMIN_ID, "lilith", 0, false)
}))
.into_response(),
(Method::GET, "/.well-known/fluxer") => Json(json!({
"features": {
"premium_enabled": false,
"account_identity": mock.account_identity,
"tag_style": if mock.unique_usernames { "none" } else { "random" }
}
}))
.into_response(),
(Method::GET, p) if p == target_user => {
Json(json!({ "users": [mock.target] })).into_response()
}
(Method::GET, p) if p == target_sessions => Json(json!({ "sessions": [] })).into_response(),
(Method::GET, p) if p == target_credentials => Json(json!([])).into_response(),
_ => (
StatusCode::NOT_FOUND,
Json(json!({ "message": "not found" })),
)
.into_response(),
}
}
fn user(id: &str, username: &str, discriminator: u16, bot: bool) -> Value {
json!({
"id": id,
"username": username,
"discriminator": discriminator,
"avatar": null,
"banner": null,
"email": null,
"email_verified": false,
"email_bounced": false,
"global_name": null,
"bio": null,
"pronouns": null,
"accent_color": null,
"date_of_birth": null,
"locale": "en-GB",
"acls": ["*"],
"traits": [],
"flags": "0",
"premium_flags": 0,
"bot": bot,
"system": false,
"premium_type": null,
"premium_since": null,
"premium_until": null,
"premium_grace_ends_at": null,
"premium_lifetime_sequence": null,
"has_totp": false,
"authenticator_types": [],
"temp_banned_until": null,
"pending_deletion_at": null,
"pending_bulk_message_deletion_at": null,
"deletion_reason_code": null,
"deletion_public_reason": null,
"deletion_audit_log_reason": null,
"deletion_scheduled_by": null,
"deletion_scheduled_at": null,
"last_active_at": null,
"last_active_ip": null,
"last_active_ip_reverse": null,
"last_active_location": null
})
}
fn test_config(api_endpoint: String, self_hosted: bool) -> AdminConfig {
AdminConfig {
env: RuntimeEnv::Test,
host: "127.0.0.1".to_owned(),
port: 0,
secret_key_base: SECRET_KEY.to_owned(),
base_path: String::new(),
api_endpoint,
media_endpoint: "https://media.example.test".to_owned(),
static_cdn_endpoint: "https://static.example.test".to_owned(),
reports_bucket_origin: String::new(),
admin_endpoint: "https://admin.example.test".to_owned(),
web_app_endpoint: "https://app.example.test".to_owned(),
oauth_client_id: "admin-client".to_owned(),
oauth_client_secret: "admin-secret".to_owned(),
oauth_redirect_uri: "https://admin.example.test/callback".to_owned(),
build_version: "test".to_owned(),
self_hosted,
proxy: ProxyConfig {
trust_client_ip_header: false,
client_ip_header_name: "x-forwarded-for".to_owned(),
},
}
}
@@ -11,7 +11,7 @@ use axum::{
};
use fluxer_admin::{
build_router,
config::{AdminConfig, ProxyConfig, RuntimeEnv},
config::{AdminConfig, ProxyConfig},
session,
};
use serde_json::{Value, json};
@@ -313,7 +313,6 @@ fn admin_user() -> Value {
fn test_config(api_endpoint: String) -> AdminConfig {
AdminConfig {
env: RuntimeEnv::Test,
host: "127.0.0.1".to_owned(),
port: 0,
secret_key_base: SECRET_KEY.to_owned(),