mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-18 21:45:31 +09:00
feat(enforcement): record decisions and send notices (#3492)
This commit is contained in:
1 parent
da1842ecab
commit
f5c30fdf8f
383 files changed
+29692
-4139
No files matched your search
@@ -74,9 +74,11 @@ impl AdminApiClient {
|
||||
pub async fn bulk_delete_user_messages(
|
||||
&self,
|
||||
user_ids: &[String],
|
||||
basis: generated_types::BulkModerationBasis,
|
||||
audit_log_reason: Option<&str>,
|
||||
) -> ApiResult<BulkJobResponse> {
|
||||
let body = generated_types::AdminBulkJobCreateRequest::DeleteUserMessages {
|
||||
basis,
|
||||
user_ids: snowflakes(user_ids),
|
||||
};
|
||||
self.send(
|
||||
@@ -91,26 +93,18 @@ impl AdminApiClient {
|
||||
pub async fn bulk_schedule_user_deletion(
|
||||
&self,
|
||||
user_ids: &[String],
|
||||
reason_code: u32,
|
||||
days_until_deletion: u32,
|
||||
public_reason: Option<&str>,
|
||||
notify_user: bool,
|
||||
basis: generated_types::BulkModerationBasis,
|
||||
audit_log_reason: Option<&str>,
|
||||
) -> ApiResult<BulkJobResponse> {
|
||||
let body = generated_types::AdminBulkJobCreateRequest::ScheduleUserDeletion {
|
||||
basis,
|
||||
days_until_deletion: crate::api::generated::nonzero_u32(
|
||||
days_until_deletion,
|
||||
"days_until_deletion",
|
||||
)
|
||||
.map_err(ApiError::Parse)?
|
||||
.into(),
|
||||
notify_user,
|
||||
public_reason: public_reason.map(std::borrow::ToOwned::to_owned),
|
||||
reason_code: crate::api::generated::deletion_reason_code(
|
||||
i32::try_from(reason_code).map_err(|e| ApiError::Parse(e.to_string()))?,
|
||||
"reason_code",
|
||||
)
|
||||
.map_err(ApiError::Parse)?,
|
||||
user_ids: snowflakes(user_ids),
|
||||
};
|
||||
self.send(
|
||||
|
||||
@@ -0,0 +1,264 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
use reqwest::Method;
|
||||
use serde::Deserialize;
|
||||
|
||||
use crate::api::generated::types as generated_types;
|
||||
|
||||
use super::client::{AdminApiClient, ApiError, ApiResult};
|
||||
use super::types::{AdminUser, UserMutationResponse};
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct RestrictionResponse {
|
||||
pub user: AdminUser,
|
||||
pub skipped_ips: Vec<generated_types::SkippedNetworkBlock>,
|
||||
}
|
||||
|
||||
pub struct DecisionSubmission {
|
||||
pub decision: generated_types::EnforcementDecisionInput,
|
||||
pub internal_note: String,
|
||||
}
|
||||
|
||||
pub struct ClosureRequest {
|
||||
pub basis: generated_types::ModerationBasis,
|
||||
pub days_until_deletion: u32,
|
||||
pub remove_evidence: bool,
|
||||
pub block_networks: Option<generated_types::NetworkBlockRequest>,
|
||||
pub replace_pending_deletion_at: Option<chrono::DateTime<chrono::Utc>>,
|
||||
pub internal_note: Option<String>,
|
||||
}
|
||||
|
||||
impl AdminApiClient {
|
||||
pub async fn suspend_user(
|
||||
&self,
|
||||
user_id: &str,
|
||||
duration_hours: u32,
|
||||
remove_evidence: bool,
|
||||
block_networks: Option<generated_types::NetworkBlockRequest>,
|
||||
submission: &DecisionSubmission,
|
||||
) -> ApiResult<RestrictionResponse> {
|
||||
let body = generated_types::AdminUserBanRequest {
|
||||
block_networks,
|
||||
decision: submission.decision.clone(),
|
||||
duration_hours: i64::from(duration_hours),
|
||||
remove_evidence: remove_evidence.then_some(true),
|
||||
};
|
||||
self.send(
|
||||
Method::PUT,
|
||||
&format!("/admin/users/{}/ban", urlencoding::encode(user_id)),
|
||||
Some(&body),
|
||||
Some(&submission.internal_note),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn list_network_block_candidates(
|
||||
&self,
|
||||
user_id: &str,
|
||||
) -> ApiResult<generated_types::NetworkBlockCandidatesResponse> {
|
||||
self.get(
|
||||
&format!(
|
||||
"/admin/users/{}/network-block-candidates",
|
||||
urlencoding::encode(user_id)
|
||||
),
|
||||
None,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn warn_user(
|
||||
&self,
|
||||
user_id: &str,
|
||||
submission: &DecisionSubmission,
|
||||
) -> ApiResult<generated_types::AdminUserWarningResponse> {
|
||||
let body = generated_types::AdminUserWarningRequest {
|
||||
decision: submission.decision.clone(),
|
||||
};
|
||||
self.send(
|
||||
Method::POST,
|
||||
&format!("/admin/users/{}/warnings", urlencoding::encode(user_id)),
|
||||
Some(&body),
|
||||
Some(&submission.internal_note),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn close_account(
|
||||
&self,
|
||||
user_id: &str,
|
||||
request: &ClosureRequest,
|
||||
) -> ApiResult<RestrictionResponse> {
|
||||
let body = generated_types::AdminUserDeletionScheduleRequest {
|
||||
basis: request.basis.clone(),
|
||||
block_networks: request.block_networks.clone(),
|
||||
days_until_deletion: crate::api::generated::nonzero_u32(
|
||||
request.days_until_deletion,
|
||||
"days_until_deletion",
|
||||
)
|
||||
.map_err(ApiError::Parse)?
|
||||
.into(),
|
||||
remove_evidence: request.remove_evidence.then_some(true),
|
||||
replace_pending_deletion_at: request.replace_pending_deletion_at,
|
||||
};
|
||||
self.send(
|
||||
Method::PUT,
|
||||
&format!("/admin/users/{}/deletion", urlencoding::encode(user_id)),
|
||||
Some(&body),
|
||||
request.internal_note.as_deref(),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn limit_features(
|
||||
&self,
|
||||
user_id: &str,
|
||||
limit: generated_types::AdminUserFeatureLimitRequestLimit,
|
||||
ends_at: Option<chrono::DateTime<chrono::Utc>>,
|
||||
submission: &DecisionSubmission,
|
||||
) -> ApiResult<AdminUser> {
|
||||
let body = generated_types::AdminUserFeatureLimitRequest {
|
||||
decision: submission.decision.clone(),
|
||||
ends_at,
|
||||
limit,
|
||||
};
|
||||
let response: UserMutationResponse = self
|
||||
.send(
|
||||
Method::PUT,
|
||||
&format!(
|
||||
"/admin/users/{}/feature-limits",
|
||||
urlencoding::encode(user_id)
|
||||
),
|
||||
Some(&body),
|
||||
Some(&submission.internal_note),
|
||||
)
|
||||
.await?;
|
||||
Ok(response.user)
|
||||
}
|
||||
|
||||
pub async fn list_review_queue(
|
||||
&self,
|
||||
) -> ApiResult<generated_types::EnforcementDecisionListResponse> {
|
||||
self.get(
|
||||
"/admin/enforcement-decisions",
|
||||
Some(&[("review_queue", "true")]),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn list_user_decisions(
|
||||
&self,
|
||||
user_id: &str,
|
||||
) -> ApiResult<generated_types::EnforcementDecisionListResponse> {
|
||||
self.get(
|
||||
"/admin/enforcement-decisions",
|
||||
Some(&[("subject_type", "user"), ("subject_id", user_id)]),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn get_decision(
|
||||
&self,
|
||||
decision_id: &str,
|
||||
) -> ApiResult<generated_types::EnforcementDecisionResponse> {
|
||||
self.get(
|
||||
&format!(
|
||||
"/admin/enforcement-decisions/{}",
|
||||
urlencoding::encode(decision_id)
|
||||
),
|
||||
None,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn end_decision(
|
||||
&self,
|
||||
decision_id: &str,
|
||||
outcome: generated_types::EnforcementDecisionEndRequestOutcome,
|
||||
statement: Option<String>,
|
||||
internal_note: &str,
|
||||
) -> ApiResult<generated_types::EnforcementDecisionResponse> {
|
||||
let body = generated_types::EnforcementDecisionEndRequest { outcome, statement };
|
||||
self.send(
|
||||
Method::POST,
|
||||
&format!(
|
||||
"/admin/enforcement-decisions/{}/end",
|
||||
urlencoding::encode(decision_id)
|
||||
),
|
||||
Some(&body),
|
||||
Some(internal_note),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn annotate_decision(
|
||||
&self,
|
||||
decision_id: &str,
|
||||
body: String,
|
||||
) -> ApiResult<generated_types::EnforcementDecisionNoteResponse> {
|
||||
let body = generated_types::EnforcementDecisionNoteRequest { body };
|
||||
self.send(
|
||||
Method::POST,
|
||||
&format!(
|
||||
"/admin/enforcement-decisions/{}/notes",
|
||||
urlencoding::encode(decision_id)
|
||||
),
|
||||
Some(&body),
|
||||
None,
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
pub fn api_error_message(error: &ApiError) -> Option<String> {
|
||||
let ApiError::Http { status, message } = error else {
|
||||
return None;
|
||||
};
|
||||
if !matches!(status, 400 | 403 | 404 | 409) {
|
||||
return None;
|
||||
}
|
||||
let body: serde_json::Value = serde_json::from_str(message).ok()?;
|
||||
let detail = body["errors"]
|
||||
.as_array()
|
||||
.and_then(|errors| errors.first())
|
||||
.and_then(|error| error["message"].as_str());
|
||||
detail
|
||||
.or_else(|| body["message"].as_str())
|
||||
.map(str::to_owned)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::api_error_message;
|
||||
use crate::api::client::ApiError;
|
||||
|
||||
fn http(status: u16, body: &str) -> ApiError {
|
||||
ApiError::Http {
|
||||
status,
|
||||
message: body.to_owned(),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn api_error_message_prefers_the_first_field_error() {
|
||||
let body = r#"{"code":"INVALID_FORM_BODY","message":"Invalid form body","errors":[{"path":"decision.statement","code":"ENFORCEMENT_STATEMENT_NOT_ALLOWED","message":"A note to the user is not allowed for this reason"}]}"#;
|
||||
assert_eq!(
|
||||
api_error_message(&http(400, body)).as_deref(),
|
||||
Some("A note to the user is not allowed for this reason")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn api_error_message_reads_conflicts_and_ignores_server_errors() {
|
||||
let body = r#"{"code":"ENFORCEMENT_DECISION_REPLACED","message":"A later decision replaced this one"}"#;
|
||||
assert_eq!(
|
||||
api_error_message(&http(409, body)).as_deref(),
|
||||
Some("A later decision replaced this one")
|
||||
);
|
||||
assert_eq!(api_error_message(&http(500, body)), None);
|
||||
assert_eq!(api_error_message(&http(409, "not json")), None);
|
||||
assert_eq!(
|
||||
api_error_message(&ApiError::Network("down".to_owned())),
|
||||
None
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -56,14 +56,6 @@ pub(crate) fn nonzero_u32(value: u32, field: &str) -> Result<std::num::NonZeroU3
|
||||
std::num::NonZeroU32::new(value).ok_or_else(|| format!("{field} must be greater than zero"))
|
||||
}
|
||||
|
||||
pub(crate) fn deletion_reason_code(
|
||||
value: i32,
|
||||
field: &str,
|
||||
) -> Result<types::DeletionReasonCode, String> {
|
||||
types::DeletionReasonCode::try_from(value)
|
||||
.map_err(|_| format!("{field} is not a deletion reason code: {value}"))
|
||||
}
|
||||
|
||||
pub(crate) fn user_flags(values: &[String]) -> ApiResult<Vec<generated_types::UserFlags>> {
|
||||
values
|
||||
.iter()
|
||||
|
||||
@@ -69,13 +69,20 @@ impl AdminApiClient {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn delete_guild(&self, guild_id: &str) -> ApiResult<SuccessResponse> {
|
||||
let response = self
|
||||
.generated()
|
||||
.delete_admin_guild(&snowflake(guild_id))
|
||||
.await
|
||||
.map_err(|e| self.generated_error(e))?;
|
||||
self.generated_value(response.into_inner())
|
||||
pub async fn delete_guild(
|
||||
&self,
|
||||
guild_id: &str,
|
||||
basis: generated_types::CommunityRemovalBasis,
|
||||
internal_note: Option<&str>,
|
||||
) -> ApiResult<SuccessResponse> {
|
||||
let body = generated_types::AdminGuildDeleteRequest { basis };
|
||||
self.send(
|
||||
Method::DELETE,
|
||||
&format!("/admin/guilds/{}", urlencoding::encode(guild_id)),
|
||||
Some(&body),
|
||||
internal_note,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn transfer_guild_ownership(
|
||||
|
||||
@@ -4,10 +4,12 @@ use reqwest::Method;
|
||||
|
||||
use crate::api::generated::{snowflake, types as generated_types};
|
||||
|
||||
use crate::templates::components::enforcement_fields::BasisSubmission;
|
||||
|
||||
use super::client::{AdminApiClient, ApiError, ApiResult};
|
||||
use super::types::{
|
||||
BrowseChannelResponse, DeleteAllUserMessagesResponse, LookupMessageResponse,
|
||||
MessageShredResponse, MessageShredStatusResponse, SearchChannelMessagesResponse,
|
||||
BrowseChannelResponse, LookupMessageResponse, MessageShredStatusResponse,
|
||||
SearchChannelMessagesResponse,
|
||||
};
|
||||
|
||||
impl AdminApiClient {
|
||||
@@ -15,8 +17,10 @@ impl AdminApiClient {
|
||||
&self,
|
||||
channel_id: &str,
|
||||
message_id: &str,
|
||||
basis: generated_types::MessageDeletionBasis,
|
||||
audit_log_reason: Option<&str>,
|
||||
) -> ApiResult<()> {
|
||||
let body = generated_types::AdminMessageDeleteRequest { basis };
|
||||
let _: serde_json::Value = self
|
||||
.send(
|
||||
Method::DELETE,
|
||||
@@ -25,7 +29,7 @@ impl AdminApiClient {
|
||||
urlencoding::encode(channel_id),
|
||||
urlencoding::encode(message_id)
|
||||
),
|
||||
None::<&serde_json::Value>,
|
||||
Some(&body),
|
||||
audit_log_reason,
|
||||
)
|
||||
.await?;
|
||||
@@ -54,35 +58,44 @@ impl AdminApiClient {
|
||||
pub async fn queue_message_shred(
|
||||
&self,
|
||||
user_id: &str,
|
||||
entries: &[serde_json::Value],
|
||||
) -> ApiResult<MessageShredResponse> {
|
||||
let entries = entries
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(serde_json::from_value::<generated_types::AdminUserMessageShredRequestEntriesItem>)
|
||||
.collect::<Result<Vec<_>, _>>()
|
||||
.map_err(|e| ApiError::Parse(e.to_string()))?;
|
||||
let body = generated_types::AdminUserMessageShredRequest { entries };
|
||||
let response = self
|
||||
.generated()
|
||||
.shred_admin_user_messages(&snowflake(user_id), &body)
|
||||
.await
|
||||
.map_err(|e| self.generated_error(e))?;
|
||||
self.generated_value(response.into_inner())
|
||||
entries: Vec<generated_types::AdminUserMessageShredRequestEntriesItem>,
|
||||
basis: &BasisSubmission,
|
||||
) -> ApiResult<generated_types::MessageShredResponse> {
|
||||
let body = generated_types::AdminUserMessageShredRequest {
|
||||
basis: basis.moderation_basis(),
|
||||
entries,
|
||||
};
|
||||
self.send(
|
||||
Method::POST,
|
||||
&format!(
|
||||
"/admin/users/{}/message-shreds",
|
||||
urlencoding::encode(user_id)
|
||||
),
|
||||
Some(&body),
|
||||
basis.internal_note(),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn delete_all_user_messages(
|
||||
&self,
|
||||
user_id: &str,
|
||||
dry_run: bool,
|
||||
) -> ApiResult<DeleteAllUserMessagesResponse> {
|
||||
let dry_run = if dry_run { "true" } else { "false" };
|
||||
let response = self
|
||||
.generated()
|
||||
.delete_admin_user_messages(&snowflake(user_id), Some(dry_run))
|
||||
.await
|
||||
.map_err(|e| self.generated_error(e))?;
|
||||
self.generated_value(response.into_inner())
|
||||
basis: Option<&BasisSubmission>,
|
||||
) -> ApiResult<generated_types::DeleteAllUserMessagesResponse> {
|
||||
let body = generated_types::AdminUserMessageDeleteRequest {
|
||||
basis: basis.map(BasisSubmission::moderation_basis),
|
||||
};
|
||||
let dry_run = if basis.is_some() { "false" } else { "true" };
|
||||
self.send(
|
||||
Method::DELETE,
|
||||
&format!(
|
||||
"/admin/users/{}/messages?dry_run={dry_run}",
|
||||
urlencoding::encode(user_id)
|
||||
),
|
||||
Some(&body),
|
||||
basis.and_then(BasisSubmission::internal_note),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn get_message_shred_status(
|
||||
|
||||
@@ -12,6 +12,7 @@ pub mod bulk;
|
||||
pub mod client;
|
||||
pub mod codes;
|
||||
pub mod discovery;
|
||||
pub mod enforcement;
|
||||
pub mod guild_assets;
|
||||
pub mod guild_threads;
|
||||
pub mod guilds;
|
||||
|
||||
@@ -2,7 +2,13 @@
|
||||
|
||||
use reqwest::Method;
|
||||
|
||||
use crate::api::generated::{snowflake, types::UpdateReportRequestResolution};
|
||||
use crate::api::generated::{
|
||||
snowflake,
|
||||
types::{
|
||||
UpdateReportRequest, UpdateReportRequestOutcome, UpdateReportRequestResolution,
|
||||
UpdateReportRequestStatus,
|
||||
},
|
||||
};
|
||||
|
||||
use super::client::{AdminApiClient, ApiError, ApiResult};
|
||||
use super::types::{
|
||||
@@ -44,20 +50,24 @@ impl AdminApiClient {
|
||||
&self,
|
||||
report_id: &str,
|
||||
resolution: UpdateReportRequestResolution,
|
||||
outcome: Option<UpdateReportRequestOutcome>,
|
||||
duplicate_of: Option<&str>,
|
||||
public_comment: Option<&str>,
|
||||
notify_reporter: bool,
|
||||
audit_log_reason: Option<&str>,
|
||||
) -> ApiResult<ResolveReportResponse> {
|
||||
let mut body = serde_json::json!({"status": "resolved", "resolution": resolution});
|
||||
if let Some(public_comment) = public_comment {
|
||||
body["public_comment"] = serde_json::Value::from(public_comment);
|
||||
}
|
||||
body["notify_reporter"] = serde_json::Value::from(notify_reporter);
|
||||
let body = UpdateReportRequest {
|
||||
duplicate_of: duplicate_of.map(snowflake),
|
||||
notify_reporter,
|
||||
outcome,
|
||||
public_comment: public_comment.map(str::to_owned),
|
||||
resolution,
|
||||
status: UpdateReportRequestStatus::Resolved,
|
||||
};
|
||||
self.send(
|
||||
Method::PATCH,
|
||||
&format!("/admin/reports/{}", urlencoding::encode(report_id)),
|
||||
Some(&body),
|
||||
audit_log_reason,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
@@ -118,6 +118,8 @@ pub struct AdminUser {
|
||||
pub deletion_scheduled_by: Option<String>,
|
||||
#[serde(default)]
|
||||
pub deletion_scheduled_at: Option<String>,
|
||||
#[serde(default)]
|
||||
pub enforcement_decision_id: Option<String>,
|
||||
pub last_active_at: Option<String>,
|
||||
pub last_active_ip: Option<String>,
|
||||
pub last_active_ip_reverse: Option<String>,
|
||||
@@ -253,6 +255,8 @@ pub struct BanCheckResult {
|
||||
#[serde(default)]
|
||||
pub expires_at: Option<String>,
|
||||
#[serde(default)]
|
||||
pub decision_id: Option<String>,
|
||||
#[serde(default)]
|
||||
pub entries: Vec<serde_json::Value>,
|
||||
}
|
||||
|
||||
|
||||
@@ -8,32 +8,12 @@ pub struct LookupMessageResponse {
|
||||
pub data: serde_json::Value,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
pub struct MessageShredResponse {
|
||||
pub success: Option<bool>,
|
||||
pub job_id: Option<String>,
|
||||
pub requested: Option<u64>,
|
||||
#[serde(flatten)]
|
||||
pub extra: serde_json::Value,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
pub struct MessageShredStatusResponse {
|
||||
#[serde(flatten)]
|
||||
pub data: serde_json::Value,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
pub struct DeleteAllUserMessagesResponse {
|
||||
pub success: Option<bool>,
|
||||
pub dry_run: bool,
|
||||
pub channel_count: u64,
|
||||
pub message_count: u64,
|
||||
pub job_id: Option<String>,
|
||||
#[serde(flatten)]
|
||||
pub extra: serde_json::Value,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
pub struct BrowseChannelResponse {
|
||||
#[serde(flatten)]
|
||||
|
||||
@@ -6,6 +6,8 @@ use reqwest::Method;
|
||||
|
||||
use crate::api::generated::{snowflake, types as generated_types};
|
||||
|
||||
use crate::templates::components::enforcement_fields::BasisSubmission;
|
||||
|
||||
use super::client::{AdminApiClient, ApiError, ApiResult};
|
||||
use super::types::{
|
||||
AdminUser, AdminUserMeResponse, GuildInfo, ListUserGuildsResponse, LookupUserResponse,
|
||||
@@ -282,6 +284,7 @@ impl AdminApiClient {
|
||||
&self,
|
||||
user_id: &str,
|
||||
fields: &[String],
|
||||
basis: &BasisSubmission,
|
||||
) -> ApiResult<AdminUser> {
|
||||
let fields = fields
|
||||
.iter()
|
||||
@@ -290,14 +293,22 @@ impl AdminApiClient {
|
||||
})
|
||||
.collect::<Result<Vec<_>, _>>()
|
||||
.map_err(|e| ApiError::Parse(e.to_string()))?;
|
||||
let body = generated_types::AdminUserClearFieldsRequest { fields };
|
||||
let response = self
|
||||
.generated()
|
||||
.clear_admin_user_profile_fields(&snowflake(user_id), &body)
|
||||
.await
|
||||
.map_err(|e| self.generated_error(e))?;
|
||||
let resp: UserMutationResponse = self.generated_value(response.into_inner())?;
|
||||
Ok(resp.user)
|
||||
let body = generated_types::AdminUserClearFieldsRequest {
|
||||
basis: basis.moderation_basis(),
|
||||
fields,
|
||||
};
|
||||
let response: UserMutationResponse = self
|
||||
.send(
|
||||
Method::DELETE,
|
||||
&format!(
|
||||
"/admin/users/{}/profile-fields",
|
||||
urlencoding::encode(user_id)
|
||||
),
|
||||
Some(&body),
|
||||
basis.internal_note(),
|
||||
)
|
||||
.await?;
|
||||
Ok(response.user)
|
||||
}
|
||||
|
||||
pub async fn set_bot_status(&self, user_id: &str, is_bot: bool) -> ApiResult<AdminUser> {
|
||||
@@ -355,93 +366,28 @@ impl AdminApiClient {
|
||||
Ok(resp.user)
|
||||
}
|
||||
|
||||
pub async fn temp_ban_user(
|
||||
&self,
|
||||
user_id: &str,
|
||||
duration_hours: u32,
|
||||
reason: Option<&str>,
|
||||
notify_user: bool,
|
||||
private_reason: Option<&str>,
|
||||
) -> ApiResult<AdminUser> {
|
||||
let body = generated_types::AdminUserBanRequest {
|
||||
duration_hours: i32::try_from(duration_hours)
|
||||
.map_err(|e| ApiError::Parse(e.to_string()))?
|
||||
.into(),
|
||||
notify_user,
|
||||
reason: reason.map(std::borrow::ToOwned::to_owned),
|
||||
};
|
||||
let resp: UserMutationResponse = self
|
||||
.send(
|
||||
Method::PUT,
|
||||
&format!("/admin/users/{}/ban", urlencoding::encode(user_id)),
|
||||
Some(&body),
|
||||
private_reason,
|
||||
)
|
||||
.await?;
|
||||
Ok(resp.user)
|
||||
}
|
||||
|
||||
pub async fn unban_user(
|
||||
&self,
|
||||
user_id: &str,
|
||||
public_reason: Option<&str>,
|
||||
notify_user: bool,
|
||||
private_reason: Option<&str>,
|
||||
) -> ApiResult<AdminUser> {
|
||||
let body = generated_types::AdminUserUnbanRequest {
|
||||
notify_user,
|
||||
public_reason: public_reason.map(std::borrow::ToOwned::to_owned),
|
||||
};
|
||||
let response = self
|
||||
.generated_with_reason(private_reason)?
|
||||
.unban_admin_user(&snowflake(user_id), &body)
|
||||
.unban_admin_user(&snowflake(user_id))
|
||||
.await
|
||||
.map_err(|e| self.generated_error(e))?;
|
||||
let resp: UserMutationResponse = self.generated_value(response.into_inner())?;
|
||||
Ok(resp.user)
|
||||
}
|
||||
|
||||
pub async fn schedule_deletion(
|
||||
&self,
|
||||
user_id: &str,
|
||||
reason_code: i32,
|
||||
public_reason: Option<&str>,
|
||||
days_until_deletion: u32,
|
||||
notify_user: bool,
|
||||
audit_log_reason: Option<&str>,
|
||||
) -> ApiResult<AdminUser> {
|
||||
let body = generated_types::AdminUserDeletionScheduleRequest {
|
||||
days_until_deletion: crate::api::generated::nonzero_u32(
|
||||
days_until_deletion,
|
||||
"days_until_deletion",
|
||||
)
|
||||
.map_err(ApiError::Parse)?
|
||||
.into(),
|
||||
notify_user,
|
||||
public_reason: public_reason.map(std::borrow::ToOwned::to_owned),
|
||||
reason_code: crate::api::generated::deletion_reason_code(reason_code, "reason_code")
|
||||
.map_err(ApiError::Parse)?,
|
||||
replace_pending_deletion_at: None,
|
||||
};
|
||||
let response = self
|
||||
.generated_with_reason(audit_log_reason)?
|
||||
.schedule_admin_user_deletion(&snowflake(user_id), &body)
|
||||
.await
|
||||
.map_err(|error| self.generated_error(error))?;
|
||||
let resp: UserMutationResponse = self.generated_value(response.into_inner())?;
|
||||
Ok(resp.user)
|
||||
}
|
||||
|
||||
pub async fn cancel_deletion(
|
||||
&self,
|
||||
user_id: &str,
|
||||
expected_pending_deletion_at: &str,
|
||||
notify_user: bool,
|
||||
audit_log_reason: Option<&str>,
|
||||
) -> ApiResult<AdminUser> {
|
||||
let body = serde_json::json!({
|
||||
"expected_pending_deletion_at": expected_pending_deletion_at,
|
||||
"notify_user": notify_user,
|
||||
});
|
||||
let resp: UserMutationResponse = self
|
||||
.send(
|
||||
@@ -454,25 +400,6 @@ impl AdminApiClient {
|
||||
Ok(resp.user)
|
||||
}
|
||||
|
||||
pub async fn annotate_ban(
|
||||
&self,
|
||||
user_id: &str,
|
||||
ban_audit_log_id: &str,
|
||||
note: &str,
|
||||
) -> ApiResult<()> {
|
||||
let body = serde_json::json!({
|
||||
"ban_audit_log_id": ban_audit_log_id,
|
||||
"note": note,
|
||||
});
|
||||
self.send_void(
|
||||
Method::POST,
|
||||
&format!("/admin/users/{}/ban/notes", urlencoding::encode(user_id)),
|
||||
Some(&body),
|
||||
None,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn change_dob(&self, user_id: &str, dob: &str) -> ApiResult<AdminUser> {
|
||||
let body = generated_types::AdminUserDobUpdateRequest {
|
||||
date_of_birth: dob.to_owned(),
|
||||
|
||||
Reference in new issue
Block a user