use crate::wire::RespJson;
use nagisa_types::prelude::*;
use serde_json::Value;
pub(crate) fn map_response(action: &str, resp: RespJson) -> Result<Value> {
let ok = resp.status == "ok" || resp.status == "async" || resp.retcode == 0;
if ok {
return Ok(resp.data);
}
if resp.retcode == 404 {
return Err(Error::Unsupported(action.to_string()));
}
let message = resp.message.unwrap_or_else(|| format!("retcode {}", resp.retcode));
let kind = classify_retcode(resp.retcode);
Err(Error::Action { retcode: resp.retcode, message, kind })
}
pub(crate) fn classify_retcode(retcode: i64) -> ActionErrorKind {
match retcode {
404 | 1404 => ActionErrorKind::Unsupported,
1400 | 10003 | 10004 => ActionErrorKind::BadParams,
401 | 403 | 1403 => ActionErrorKind::AuthFailed,
429 => ActionErrorKind::RateLimited,
r if (100..600).contains(&r) => ActionErrorKind::Internal,
_ => ActionErrorKind::Other,
}
}
pub(crate) fn echo_key(echo: &Value) -> String {
match echo {
Value::String(s) => s.clone(),
other => other.to_string(),
}
}
pub(crate) fn data_i64(v: &Value, key: &str) -> Option<i64> {
match v.get(key)? {
Value::Number(n) => n.as_i64(),
Value::String(s) => s.parse().ok(),
_ => None,
}
}
pub(crate) fn data_str(v: &Value, key: &str) -> Option<String> {
v.get(key).and_then(|x| x.as_str()).map(String::from)
}
pub(crate) fn csrf_token_from_data(data: &Value) -> String {
data_i64(data, "csrf_token")
.or_else(|| data_i64(data, "token"))
.map(|t| t.to_string())
.or_else(|| data_str(data, "csrf_token"))
.or_else(|| data_str(data, "token"))
.unwrap_or_default()
}
pub(crate) fn forward_send_result(data: &Value, peer: Peer) -> ForwardSendResult {
let onebot_id = data_i64(data, "message_id").map(|v| v as i32);
let forward_id = data_str(data, "forward_id")
.or_else(|| data_str(data, "res_id"))
.or_else(|| data_str(data, "resid"));
ForwardSendResult { message_id: MessageId { peer, seq: 0, onebot_id }, forward_id }
}
pub(crate) fn decode_rkey_list(data: &Value) -> Vec<Rkey> {
let arr = data
.get("rkeys")
.and_then(Value::as_array)
.or_else(|| data.as_array())
.cloned();
if let Some(arr) = arr {
return arr
.into_iter()
.map(|v| Rkey {
kind: data_str(&v, "type").unwrap_or_default(),
rkey: data_str(&v, "rkey").unwrap_or_default(),
create_time: data_i64(&v, "created_at").or_else(|| data_i64(&v, "create_time")),
ttl: data_i64(&v, "ttl").or_else(|| data_i64(&v, "ttl_seconds")),
raw: v,
})
.collect();
}
let expired_time = data_i64(data, "expired_time");
let mut out = Vec::new();
for (field, kind) in [("private_key", "private"), ("group_key", "group")] {
if let Some(rkey) = data_str(data, field) {
out.push(Rkey {
kind: kind.to_string(),
rkey,
create_time: None,
ttl: expired_time,
raw: data.clone(),
});
}
}
out
}
pub(crate) fn onebot_id_of(id: &MessageId) -> Result<i32> {
id.onebot_id.ok_or_else(|| {
Error::action_kind(ActionErrorKind::BadParams, "message id has no onebot_id")
})
}
fn parse_sex(s: Option<&str>) -> Sex {
match s {
Some("male") => Sex::Male,
Some("female") => Sex::Female,
_ => Sex::Unknown,
}
}
fn parse_role(s: Option<&str>) -> Role {
match s {
Some("owner") => Role::Owner,
Some("admin") => Role::Admin,
_ => Role::Member,
}
}
pub(crate) fn group_info_from(v: &Value) -> GroupInfo {
GroupInfo {
group: Uin(data_i64(v, "group_id").unwrap_or(0)),
name: data_str(v, "group_name").unwrap_or_default(),
member_count: data_i64(v, "member_count").unwrap_or(0) as i32,
max_member_count: data_i64(v, "max_member_count").unwrap_or(0) as i32,
remark: data_str(v, "group_remark"),
created_time: data_i64(v, "group_create_time"),
description: data_str(v, "group_memo"),
announcement: None,
question: None,
is_freeze: v.get("is_freeze").and_then(Value::as_bool),
active_member_count: data_i64(v, "active_member_count").map(|x| x as i32),
is_top: v.get("is_top").and_then(Value::as_bool),
owner_id: data_i64(v, "owner_id").map(Uin),
shut_up_all_time: data_i64(v, "shut_up_all_timestamp"),
shut_up_me_time: data_i64(v, "shut_up_me_timestamp"),
raw: v.clone(),
}
}
pub(crate) fn member_info_from(v: &Value) -> MemberInfo {
MemberInfo {
user: Uin(data_i64(v, "user_id").unwrap_or(0)),
group: Uin(data_i64(v, "group_id").unwrap_or(0)),
nickname: data_str(v, "nickname").unwrap_or_default(),
card: data_str(v, "card").unwrap_or_default(),
title: data_str(v, "title").unwrap_or_default(),
level: data_str(v, "level").and_then(|s| s.parse().ok()).unwrap_or(0),
role: parse_role(v.get("role").and_then(|x| x.as_str())),
sex: parse_sex(v.get("sex").and_then(|x| x.as_str())),
age: data_i64(v, "age").map(|x| x as i32),
join_time: data_i64(v, "join_time").unwrap_or(0),
last_sent_time: data_i64(v, "last_sent_time"),
mute_end_time: data_i64(v, "shut_up_timestamp").or_else(|| data_i64(v, "mute_end_time")),
area: data_str(v, "area").filter(|s| !s.is_empty()),
unfriendly: v.get("unfriendly").and_then(Value::as_bool),
title_expire_time: data_i64(v, "title_expire_time"),
card_changeable: v.get("card_changeable").and_then(Value::as_bool),
qq_level: data_i64(v, "qq_level").map(|x| x as i32),
is_robot: v.get("is_robot").and_then(Value::as_bool),
qage: data_i64(v, "qage").map(|x| x as i32),
raw: v.clone(),
}
}
fn friend_status_from(v: &Value) -> FriendStatus {
FriendStatus {
status: data_i64(v, "status").map(|x| x as i32),
ext_status: data_i64(v, "ext_status").map(|x| x as i32),
battery_status: data_i64(v, "battery_status").map(|x| x as i32),
}
}
fn business_from(v: &Value) -> Business {
Business {
kind: data_i64(v, "type").map(|x| x as i32),
name: data_str(v, "name").filter(|s| !s.is_empty()),
level: data_i64(v, "level").map(|x| x as i32),
icon: data_str(v, "icon").filter(|s| !s.is_empty()),
is_pro: v.get("is_pro").and_then(Value::as_bool),
is_year: v.get("is_year").and_then(Value::as_bool),
}
}
fn birthday_from(v: &Value) -> Option<String> {
let y = data_i64(v, "birthday_year")?;
let m = data_i64(v, "birthday_month")?;
let d = data_i64(v, "birthday_day")?;
if y == 0 || m == 0 || d == 0 {
return None;
}
Some(format!("{y:04}-{m:02}-{d:02}"))
}
pub(crate) fn user_info_from(v: &Value) -> UserInfo {
UserInfo {
user: Uin(data_i64(v, "user_id").unwrap_or(0)),
nickname: data_str(v, "nickname").unwrap_or_default(),
sex: parse_sex(v.get("sex").and_then(|x| x.as_str())),
age: data_i64(v, "age").map(|a| a as i32),
level: data_i64(v, "level").map(|l| l as i32),
qid: data_str(v, "q_id").or_else(|| data_str(v, "qid")).filter(|s| !s.is_empty()),
bio: data_str(v, "sign").or_else(|| data_str(v, "bio")).filter(|s| !s.is_empty()),
country: data_str(v, "country").filter(|s| !s.is_empty()),
city: data_str(v, "city").filter(|s| !s.is_empty()),
school: data_str(v, "school").filter(|s| !s.is_empty()),
remark: data_str(v, "remark").filter(|s| !s.is_empty()),
status: v.get("status").filter(|s| s.is_object()).map(friend_status_from),
business: v
.get("Business")
.and_then(Value::as_array)
.map(|arr| arr.iter().map(business_from).collect())
.unwrap_or_default(),
register_time: data_i64(v, "RegisterTime").or_else(|| data_i64(v, "register_time")),
avatar: data_str(v, "avatar").filter(|s| !s.is_empty()),
raw: v.clone(),
}
}
pub(crate) fn friend_info_from(v: &Value) -> FriendInfo {
FriendInfo {
user: Uin(data_i64(v, "user_id").unwrap_or(0)),
nickname: data_str(v, "nickname").unwrap_or_default(),
sex: parse_sex(v.get("sex").and_then(|x| x.as_str())),
remark: data_str(v, "remark").unwrap_or_default(),
qid: data_str(v, "q_id").or_else(|| data_str(v, "qid")).filter(|s| !s.is_empty()),
category: friend_category_from(v),
group: v.get("group").filter(|g| g.is_object()).map(friend_group_from),
birthday: birthday_from(v),
phone: data_str(v, "phoneNum")
.or_else(|| data_str(v, "phone"))
.filter(|s| !s.is_empty() && s != "-"),
email: data_str(v, "email").filter(|s| !s.is_empty()),
login_days: data_i64(v, "login_days").map(|x| x as i32),
long_nick: data_str(v, "longNick")
.or_else(|| data_str(v, "long_nick"))
.filter(|s| !s.is_empty()),
raw: v.clone(),
}
}
fn friend_group_from(v: &Value) -> FriendGroup {
FriendGroup {
group_id: data_i64(v, "group_id").map(|x| x as i32).unwrap_or(0),
group_name: data_str(v, "group_name").unwrap_or_default(),
}
}
fn friend_category_from(v: &Value) -> Option<FriendCategory> {
let id = data_i64(v, "categoryId").or_else(|| data_i64(v, "category_id"))?;
let name = data_str(v, "categroyName")
.or_else(|| data_str(v, "categoryName"))
.or_else(|| data_str(v, "category_name"))
.unwrap_or_default();
Some(FriendCategory { id: id as i32, name })
}
pub(crate) fn honor_member_from(v: &Value) -> HonorMember {
HonorMember {
user: Uin(data_i64(v, "user_id").unwrap_or(0)),
nickname: data_str(v, "nickname").unwrap_or_default(),
avatar: data_str(v, "avatar"),
description: data_str(v, "description"),
day_count: data_i64(v, "day_count").map(|x| x as i32),
raw: v.clone(),
}
}
pub(crate) fn announcement_from(group: Uin, v: &Value) -> Announcement {
let msg = v.get("message");
let content = msg
.and_then(|m| m.get("text"))
.and_then(|t| t.as_str())
.map(String::from)
.unwrap_or_default();
let image_url = msg
.and_then(|m| m.get("images"))
.and_then(|i| i.as_array())
.and_then(|arr| arr.first())
.and_then(|img| {
img.get("url")
.and_then(|u| u.as_str())
.or_else(|| img.get("id").and_then(|u| u.as_str()))
.map(String::from)
})
.filter(|s| !s.is_empty());
Announcement {
id: data_str(v, "notice_id").unwrap_or_default(),
group,
sender: Uin(data_i64(v, "sender_id").unwrap_or(0)),
content,
time: data_i64(v, "publish_time").or_else(|| data_i64(v, "time")).unwrap_or(0),
image_url,
raw: v.clone(),
}
}
pub(crate) fn essence_from(group: Uin, v: &Value) -> EssenceMessage {
let onebot_id = data_i64(v, "message_id").map(|m| m as i32);
let content = v
.get("content")
.map(|c| crate::decode::decode_message_value(c, Peer::group(group.0)))
.unwrap_or_default();
EssenceMessage {
group,
message_id: MessageId { peer: Peer::group(group.0), seq: 0, onebot_id },
sender: Uin(data_i64(v, "sender_id").unwrap_or(0)),
sender_nick: data_str(v, "sender_nick").unwrap_or_default(),
operator: Uin(data_i64(v, "operator_id").unwrap_or(0)),
operator_time: data_i64(v, "operator_time").unwrap_or(0),
content,
raw: v.clone(),
}
}
pub(crate) fn group_file_from(v: &Value) -> FileMeta {
FileMeta {
id: data_str(v, "file_id").unwrap_or_default(),
name: data_str(v, "file_name").unwrap_or_default(),
size: data_i64(v, "file_size").unwrap_or(0) as u64,
hash: data_str(v, "file_hash").or_else(|| data_str(v, "hash")),
busid: data_i64(v, "busid"),
uploader: data_i64(v, "uploader").map(Uin),
upload_time: data_i64(v, "upload_time"),
dead_time: data_i64(v, "dead_time"),
download_times: data_i64(v, "download_times").map(|x| x as i32),
parent_folder_id: None,
}
}
pub(crate) fn group_folder_from(v: &Value) -> GroupFolder {
GroupFolder {
id: data_str(v, "folder_id").unwrap_or_default(),
name: data_str(v, "folder_name").unwrap_or_default(),
file_count: data_i64(v, "total_file_count").map(|c| c as u32),
create_time: data_i64(v, "create_time"),
parent_folder_id: None,
last_modified_time: None,
creator_id: None,
raw: v.clone(),
}
}
pub(crate) fn impl_stat_from(data: &Value) -> Option<ImplStat> {
let s = data.get("stat")?;
Some(ImplStat {
message_received: s.get("message_received").and_then(Value::as_i64),
message_sent: s.get("message_sent").and_then(Value::as_i64),
})
}