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