diff --git a/README.MD b/README.MD index 30a964d..c35b573 100644 --- a/README.MD +++ b/README.MD @@ -20,29 +20,29 @@ ## Features List -| Protocol | Support | Login | Support | Messages | Support | Operations | Support | Events | Support | -|----------|:-------:|----------------------------|:-------:|:--------------|:-------:|:------------------|:-------:|:--------------------|:-------:| -| Windows | 🔴 | QrCode | 🟢 | BounceFace | 🔴 | Poke | 🔴 | ~~Captcha~~ | 🔴 | -| macOS | 🔴 | ~~Password~~ | 🔴 | Face | 🟡 [^1] | Recall | 🔴 | BotOnline | 🟢 | -| Linux | 🟢 | EasyLogin | 🟡 | File | 🟡[^1] | Leave Group | 🔴 | BotOffline | 🟢 | -| | | UnusualDevice
Password | 🔴 | Forward | 🟢 | Set Special Title | 🔴 | Message | 🟢 | -| | | UnusualDevice
Easy | 🔴 | ~~GreyTip~~ | 🔴 | Kick Member | 🔴 | Poke | 🟢 | -| | | ~~NewDeviceVerify~~ | 🔴 | GroupReaction | 🔴 | Mute Member | 🔴 | MessageRecall | 🔴 | -| | | | | Image | 🟢 | Set Admin | 🔴 | GroupMemberDecrease | 🔴 | -| | | | | Json | 🟢 | Friend Request | 🔴 | GroupMemberIncrease | 🔴 | -| | | | | KeyBoard | 🔴 | Group Request | 🔴 | GroupPromoteAdmin | 🔴 | -| | | | | LightApp | 🟢 | ~~Voice Call~~ | 🔴 | GroupInvite | 🔴 | -| | | | | LongMsg | 🟡[^1] | Client Key | 🔴 | GroupRequestJoin | 🔴 | -| | | | | Markdown | 🔴 | Cookies | 🔴 | FriendRequest | 🔴 | -| | | | | MarketFace | 🟡[^1] | Send Message | 🔴 | ~~FriendTyping~~ | 🔴 | -| | | | | Mention | 🟢 | | | ~~FriendVoiceCall~~ | 🔴 | -| | | | | MultiMsg | 🟡[^1] | | | | | -| | | | | Poke | 🔴 | | | | | -| | | | | Record | 🟢 | | | | | -| | | | | SpecialPoke | 🔴 | | | | | -| | | | | Text | 🟢 | | | | | -| | | | | Video | 🟢 | | | | | -| | | | | Xml | 🟢 | | | | | +| Protocol | Support | Login | Support | Messages | Support | Operations | Support | Events | Support | +|----------|:-------:|--------------------------------|:-------:|:--------------|:-------:|:------------------|:-------:|:--------------------|:-------:| +| Windows | 🔴 | QrCode | 🟢 | BounceFace | 🔴 | Poke | 🔴 | ~~Captcha~~ | 🔴 | +| macOS | 🔴 | ~~Password~~ | 🔴 | Face | 🟡 [^1] | Recall | 🔴 | BotOnline | 🟢 | +| Linux | 🟢 | EasyLogin | 🟡 | File | 🟡[^1] | Leave Group | 🔴 | BotOffline | 🟢 | +| | | ~~UnusualDevice
Password~~ | 🔴 | Forward | 🟢 | Set Special Title | 🔴 | Message | 🟢 | +| | | ~~UnusualDevice
Easy~~ | 🔴 | ~~GreyTip~~ | 🔴 | Kick Member | 🔴 | Poke | 🟢 | +| | | ~~NewDeviceVerify~~ | 🔴 | GroupReaction | 🔴 | Mute Member | 🔴 | MessageRecall | 🔴 | +| | | | | Image | 🟢 | Set Admin | 🔴 | GroupMemberDecrease | 🔴 | +| | | | | Json | 🟢 | Friend Request | 🔴 | GroupMemberIncrease | 🔴 | +| | | | | KeyBoard | 🔴 | Group Request | 🔴 | GroupPromoteAdmin | 🔴 | +| | | | | LightApp | 🟢 | ~~Voice Call~~ | 🔴 | GroupInvite | 🔴 | +| | | | | LongMsg | 🟡[^1] | Client Key | 🔴 | GroupRequestJoin | 🟢 | +| | | | | Markdown | 🔴 | Cookies | 🔴 | FriendRequest | 🔴 | +| | | | | MarketFace | 🟡[^1] | Send Message | 🔴 | ~~FriendTyping~~ | 🔴 | +| | | | | Mention | 🟢 | | | ~~FriendVoiceCall~~ | 🔴 | +| | | | | MultiMsg | 🟡[^1] | | | | | +| | | | | Poke | 🔴 | | | | | +| | | | | Record | 🟢 | | | | | +| | | | | SpecialPoke | 🔴 | | | | | +| | | | | Text | 🟢 | | | | | +| | | | | Video | 🟢 | | | | | +| | | | | Xml | 🟢 | | | | | [^1]: Only implemented event parsing diff --git a/mania/src/core/business.rs b/mania/src/core/business.rs index d25d659..6a2620c 100644 --- a/mania/src/core/business.rs +++ b/mania/src/core/business.rs @@ -11,7 +11,9 @@ use std::pin::Pin; use std::sync::Arc; use std::time::Duration; -use crate::core::cache::Cache; +use crate::ClientConfig; +pub use crate::core::cache::Cache; +use crate::core::connect::optimum_server; use crate::core::context::Context; use crate::core::event::prelude::*; use crate::core::event::{CEParse, resolve_event}; @@ -110,7 +112,8 @@ pub struct Business { } impl Business { - pub async fn new(addr: SocketAddr, context: Arc) -> BusinessResult { + pub async fn new(config: Arc, context: Arc) -> BusinessResult { + let addr = optimum_server(config.get_optimum_server, config.use_ipv6_network).await?; let (sender, receiver) = socket::connect(addr).await?; let event_dispatcher = EventDispatcher::new(); let event_listener = EventListener::new(&event_dispatcher); @@ -119,7 +122,7 @@ impl Business { reconnecting: Mutex::new(()), pending_requests: DashMap::new(), context, - cache: Arc::new(Cache::new()), + cache: Arc::new(Cache::new(config.cache_mode)), // TODO: construct from context event_dispatcher, event_listener, }); diff --git a/mania/src/core/business/messaging_logic.rs b/mania/src/core/business/messaging_logic.rs index 8335965..90b51af 100644 --- a/mania/src/core/business/messaging_logic.rs +++ b/mania/src/core/business/messaging_logic.rs @@ -6,7 +6,7 @@ use crate::core::event::notify::group_sys_request_join::GroupSysRequestJoinEvent use crate::core::event::prelude::*; use crate::event::friend::{FriendEvent, friend_message}; use crate::event::group::group_poke::GroupPokeEvent; -use crate::event::group::{GroupEvent, group_message}; +use crate::event::group::{GroupEvent, group_join_request, group_message}; use crate::event::system::{SystemEvent, temp_message}; use crate::message::chain::{MessageChain, MessageType}; use crate::message::entity::Entity; @@ -31,8 +31,8 @@ async fn messaging_logic_incoming( event: &mut dyn ServerEvent, handle: Arc, ) -> &dyn ServerEvent { - match event { - _ if let Some(msg) = event.as_any_mut().downcast_mut::() => { + { + if let Some(msg) = event.as_any_mut().downcast_mut::() { if let Some(mut chain) = msg.chain.take() { resolve_incoming_chain(&mut chain, handle.clone()).await; // TODO: await ResolveChainMetadata(push.Chain); @@ -40,57 +40,109 @@ async fn messaging_logic_incoming( // TODO?: sb tx! Collection.Invoker.PostEvent(new GroupInvitationEvent(groupUin, chain.FriendUin, sequence)); match &chain.typ { MessageType::Group(_) => { - handle - .event_dispatcher - .group - .send(Some(GroupEvent::GroupMessage( - group_message::GroupMessageEvent { chain }, - ))) - .expect("Failed to send group_message event"); + if let Err(e) = + handle + .event_dispatcher + .group + .send(Some(GroupEvent::GroupMessage( + group_message::GroupMessageEvent { chain }, + ))) + { + tracing::error!("Failed to send group_message event: {:?}", e); + } } MessageType::Friend(_) => { - handle - .event_dispatcher - .friend - .send(Some(FriendEvent::FriendMessageEvent( - friend_message::FriendMessageEvent { chain }, - ))) - .expect("Failed to send friend_message event"); + if let Err(e) = handle.event_dispatcher.friend.send(Some( + FriendEvent::FriendMessageEvent(friend_message::FriendMessageEvent { + chain, + }), + )) { + tracing::error!("Failed to send friend_message event: {:?}", e); + } } MessageType::Temp => { - handle - .event_dispatcher - .system - .send(Some(SystemEvent::TempMessageEvent( - temp_message::TempMessageEvent { chain }, - ))) - .expect("Failed to send temp_message event"); + if let Err(e) = handle.event_dispatcher.system.send(Some( + SystemEvent::TempMessageEvent(temp_message::TempMessageEvent { chain }), + )) { + tracing::error!("Failed to send temp_message event: {:?}", e); + } } _ => {} } } else { tracing::warn!("Empty message chain in PushMessageEvent"); } + return event; } - _ if let Some(event) = event + } + { + if let Some(req) = event .as_any_mut() - .downcast_mut::() => + .downcast_mut::() { - tracing::debug!("Handling GroupSysRequestJoinEvent: {:?}", event); // TODO: dispatch + let target_uin = match handle.resolve_stranger_uid2uin(&req.target_uid).await { + Ok(uin) => uin, + Err(e) => { + tracing::error!( + "Failed to resolve stranger uid for {}: {:?}", + req.target_uid, + e + ); + return event; + } + }; + let requests = match handle.fetch_group_requests().await { + Ok(r) => r, + Err(e) => { + tracing::error!("Failed to fetch group requests: {:?}", e); + return event; + } + }; + if let Some(r) = requests + .iter() + .find(|r| r.group_uin == req.group_uin && r.target_member_uin == target_uin) + { + if let Err(e) = + handle + .event_dispatcher + .group + .send(Some(GroupEvent::GroupJoinRequest( + group_join_request::GroupJoinRequestEvent { + group_uin: req.group_uin, + target_uin, + target_nickname: r.target_member_card.to_owned(), + invitor_uin: r.invitor_member_uin.unwrap_or_default(), + answer: r.comment.to_owned().unwrap_or_default(), + request_seq: r.sequence, + }, + ))) + { + tracing::error!("Failed to send group join request event: {:?}", e); + } + } else { + tracing::warn!("No group join request found for target: {}", target_uin); + } + return event; + } + } + { + if let Some(poke) = event.as_any_mut().downcast_mut::() { + if let Err(e) = handle + .event_dispatcher + .group + .send(Some(GroupEvent::GroupPoke(GroupPokeEvent { + group_uin: poke.group_uin, + operator_uin: poke.operator_uin, + target_uin: poke.target_uin, + action: poke.action.to_owned(), + suffix: poke.suffix.to_owned(), + action_img_url: poke.action_img_url.to_owned(), + }))) + { + tracing::error!("Failed to send group poke event: {:?}", e); + } + return event; } - _ if let Some(event) = event.as_any_mut().downcast_mut::() => handle - .event_dispatcher - .group - .send(Some(GroupEvent::GroupPoke(GroupPokeEvent { - group_uin: event.group_uin, - operator_uin: event.operator_uin, - target_uin: event.target_uin, - action: event.action.to_owned(), - suffix: event.suffix.to_owned(), - action_img_url: event.action_img_url.to_owned(), - }))) - .expect("Failed to send group event"), - _ => {} } event } @@ -229,9 +281,9 @@ async fn resolve_incoming_chain(chain: &mut MessageChain, handle: Arc { let uid = handle - .resolve_uid(Some(grp.group_uin), chain.friend_uin) - .await; - let uid = uid.unwrap_or_default(); + .uin2uid(chain.friend_uin, Some(grp.group_uin)) + .await + .unwrap_or_default(); match handle .download_video( &uid, diff --git a/mania/src/core/cache.rs b/mania/src/core/cache.rs index b492873..11c4c48 100644 --- a/mania/src/core/cache.rs +++ b/mania/src/core/cache.rs @@ -1,20 +1,64 @@ use crate::entity::bot_friend::BotFriend; use crate::entity::bot_group_member::BotGroupMember; use dashmap::DashMap; -use tokio::sync::RwLock; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum CacheMode { + Full, + Half, + None, +} pub struct Cache { - pub uin2uid: DashMap, - pub cached_friends: RwLock>, - pub cached_group_members: DashMap>, + pub(crate) cache_mode: CacheMode, + pub(crate) uin2uid: Option>, + pub(crate) uid2uin: Option>, + pub(crate) cached_friends: Option>, + pub(crate) cached_group_members: Option>>, } impl Cache { - pub fn new() -> Self { + pub fn new(cache_mode: CacheMode) -> Self { + match cache_mode { + CacheMode::Full => Self::full(), + CacheMode::Half => Self::half(), + CacheMode::None => Self::none(), + } + } + + fn full() -> Self { Self { - uin2uid: DashMap::new(), - cached_friends: RwLock::new(Vec::new()), - cached_group_members: DashMap::new(), + cache_mode: CacheMode::Full, + uin2uid: Some(DashMap::new()), + uid2uin: Some(DashMap::new()), + cached_friends: Some(DashMap::new()), + cached_group_members: Some(DashMap::new()), } } + + fn half() -> Self { + Self { + cache_mode: CacheMode::Half, + uin2uid: None, + uid2uin: None, + cached_friends: Some(DashMap::new()), + cached_group_members: Some(DashMap::new()), + } + } + + fn none() -> Self { + Self { + cache_mode: CacheMode::None, + uin2uid: None, + uid2uin: None, + cached_friends: None, + cached_group_members: None, + } + } + + pub(crate) fn insert_uin_uid(&self, uin: u32, uid: String) { + // SAFETY: we can ensure that the DashMap is not None + self.uin2uid.as_ref().unwrap().insert(uin, uid.clone()); + self.uid2uin.as_ref().unwrap().insert(uid, uin); + } } diff --git a/mania/src/core/context.rs b/mania/src/core/context.rs index f06c273..936d3e2 100644 --- a/mania/src/core/context.rs +++ b/mania/src/core/context.rs @@ -1,13 +1,12 @@ -use rand::Rng; -use serde::{Deserialize, Serialize}; -use std::{fs, io}; -use uuid::Uuid; - use crate::core::crypto::consts::ECDH_256_PEER_LOGIN_KEY; use crate::core::crypto::{Ecdh, P256}; use crate::core::key_store::KeyStore; use crate::core::session::Session; use crate::core::sign::SignProvider; +use rand::Rng; +use serde::{Deserialize, Serialize}; +use std::{fs, io}; +use uuid::Uuid; #[derive(Debug, Serialize, Deserialize, Clone, Copy)] pub enum Protocol { diff --git a/mania/src/core/entity.rs b/mania/src/core/entity.rs new file mode 100644 index 0000000..02193ef --- /dev/null +++ b/mania/src/core/entity.rs @@ -0,0 +1 @@ +pub mod fetch_group_requests; diff --git a/mania/src/core/entity/fetch_group_requests.rs b/mania/src/core/entity/fetch_group_requests.rs new file mode 100644 index 0000000..e51ab16 --- /dev/null +++ b/mania/src/core/entity/fetch_group_requests.rs @@ -0,0 +1,15 @@ +#[derive(Debug, Default)] +pub struct FetchGroupRequests { + pub group_uin: u32, + pub invitor_member_uid: Option, + pub invitor_member_card: Option, + pub target_member_uid: String, + pub target_member_card: String, + pub operator_uid: Option, + pub operator_name: Option, + pub sequence: u64, + pub state: u32, + pub event_type: u32, + pub comment: Option, + pub is_filtered: bool, +} diff --git a/mania/src/core/event.rs b/mania/src/core/event.rs index cf129ae..a8c58a7 100644 --- a/mania/src/core/event.rs +++ b/mania/src/core/event.rs @@ -135,6 +135,7 @@ pub(crate) mod prelude { pub use mania_macros::{DummyEvent, ServerEvent, command, oidb_command}; pub use num_enum::TryFromPrimitive; pub use prost::Message; + pub use std::collections::HashMap; pub use std::convert::TryFrom; pub use std::fmt::Debug; } diff --git a/mania/src/core/event/message/push_msg.rs b/mania/src/core/event/message/push_msg.rs index ff328f8..e601ef4 100644 --- a/mania/src/core/event/message/push_msg.rs +++ b/mania/src/core/event/message/push_msg.rs @@ -4,7 +4,6 @@ use crate::core::event::prelude::*; use crate::core::protos::message::{GroupJoin, NotifyMessageBody, PushMsg}; use crate::message::chain::MessageChain; use crate::message::packer::MessagePacker; -use std::collections::HashMap; #[derive(Debug, Eq, PartialEq, TryFromPrimitive)] #[repr(u32)] diff --git a/mania/src/core/event/system/alive.rs b/mania/src/core/event/system/alive.rs index 84356e0..c05948d 100644 --- a/mania/src/core/event/system/alive.rs +++ b/mania/src/core/event/system/alive.rs @@ -2,9 +2,7 @@ use crate::core::event::prelude::*; #[command("Heartbeat.Alive")] #[derive(Debug, ServerEvent)] -pub struct AliveEvent { - pub test: u32, // TODO: remove it -} +pub struct AliveEvent; impl ClientEvent for AliveEvent { fn packet_type(&self) -> PacketType { @@ -16,6 +14,6 @@ impl ClientEvent for AliveEvent { } fn parse(_: Bytes, _: &Context) -> CEParseResult { - Ok(ClientResult::single(Box::new(Self { test: 0 }))) + Ok(ClientResult::single(Box::new(Self {}))) } } diff --git a/mania/src/core/event/system/fetch_filtered_group_request.rs b/mania/src/core/event/system/fetch_filtered_group_request.rs new file mode 100644 index 0000000..e97504f --- /dev/null +++ b/mania/src/core/event/system/fetch_filtered_group_request.rs @@ -0,0 +1,50 @@ +use crate::core::entity::fetch_group_requests::FetchGroupRequests; +use crate::core::event::prelude::*; +use crate::core::protos::service::oidb::{OidbSvcTrpcTcp0x10C0, OidbSvcTrpcTcp0x10C0Response}; + +#[oidb_command(0x10c0, 2)] +#[derive(Debug, ServerEvent, Default)] +pub struct FetchFilteredGroupRequestsEvent { + pub results: Vec, +} + +impl ClientEvent for FetchFilteredGroupRequestsEvent { + fn build(&self, _: &Context) -> CEBuildResult { + let request = OidbSvcTrpcTcp0x10C0 { + count: 20, + field2: 0, + }; + Ok(OidbPacket::new(0x10c0, 2, request.encode_to_vec(), false, false).to_binary()) + } + + fn parse(packet: Bytes, _: &Context) -> CEParseResult { + let response = OidbPacket::parse_into::(packet)?; + let results = response + .requests + .into_iter() + .map(|req| FetchGroupRequests { + group_uin: req.group.as_ref().map_or(0, |g| g.group_uin), + invitor_member_uid: req.invitor.as_ref().map(|i| i.uid.to_owned()), + invitor_member_card: req.invitor.as_ref().map(|i| i.name.clone()), + target_member_uid: req + .target + .as_ref() + .map_or("".to_string(), |t| t.uid.to_owned()), + target_member_card: req + .target + .as_ref() + .map_or("".to_string(), |t| t.name.to_owned()), + operator_uid: req.operator.as_ref().map(|o| o.uid.to_owned()), + operator_name: req.operator.as_ref().map(|o| o.name.to_owned()), + sequence: req.sequence, + state: req.state, + event_type: req.event_type, + comment: Some(req.comment), + is_filtered: true, + }) + .collect::>(); + Ok(ClientResult::single(Box::new( + FetchFilteredGroupRequestsEvent { results }, + ))) + } +} diff --git a/mania/src/core/event/system/fetch_friend.rs b/mania/src/core/event/system/fetch_friend.rs index b67deae..040c4f4 100644 --- a/mania/src/core/event/system/fetch_friend.rs +++ b/mania/src/core/event/system/fetch_friend.rs @@ -4,7 +4,6 @@ use crate::core::protos::service::oidb::{ OidbSvcTrpcTcp0xFd41uin, }; use crate::entity::bot_friend::{BotFriend, BotFriendGroup}; -use std::collections::HashMap; #[oidb_command(0xfd4, 1)] #[derive(Debug, ServerEvent, Default)] diff --git a/mania/src/core/event/system/fetch_group_requests.rs b/mania/src/core/event/system/fetch_group_requests.rs new file mode 100644 index 0000000..5fd431c --- /dev/null +++ b/mania/src/core/event/system/fetch_group_requests.rs @@ -0,0 +1,50 @@ +use crate::core::entity::fetch_group_requests::FetchGroupRequests; +use crate::core::event::prelude::*; +use crate::core::protos::service::oidb::{OidbSvcTrpcTcp0x10C0, OidbSvcTrpcTcp0x10C0Response}; + +#[oidb_command(0x10c0, 1)] +#[derive(Debug, ServerEvent, Default)] +pub struct FetchGroupRequestsEvent { + pub results: Vec, +} + +impl ClientEvent for FetchGroupRequestsEvent { + fn build(&self, _: &Context) -> CEBuildResult { + let request = OidbSvcTrpcTcp0x10C0 { + count: 20, + field2: 0, + }; + Ok(OidbPacket::new(0x10c0, 1, request.encode_to_vec(), false, false).to_binary()) + } + + fn parse(packet: Bytes, _: &Context) -> CEParseResult { + let response = OidbPacket::parse_into::(packet)?; + let results = response + .requests + .into_iter() + .map(|req| FetchGroupRequests { + group_uin: req.group.as_ref().map_or(0, |g| g.group_uin), + invitor_member_uid: req.invitor.as_ref().map(|i| i.uid.to_owned()), + invitor_member_card: req.invitor.as_ref().map(|i| i.name.clone()), + target_member_uid: req + .target + .as_ref() + .map_or("".to_string(), |t| t.uid.to_owned()), + target_member_card: req + .target + .as_ref() + .map_or("".to_string(), |t| t.name.to_owned()), + operator_uid: req.operator.as_ref().map(|o| o.uid.to_owned()), + operator_name: req.operator.as_ref().map(|o| o.name.to_owned()), + sequence: req.sequence, + state: req.state, + event_type: req.event_type, + comment: Some(req.comment), + is_filtered: false, + }) + .collect::>(); + Ok(ClientResult::single(Box::new(FetchGroupRequestsEvent { + results, + }))) + } +} diff --git a/mania/src/core/event/system/fetch_user_info.rs b/mania/src/core/event/system/fetch_user_info.rs new file mode 100644 index 0000000..27b629c --- /dev/null +++ b/mania/src/core/event/system/fetch_user_info.rs @@ -0,0 +1,197 @@ +use crate::core::event::prelude::*; +use crate::core::protos::service::oidb::{ + Avatar, Business, CustomStatus, OidbSvcTrpcTcp0xFe12, OidbSvcTrpcTcp0xFe12key, + OidbSvcTrpcTcp0xFe12response, OidbSvcTrpcTcp0xFe12responseBody, + OidbSvcTrpcTcp0xFe12responseProperty, OidbSvcTrpcTcp0xFe12uin, business_list, +}; +use crate::entity::bot_user_info::{BotStatus, BotUserInfo, BusinessCustom, GenderInfo}; +use chrono::{DateTime, TimeZone, Utc}; +use std::time::{Duration, UNIX_EPOCH}; + +#[oidb_command(0xfe1, 2)] +#[derive(Debug, ServerEvent, Default)] +pub struct FetchUserInfoEvent { + pub user_info: BotUserInfo, + pub uin: u32, + pub uid: Option, +} + +static KEYS: &[u32] = &[ + 20002, 27394, 20009, 20031, 101, 103, 102, 20022, 20023, 20024, 24002, 27037, 27049, 20011, + 20016, 20021, 20003, 20004, 20005, 20006, 20020, 20026, 24007, 104, 105, 42432, 42362, 41756, + 41757, 42257, 27372, 42315, 107, 45160, 45161, 27406, 62026, 20037, +]; + +impl ClientEvent for FetchUserInfoEvent { + fn build(&self, _: &Context) -> CEBuildResult { + let keys: Vec = KEYS + .iter() + .map(|&k| OidbSvcTrpcTcp0xFe12key { key: k }) + .collect(); + if let Some(uid) = &self.uid { + let request = OidbSvcTrpcTcp0xFe12 { + uid: Some(uid.to_owned()), + field2: 0, + keys, + }; + Ok(OidbPacket::new(0xfe1, 2, request.encode_to_vec(), false, false).to_binary()) + } else { + let request = OidbSvcTrpcTcp0xFe12uin { + uin: self.uin, + field2: 0, + keys, + }; + Ok(OidbPacket::new(0xfe1, 2, request.encode_to_vec(), false, true).to_binary()) + } + } + + fn parse(packet: Bytes, _: &Context) -> CEParseResult { + let response = OidbPacket::parse_into::(packet)?; + let body: OidbSvcTrpcTcp0xFe12responseBody = response + .body + .ok_or_else(|| EventError::OtherError("Missing response body".into()))?; + let properties: OidbSvcTrpcTcp0xFe12responseProperty = body + .properties + .ok_or_else(|| EventError::OtherError("Missing properties".into()))?; + + let bytes_props: HashMap> = properties + .bytes_properties + .into_iter() + .map(|prop| (prop.code, prop.value.to_vec())) + .collect(); + let number_props: HashMap = properties + .number_properties + .into_iter() + .map(|prop| (prop.number1, prop.number2)) + .collect(); + + let get_birthday = |birthday: &str| -> DateTime { + let bytes = Bytes::from(birthday.to_owned()); + let mut reader = PacketReader::new(bytes); + let year = reader.u16(); + let month = reader.u8(); + let day = reader.u8(); + Utc.with_ymd_and_hms(year as i32, month as u32, day as u32, 0, 0, 0) + .single() + .unwrap_or_else(|| Utc.with_ymd_and_hms(1970, 1, 1, 0, 0, 0).single().unwrap()) + }; + + let birthday = bytes_props + .get(&20031) + .and_then(|v| String::from_utf8(v.to_owned()).ok()) + .map(|s| get_birthday(&s)) + .unwrap_or_default(); + + let reg_secs = *number_props.get(&20026).unwrap_or(&0) as u64; + let register_time = DateTime::::from(UNIX_EPOCH + Duration::from_secs(reg_secs)); + + let qid = bytes_props + .get(&27394) + .and_then(|v| String::from_utf8(v.to_owned()).ok()); + + let mut status_id = number_props.get(&27372).copied().unwrap_or(0); + if status_id > 268435455 { + status_id -= 268435456; + } + + let customs = bytes_props.get(&27406).and_then(|bytes| { + if !bytes.is_empty() { + CustomStatus::decode(&bytes[..]).ok() + } else { + None + } + }); + + let avatars = bytes_props + .get(&101) + .and_then(|bytes| Avatar::decode(&bytes[..]).ok()) + .unwrap_or_default(); + + let business_a = bytes_props + .get(&107) + .and_then(|bytes| Business::decode(&bytes[..]).ok()) + .and_then(|business| { + business.body.map(|body| { + body.lists + .into_iter() + .map(|b| BusinessCustom { + bus_type: b.r#type, + level: b.level, + icon: match &b.icon { + Some(business_list::Icon::Icon1(s)) if !s.is_empty() => { + Some(s.to_owned()) + } + Some(business_list::Icon::Icon2(s)) if !s.is_empty() => { + Some(s.to_owned()) + } + _ => None, + }, + is_pro: b.is_pro as u32, + is_year: b.is_year as u32, + }) + .collect() + }) + }) + .unwrap_or_default(); + + let nickname = bytes_props + .get(&20002) + .cloned() + .and_then(|v| String::from_utf8(v).ok()) + .unwrap_or_default(); + let city = bytes_props + .get(&20020) + .cloned() + .and_then(|v| String::from_utf8(v).ok()) + .unwrap_or_default(); + let country = bytes_props + .get(&20003) + .cloned() + .and_then(|v| String::from_utf8(v).ok()) + .unwrap_or_default(); + let school = bytes_props + .get(&20021) + .cloned() + .and_then(|v| String::from_utf8(v).ok()) + .unwrap_or_default(); + let sign = bytes_props + .get(&102) + .cloned() + .and_then(|v| String::from_utf8(v).ok()) + .unwrap_or_default(); + + let gender = match *number_props.get(&20009).unwrap_or(&0) { + 1 => GenderInfo::Male, + 2 => GenderInfo::Female, + _ => GenderInfo::Unset, + }; + + let user_info = BotUserInfo { + uin: body.uin, + nickname, + avatar: format!("{}640", avatars.url.unwrap_or_default()), + birthday, + city, + country, + school, + age: *number_props.get(&20037).unwrap_or(&0), + register_time, + gender, + qid, + level: *number_props.get(&105).unwrap_or(&0), + sign, + status: BotStatus { + status_id, + face_id: customs.as_ref().map(|c| c.face_id), + msg: customs.and_then(|c| c.msg), + }, + business: business_a, + }; + + Ok(ClientResult::single(Box::new(FetchUserInfoEvent { + user_info, + uin: body.uin, + uid: None, + }))) + } +} diff --git a/mania/src/core/event/system/mod.rs b/mania/src/core/event/system/mod.rs index 270c26b..06ad67b 100644 --- a/mania/src/core/event/system/mod.rs +++ b/mania/src/core/event/system/mod.rs @@ -1,7 +1,10 @@ pub mod alive; +pub mod fetch_filtered_group_request; pub mod fetch_friend; +pub mod fetch_group_requests; pub mod fetch_members; pub mod fetch_rkey; +pub mod fetch_user_info; pub mod info_sync; pub mod kick_nt; pub mod nt_sso_alive; diff --git a/mania/src/core/mod.rs b/mania/src/core/mod.rs index 22670ba..66040b5 100644 --- a/mania/src/core/mod.rs +++ b/mania/src/core/mod.rs @@ -3,6 +3,7 @@ pub mod cache; pub mod connect; pub mod context; pub mod crypto; +pub mod entity; pub mod error; pub mod event; pub mod http; diff --git a/mania/src/core/operation/cache_op.rs b/mania/src/core/operation/cache_op.rs index 6e9f202..edd5267 100644 --- a/mania/src/core/operation/cache_op.rs +++ b/mania/src/core/operation/cache_op.rs @@ -1,98 +1,296 @@ use crate::core::business::BusinessHandle; +use crate::core::cache::CacheMode; use crate::core::event::downcast_mut_major_event; use crate::core::event::system::fetch_friend::FetchFriendsEvent; use crate::core::event::system::fetch_members::FetchMembersEvent; use crate::entity::bot_friend::{BotFriend, BotFriendGroup}; use crate::entity::bot_group_member::BotGroupMember; -use crate::{ManiaResult, dda}; +use crate::{ManiaError, ManiaResult, dda}; use std::borrow::Cow; use std::collections::HashMap; use std::sync::Arc; impl BusinessHandle { - pub async fn resolve_uid( + pub async fn uin2uid( self: &Arc, + uin: u32, group_uin: Option, - friend_uin: u32, ) -> ManiaResult { - if self.cache.uin2uid.is_empty() { - self.resolve_friends_uid_and_friend_groups().await?; + match self.cache.cache_mode { + CacheMode::Full | CacheMode::Half => { + if self.cache.cache_mode == CacheMode::Full + && self.cache.uin2uid.as_ref().unwrap().is_empty() + { + self.refresh_friends_cache().await?; + } + if let Some(group_uin) = group_uin + && !self + .cache + .cached_group_members + .as_ref() + .unwrap() + .contains_key(&group_uin) + { + self.refresh_group_members_cache(group_uin).await?; + } + self.resolve_uin2uid_within_cache(uin, group_uin).await + } + CacheMode::None => { + if let Some(group_uin) = group_uin { + self.fast_fetch_group_members_uid(uin, group_uin).await + } else { + self.fast_fetch_friends_uid(uin).await + } + } } - if let Some(uin) = group_uin - && !self.cache.cached_group_members.contains_key(&uin) - { - self.resolve_members_uid(uin).await?; + } + + pub async fn uid2uin(self: &Arc, uid: &str, group_uin: Option) -> ManiaResult { + match self.cache.cache_mode { + CacheMode::Full | CacheMode::Half => { + if self.cache.cache_mode == CacheMode::Full + && self.cache.uid2uin.as_ref().unwrap().is_empty() + { + self.refresh_friends_cache().await?; + } + if let Some(group_uin) = group_uin + && !self + .cache + .cached_group_members + .as_ref() + .unwrap() + .contains_key(&group_uin) + { + self.refresh_group_members_cache(group_uin).await?; + } + self.resolve_uid2uin_within_cache(uid, group_uin).await + } + CacheMode::None => { + if let Some(group_uin) = group_uin { + self.fast_fetch_group_members_uin(uid, group_uin).await + } else { + self.fast_fetch_friends_uin(uid).await + } + } + } + } + + async fn resolve_uin2uid_within_cache( + self: &Arc, + uin: u32, + group_uin: Option, + ) -> ManiaResult { + if let Some(uid) = self.cache.uin2uid.as_ref().and_then(|m| m.get(&uin)) { + return Ok(uid.value().to_string()); + } + if let Some(group_id) = group_uin { + let group_members = self + .cache + .cached_group_members + .as_ref() + .unwrap() + .get(&group_id) + .ok_or_else(|| ManiaError::GenericError(Cow::from("Group not found")))?; + group_members + .iter() + .find(|member| member.uin == uin) + .map(|member| member.uid.clone()) + .ok_or_else(|| ManiaError::GenericError(Cow::from("Member not found"))) + } else { + let friend = self + .cache + .cached_friends + .as_ref() + .unwrap() + .get(&uin) + .ok_or_else(|| ManiaError::GenericError(Cow::from("Friend not found")))?; + Ok(friend.value().uid.clone()) + } + } + + async fn resolve_uid2uin_within_cache( + self: &Arc, + uid: &str, + group_uin: Option, + ) -> ManiaResult { + if let Some(uin) = self.cache.uid2uin.as_ref().and_then(|m| m.get(uid)) { + return Ok(*uin.value()); + } + if let Some(group_id) = group_uin { + let group_members = self + .cache + .cached_group_members + .as_ref() + .unwrap() + .get(&group_id) + .ok_or_else(|| ManiaError::GenericError(Cow::from("Group not found")))?; + group_members + .iter() + .find(|member| member.uid == uid) + .map(|member| member.uin) + .ok_or_else(|| ManiaError::GenericError(Cow::from("Member not found"))) + } else { + let friend = self + .cache + .cached_friends + .as_ref() + .unwrap() + .iter() + .find(|entry| entry.value().uid == uid) + .ok_or_else(|| ManiaError::GenericError(Cow::from("Friend not found")))?; + Ok(*friend.key()) } - self.cache - .uin2uid - .get(&friend_uin) - .map(|uid_ref| uid_ref.value().clone()) - .ok_or_else(|| crate::ManiaError::GenericError(Cow::from("Uin not found"))) } - async fn resolve_friends_uid_and_friend_groups(self: &Arc) -> ManiaResult<()> { + async fn iter_fetch_friends(self: &Arc, mut process: F) -> ManiaResult> + where + F: FnMut(&mut FetchFriendsEvent) -> ManiaResult>, + { let mut next_uin: Option = None; - let mut friends: Vec = Vec::new(); - let mut friend_groups: HashMap = HashMap::new(); loop { let mut event = dda!(FetchFriendsEvent { next_uin }); let mut result = self.send_event(&mut event).await?; let event: &mut FetchFriendsEvent = downcast_mut_major_event(&mut result) - .ok_or_else(|| crate::ManiaError::GenericError("Downcast error".into()))?; - match event.next_uin { - Some(uin) => { - friend_groups.extend(event.friend_groups.to_owned()); - for friend in event.friends.iter_mut() { - let group_id = friend - .group - .as_ref() - .ok_or_else(|| { - crate::ManiaError::GenericError(Cow::from("Missing group id")) - })? - .group_id; - if let Some(name) = friend_groups.get(&group_id) { - friend.group = Some(BotFriendGroup { - group_id, - group_name: name.to_owned(), - }); - } - } - friends.extend(event.friends.to_owned()); - next_uin = Some(uin); + .ok_or_else(|| ManiaError::GenericError("Downcast error".into()))?; + if let Some(val) = process(event)? { + return Ok(Some(val)); + } + if let Some(n) = event.next_uin { + next_uin = Some(n); + } else { + break; + } + } + Ok(None::) + } + + async fn refresh_friends_cache(self: &Arc) -> ManiaResult<()> { + let mut friends: HashMap = HashMap::new(); + let mut friend_groups: HashMap = HashMap::new(); + self.iter_fetch_friends(|event: &mut FetchFriendsEvent| { + friend_groups.extend(event.friend_groups.to_owned()); + for friend in event.friends.iter_mut() { + let group_id = friend + .group + .as_ref() + .ok_or_else(|| ManiaError::GenericError(Cow::from("Missing group id")))? + .group_id; + if let Some(name) = friend_groups.get(&group_id) { + friend.group = Some(BotFriendGroup { + group_id, + group_name: name.clone(), + }); } - None => break, + friends.insert(friend.uin, friend.to_owned()); + self.cache.insert_uin_uid(friend.uin, friend.uid.clone()); } + Ok(None::<()>) + }) + .await?; + let cached_friends = self.cache.cached_friends.as_ref().unwrap(); + cached_friends.clear(); + for (uin, friend) in friends.iter() { + cached_friends.insert(*uin, friend.to_owned()); } - friends.iter().for_each(|bf| { - self.cache.uin2uid.insert(bf.uin, bf.uid.clone()); - }); - self.cache.cached_friends.write().await.clear(); - self.cache.cached_friends.write().await.extend(friends); Ok(()) } - async fn resolve_members_uid(self: &Arc, group_uin: u32) -> ManiaResult<()> { - let mut group_members: Vec = Vec::new(); + async fn fast_fetch_friends_uid(self: &Arc, uin: u32) -> ManiaResult { + let res = self + .iter_fetch_friends(|event| { + Ok(event + .friends + .iter() + .find(|f| f.uin == uin) + .map(|f| f.uid.clone())) + }) + .await?; + res.ok_or_else(|| ManiaError::GenericError(Cow::from("Friend not found"))) + } + + async fn fast_fetch_friends_uin(self: &Arc, uid: &str) -> ManiaResult { + let res = self + .iter_fetch_friends(|event| { + Ok(event.friends.iter().find(|f| f.uid == uid).map(|f| f.uin)) + }) + .await?; + res.ok_or_else(|| ManiaError::GenericError(Cow::from("Friend not found"))) + } + async fn iter_fetch_group( + self: &Arc, + group_uin: u32, + mut process: F, + ) -> ManiaResult> + where + F: FnMut(&mut FetchMembersEvent) -> ManiaResult>, + { let mut token: Option = None; loop { let mut event = dda!(FetchMembersEvent { group_uin, token }); let mut result = self.send_event(&mut event).await?; let event: &mut FetchMembersEvent = downcast_mut_major_event(&mut result) - .ok_or_else(|| crate::ManiaError::GenericError("Downcast error".into()))?; - match event.token.as_ref() { - Some(t) => { - group_members.extend(event.group_members.to_owned()); - token = Some(t.to_owned()); - } - None => break, + .ok_or_else(|| ManiaError::GenericError("Downcast error".into()))?; + if let Some(val) = process(event)? { + return Ok(Some(val)); + } + if let Some(t) = event.token.as_ref() { + token = Some(t.to_owned()); + } else { + break; } } + Ok(None::) + } + + async fn refresh_group_members_cache(self: &Arc, group_uin: u32) -> ManiaResult<()> { + let mut group_members: Vec = Vec::new(); + self.iter_fetch_group(group_uin, |event| { + group_members.extend(event.group_members.clone()); + Ok(None::<()>) + }) + .await?; group_members.iter().for_each(|bgm| { - self.cache.uin2uid.insert(bgm.uin, bgm.uid.clone()); + self.cache.insert_uin_uid(bgm.uin, bgm.uid.clone()); }); self.cache .cached_group_members + .as_ref() + .unwrap() .insert(group_uin, group_members); Ok(()) } + + async fn fast_fetch_group_members_uid( + self: &Arc, + uin: u32, + group_uin: u32, + ) -> ManiaResult { + let res = self + .iter_fetch_group(group_uin, |event| { + Ok(event + .group_members + .iter() + .find(|m| m.uin == uin) + .map(|m| m.uid.clone())) + }) + .await?; + res.ok_or_else(|| ManiaError::GenericError(Cow::from("Member not found"))) + } + + async fn fast_fetch_group_members_uin( + self: &Arc, + uid: &str, + group_uin: u32, + ) -> ManiaResult { + let res = self + .iter_fetch_group(group_uin, |event| { + Ok(event + .group_members + .iter() + .find(|m| m.uid == uid) + .map(|m| m.uin)) + }) + .await?; + res.ok_or_else(|| ManiaError::GenericError(Cow::from("Member not found"))) + } } diff --git a/mania/src/core/operation/common_op.rs b/mania/src/core/operation/common_op.rs index 9406d4f..b1072a0 100644 --- a/mania/src/core/operation/common_op.rs +++ b/mania/src/core/operation/common_op.rs @@ -8,12 +8,18 @@ use crate::core::event::message::record_c2c_download::RecordC2CDownloadEvent; use crate::core::event::message::record_group_download::RecordGroupDownloadEvent; use crate::core::event::message::video_c2c_download::VideoC2CDownloadEvent; use crate::core::event::message::video_group_download::VideoGroupDownloadEvent; +use crate::core::event::system::fetch_filtered_group_request::FetchFilteredGroupRequestsEvent; +use crate::core::event::system::fetch_group_requests::FetchGroupRequestsEvent; use crate::core::event::system::fetch_rkey::FetchRKeyEvent; +use crate::core::event::system::fetch_user_info::FetchUserInfoEvent; use crate::core::event::{downcast_major_event, downcast_mut_major_event}; use crate::core::protos::service::oidb::IndexNode; +use crate::entity::bot_group_request::BotGroupRequest; use crate::message::chain::MessageChain; use crate::{ManiaError, ManiaResult, dda}; +use futures::future::join_all; use std::sync::Arc; +use tokio::join; impl BusinessHandle { pub async fn fetch_rkey(self: &Arc) -> ManiaResult<()> { @@ -238,4 +244,62 @@ impl BusinessHandle { downcast_major_event(&res).ok_or(ManiaError::InternalEventDowncastError)?; Ok(event.video_url.clone()) } + + pub(crate) async fn resolve_stranger_uid2uin( + self: &Arc, + stranger_uid: &str, + ) -> ManiaResult { + let mut fetch_event = dda!(FetchUserInfoEvent { + uid: Some(stranger_uid.to_string()), + }); + let res = self.send_event(&mut fetch_event).await?; + let event: &FetchUserInfoEvent = + downcast_major_event(&res).ok_or(ManiaError::InternalEventDowncastError)?; + Ok(event.uin) + } + + pub async fn fetch_group_requests(self: &Arc) -> ManiaResult> { + let mut fetch_event = FetchGroupRequestsEvent::default(); + let res = self.send_event(&mut fetch_event).await?; + let fetch_event: &FetchGroupRequestsEvent = + downcast_major_event(&res).ok_or(ManiaError::InternalEventDowncastError)?; + let mut fetch_filter_event = FetchFilteredGroupRequestsEvent::default(); + let res = self.send_event(&mut fetch_filter_event).await?; + let fetch_filter_event: &FetchFilteredGroupRequestsEvent = + downcast_major_event(&res).ok_or(ManiaError::InternalEventDowncastError)?; + + let all_requests: Vec<_> = fetch_event + .results + .iter() + .chain(fetch_filter_event.results.iter()) + .collect(); + + let requests = join_all(all_requests.into_iter().map(|req| { + let this = Arc::clone(self); + async move { + let (invitor_member_uin, target_member_uin, operator_uin) = join!( + this.resolve_stranger_uid2uin(req.invitor_member_uid.as_deref().unwrap_or("")), + this.resolve_stranger_uid2uin(&req.target_member_uid), + this.resolve_stranger_uid2uin(req.operator_uid.as_deref().unwrap_or("")) + ); + BotGroupRequest { + group_uin: req.group_uin, + invitor_member_uin: invitor_member_uin.ok(), + invitor_member_card: req.invitor_member_card.to_owned(), + target_member_uin: target_member_uin.unwrap_or_default(), + target_member_card: req.target_member_card.to_owned(), + operator_uin: operator_uin.ok(), + operator_name: req.operator_name.to_owned(), + sequence: req.sequence, + state: req.state, + event_type: req.event_type, + comment: req.comment.to_owned(), + is_filtered: req.is_filtered, + } + } + })) + .await; + + Ok(requests) + } } diff --git a/mania/src/core/operation/wt_op.rs b/mania/src/core/operation/wt_op.rs index 20d2947..5fb7eab 100644 --- a/mania/src/core/operation/wt_op.rs +++ b/mania/src/core/operation/wt_op.rs @@ -207,7 +207,7 @@ impl BusinessHandle { loop { tokio::select! { _ = hb_interval.tick() => { - if let Err(e) = handle.push_event(&AliveEvent { test: 1 }).await { + if let Err(e) = handle.push_event(&AliveEvent).await { tracing::error!("Failed to send Alive event: {:?}", e); } } diff --git a/mania/src/core/protos/service/oidb/OidbSvcTrpcTcp0xFE1_2.proto b/mania/src/core/protos/service/oidb/OidbSvcTrpcTcp0xFE1_2.proto index ebbf55c..9d07fcd 100644 --- a/mania/src/core/protos/service/oidb/OidbSvcTrpcTcp0xFE1_2.proto +++ b/mania/src/core/protos/service/oidb/OidbSvcTrpcTcp0xFE1_2.proto @@ -33,7 +33,7 @@ message OidbSvcTrpcTcp0xFE1_2ResponseBody { message OidbSvcTrpcTcp0xFE1_2ResponseProperty { repeated OidbTwoNumber NumberProperties = 1; - repeated OidbFriendProperty StringProperties = 2; + repeated OidbFriendByteProperty BytesProperties = 2; } message CustomStatus { diff --git a/mania/src/core/protos/service/oidb/generics.proto b/mania/src/core/protos/service/oidb/generics.proto index 05f4338..77985f7 100644 --- a/mania/src/core/protos/service/oidb/generics.proto +++ b/mania/src/core/protos/service/oidb/generics.proto @@ -23,6 +23,11 @@ message OidbFriendProperty { string Value = 2; } +message OidbFriendByteProperty { + uint32 Code = 1; + bytes Value = 2; +} + message OidbLafter { int32 Type = 1; bytes D2 = 2; diff --git a/mania/src/core/socket.rs b/mania/src/core/socket.rs index 5282749..4b0104d 100644 --- a/mania/src/core/socket.rs +++ b/mania/src/core/socket.rs @@ -39,7 +39,7 @@ impl PacketReceiver { "socket closed", )); } - Ok(self.packets_rx.recv().await.expect("channel closed")) + Ok(self.packets_rx.recv().await.expect("channel closed")) // TODO: ERROR mania::core::socket: Socket error: An established connection was aborted by the software in your host machine. (os error 10053) } } diff --git a/mania/src/entity/bot_group_request.rs b/mania/src/entity/bot_group_request.rs new file mode 100644 index 0000000..09a32aa --- /dev/null +++ b/mania/src/entity/bot_group_request.rs @@ -0,0 +1,15 @@ +#[derive(Debug, Default)] +pub struct BotGroupRequest { + pub group_uin: u32, + pub invitor_member_uin: Option, + pub invitor_member_card: Option, + pub target_member_uin: u32, + pub target_member_card: String, + pub operator_uin: Option, + pub operator_name: Option, + pub sequence: u64, + pub state: u32, + pub event_type: u32, + pub comment: Option, + pub is_filtered: bool, +} diff --git a/mania/src/entity/bot_user_info.rs b/mania/src/entity/bot_user_info.rs new file mode 100644 index 0000000..120cc99 --- /dev/null +++ b/mania/src/entity/bot_user_info.rs @@ -0,0 +1,51 @@ +use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Debug, Default)] +pub struct BotUserInfo { + pub uin: u32, + pub avatar: String, + pub nickname: String, + pub birthday: DateTime, + pub city: String, + pub country: String, + pub school: String, + pub age: u32, + pub register_time: DateTime, + pub gender: GenderInfo, + pub qid: Option, + pub level: u32, + pub sign: String, + pub status: BotStatus, + pub business: Vec, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct BusinessCustom { + pub bus_type: u32, + pub level: u32, + pub icon: Option, + pub is_year: u32, + pub is_pro: u32, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct BusinessCustomList { + pub business_lists: Vec, +} + +#[derive(Serialize, Deserialize, Debug, Default)] +pub enum GenderInfo { + #[default] + Unset = 0, + Male = 1, + Female = 2, + Unknown = 255, +} + +#[derive(Serialize, Deserialize, Debug, Default)] +pub struct BotStatus { + pub status_id: u32, + pub face_id: Option, + pub msg: Option, +} diff --git a/mania/src/entity/mod.rs b/mania/src/entity/mod.rs index 4cc31da..e893020 100644 --- a/mania/src/entity/mod.rs +++ b/mania/src/entity/mod.rs @@ -1,3 +1,5 @@ pub mod bot_friend; pub mod bot_group_member; +pub mod bot_group_request; +pub mod bot_user_info; pub mod sys_face; diff --git a/mania/src/lib.rs b/mania/src/lib.rs index 99d757d..3b74bce 100644 --- a/mania/src/lib.rs +++ b/mania/src/lib.rs @@ -1,4 +1,3 @@ -#![forbid(unsafe_code)] #![allow(dead_code)] // TODO: remove this after stable #![feature(if_let_guard)] #![feature(let_chains)] @@ -10,7 +9,7 @@ pub mod message; pub mod utility; use crate::core::business::{Business, BusinessHandle}; -use crate::core::connect::optimum_server; +pub use crate::core::cache::CacheMode; use crate::core::context::Protocol; pub use crate::core::context::{AppInfo, Context, DeviceInfo}; pub use crate::core::error::{ManiaError, ManiaResult}; @@ -32,10 +31,12 @@ pub struct ClientConfig { pub get_optimum_server: bool, /// Custom Sign Provider pub sign_provider: Option>, - /// The maximum size of the highway block in byte, max 1MB (1024 * 1024 byte) + /// The maximum size of the highway block in byte, max 1MB (1024 * 1024 byte) pub highway_chuck_size: usize, /// Highway uploading concurrency, if the image failed to send, set this to 1 pub highway_concurrency: usize, + /// Cache mode for the client + pub cache_mode: CacheMode, } impl Default for ClientConfig { @@ -48,6 +49,7 @@ impl Default for ClientConfig { sign_provider: None, highway_chuck_size: 1024 * 1024, highway_concurrency: 4, + cache_mode: CacheMode::Full, } } } @@ -59,16 +61,18 @@ pub struct Client { impl Client { pub async fn new( - config: ClientConfig, + mut config: ClientConfig, device: DeviceInfo, key_store: KeyStore, ) -> ManiaResult { + let sign_provider = config.sign_provider.take(); + let config = Arc::new(config); let app_info = AppInfo::get(config.protocol); let context = Context { app_info, device, key_store, - sign_provider: config.sign_provider.unwrap_or_else(|| { + sign_provider: sign_provider.unwrap_or_else(|| { default_sign_provider( config.protocol, env::var("MANIA_LINUX_SIGN_URL") @@ -84,10 +88,7 @@ impl Client { session: Session::new(), }; let context = Arc::new(context); - - let addr = optimum_server(config.get_optimum_server, config.use_ipv6_network).await?; - let business = Business::new(addr, context.clone()).await?; - + let business = Business::new(config, context.clone()).await?; let handle = ClientHandle { business: business.handle(), context,