Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 71 additions & 3 deletions src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ use crate::error::NoteDeckError;
use crate::models::{
Antenna, AuthResult, Channel, ChatMessage, ChatUser, Clip, CreateNoteParams,
NormalizedDriveFile, NormalizedNote, NormalizedNoteReaction, NormalizedNotification,
NormalizedUser, NormalizedUserDetail, RawCreateNoteResponse, RawDriveFile, RawEmojisResponse,
RawMiAuthResponse, RawNote, RawNoteReaction, RawNotification, RawUser, RawUserDetail,
SearchOptions, ServerEmoji, TimelineOptions, TimelineType, UserList,
MutedWordsResult, NormalizedUser, NormalizedUserDetail, RawCreateNoteResponse, RawDriveFile,
RawEmojisResponse, RawMiAuthResponse, RawNote, RawNoteReaction, RawNotification, RawUser,
RawUserDetail, SearchOptions, ServerEmoji, TimelineOptions, TimelineType, UserList,
};

/// Maximum response body size (50 MB) to prevent memory exhaustion from malicious servers.
Expand Down Expand Up @@ -2276,6 +2276,74 @@ impl MisskeyClient {
Ok(ids)
}

/// `i`(meDetailed) から `mutedWords` / `hardMutedWords` を取得する(read のみ、#610)。
/// 想定外の要素形は serde untagged で取り切れない場合があるため、欠損時は空配列にフォールバック。
pub async fn muted_words(
&self,
host: &str,
token: &str,
) -> Result<MutedWordsResult, NoteDeckError> {
let data = self.request(host, token, "i", json!({})).await?;
let muted_words = data
.get("mutedWords")
.cloned()
.and_then(|v| serde_json::from_value(v).ok())
.unwrap_or_default();
let hard_muted_words = data
.get("hardMutedWords")
.cloned()
.and_then(|v| serde_json::from_value(v).ok())
.unwrap_or_default();
let muted_instances = data
.get("mutedInstances")
.cloned()
.and_then(|v| serde_json::from_value(v).ok())
.unwrap_or_default();
Ok(MutedWordsResult {
muted_words,
hard_muted_words,
muted_instances,
})
}

/// renote mute 中のユーザー ID 一覧を取得する(#614: 起動時の renote mute store hydrate)。
/// `renote-mute/list` は RenoteMuting レコード配列を返すため muteeId を抽出する。
pub async fn renote_muted_user_ids(
&self,
host: &str,
token: &str,
) -> Result<Vec<String>, NoteDeckError> {
const PAGE: usize = 100;
let mut ids = Vec::new();
let mut until_id: Option<String> = None;
loop {
let mut params = json!({ "limit": PAGE });
apply_pagination(&mut params, None, until_id.as_deref());
let data = self
.request(host, token, "renote-mute/list", params)
.await?;
let Some(arr) = data.as_array() else { break };
if arr.is_empty() {
break;
}
for item in arr {
if let Some(mutee_id) = item.get("muteeId").and_then(|v| v.as_str()) {
ids.push(mutee_id.to_string());
}
}
// 次ページの起点は RenoteMuting レコードの id。
until_id = arr
.last()
.and_then(|v| v.get("id"))
.and_then(|v| v.as_str())
.map(String::from);
if arr.len() < PAGE || until_id.is_none() {
break;
}
}
Ok(ids)
}

pub async fn renote_mute_user(
&self,
host: &str,
Expand Down
22 changes: 22 additions & 0 deletions src/models.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1219,6 +1219,28 @@ pub struct RawUserDetail {
pub with_replies: Option<bool>,
}

/// Misskey の `mutedWords` / `hardMutedWords` の 1 要素。
/// 文字列配列なら AND 語群(全語含むとマッチ)、文字列なら `/regex/flags` 形式の正規表現。
#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
#[cfg_attr(feature = "specta", derive(specta::Type))]
#[serde(untagged)]
pub enum MutedWord {
Group(Vec<String>),
Pattern(String),
}

/// `i`(meDetailed) から取得する word mute 設定(read のみ、#610)。
/// soft = `mutedWords`(隠して展開可)、hard = `hardMutedWords`(完全非表示)。
#[derive(Debug, Clone, Serialize, utoipa::ToSchema)]
#[cfg_attr(feature = "specta", derive(specta::Type))]
#[serde(rename_all = "camelCase")]
pub struct MutedWordsResult {
pub muted_words: Vec<MutedWord>,
pub hard_muted_words: Vec<MutedWord>,
/// インスタンスミュート(#613)。ミュート対象ホスト名の配列。同じ `i` から取得。
pub muted_instances: Vec<String>,
}

#[derive(Debug, Deserialize)]
pub struct RawMiAuthResponse {
pub ok: bool,
Expand Down
Loading