Initial commit: split VNOX monorepo into VNOX-Client
Desktop client with egui + wgpu UI, audio pipeline (cpal/opus/rodio), networking (LNEx protocol), and identity management with encrypted vault. Jitter buffer module extracted from voice-node for standalone operation.
This commit is contained in:
commit
bf0b237afd
112 changed files with 19113 additions and 0 deletions
162
src/net/internal/dispatch/channel.rs
Normal file
162
src/net/internal/dispatch/channel.rs
Normal file
|
|
@ -0,0 +1,162 @@
|
|||
use anyhow::{Result, anyhow};
|
||||
use serde_json;
|
||||
use tokio::sync::mpsc;
|
||||
|
||||
use super::super::super::payloads::*;
|
||||
use super::super::super::types::NetEvent;
|
||||
use super::super::super::types::{ChannelListItem as InfoChannelListItem, ChatMsg, MemberInfo};
|
||||
|
||||
pub async fn handle(pid: u16, payload: &[u8], tx: &mpsc::Sender<NetEvent>) -> Result<()> {
|
||||
match pid {
|
||||
PID_CHANNEL_STATE => {
|
||||
let p: ChannelStatePayload = serde_json::from_slice(payload)?;
|
||||
let _ = tx
|
||||
.send(NetEvent::ChannelState {
|
||||
channel_id: p.channel_id,
|
||||
channel_name: p.channel_name,
|
||||
kind: p.kind,
|
||||
members: p
|
||||
.members
|
||||
.into_iter()
|
||||
.map(|m| MemberInfo {
|
||||
user_id: m.user_id,
|
||||
nickname: m.nickname,
|
||||
in_voice: m.in_voice,
|
||||
})
|
||||
.collect(),
|
||||
voice_endpoint: p.voice_endpoint,
|
||||
})
|
||||
.await;
|
||||
}
|
||||
PID_CHANNEL_CREATE => {
|
||||
let p: ChannelCreatePayload = serde_json::from_slice(payload)?;
|
||||
let _ = tx
|
||||
.send(NetEvent::ChannelCreated {
|
||||
channel_id: p.channel_id,
|
||||
channel_name: p.channel_name,
|
||||
kind: p.kind,
|
||||
})
|
||||
.await;
|
||||
}
|
||||
PID_CHANNEL_DELETE => {
|
||||
let p: ChannelDeletePayload = serde_json::from_slice(payload)?;
|
||||
let _ = tx
|
||||
.send(NetEvent::ChannelDeleted {
|
||||
channel_id: p.channel_id,
|
||||
})
|
||||
.await;
|
||||
}
|
||||
PID_CHANNEL_LIST => {
|
||||
let p: ChannelListPayload = serde_json::from_slice(payload)?;
|
||||
let _ = tx
|
||||
.send(NetEvent::ChannelListEvent {
|
||||
channels: p
|
||||
.channels
|
||||
.into_iter()
|
||||
.map(|c| InfoChannelListItem {
|
||||
channel_id: c.channel_id,
|
||||
channel_name: c.channel_name,
|
||||
kind: c.kind,
|
||||
})
|
||||
.collect(),
|
||||
})
|
||||
.await;
|
||||
}
|
||||
PID_CHAT_MESSAGE => {
|
||||
let p: ChatMessagePayload = serde_json::from_slice(payload)?;
|
||||
let _ = tx
|
||||
.send(NetEvent::ChatMessage {
|
||||
message_id: p.message_id,
|
||||
channel_id: p.channel_id,
|
||||
sender_id: p.sender_id.clone(),
|
||||
nickname: p.sender_id,
|
||||
content: p.content,
|
||||
timestamp: p.timestamp,
|
||||
reply_to: p.reply_to,
|
||||
})
|
||||
.await;
|
||||
}
|
||||
PID_CHAT_HISTORY => {
|
||||
let p: ChatHistoryPayload = serde_json::from_slice(payload)?;
|
||||
let _ = tx
|
||||
.send(NetEvent::ChatHistory {
|
||||
channel_id: p.channel_id,
|
||||
messages: p
|
||||
.messages
|
||||
.into_iter()
|
||||
.map(|m| ChatMsg {
|
||||
message_id: m.message_id,
|
||||
sender_id: m.sender_id,
|
||||
content: m.content,
|
||||
timestamp: m.timestamp,
|
||||
reply_to: m.reply_to,
|
||||
})
|
||||
.collect(),
|
||||
})
|
||||
.await;
|
||||
}
|
||||
PID_USER_JOIN => {
|
||||
let p: UserJoinPayload = serde_json::from_slice(payload)?;
|
||||
let _ = tx
|
||||
.send(NetEvent::UserJoin {
|
||||
channel_id: p.channel_id,
|
||||
user_id: p.user_id,
|
||||
nickname: p.nickname,
|
||||
})
|
||||
.await;
|
||||
}
|
||||
PID_USER_LEAVE => {
|
||||
let p: UserLeavePayload = serde_json::from_slice(payload)?;
|
||||
let _ = tx
|
||||
.send(NetEvent::UserLeave {
|
||||
channel_id: p.channel_id,
|
||||
user_id: p.user_id,
|
||||
})
|
||||
.await;
|
||||
}
|
||||
PID_PONG => {
|
||||
let p: PongPayload = serde_json::from_slice(payload)?;
|
||||
let now = std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.unwrap_or_default()
|
||||
.as_millis() as i64;
|
||||
let rtt_ms = (now - p.timestamp).max(0) as u32;
|
||||
let _ = tx.send(NetEvent::LatencyUpdate { rtt_ms }).await;
|
||||
}
|
||||
PID_READ_RECEIPT_BROADCAST => {
|
||||
let p: serde_json::Value = serde_json::from_slice(payload)?;
|
||||
let _ = tx
|
||||
.send(NetEvent::ReadReceiptBroadcast {
|
||||
channel_id: p["channel_id"].as_str().unwrap_or_default().into(),
|
||||
user_id: p["user_id"].as_str().unwrap_or_default().into(),
|
||||
last_read_message_id: p["last_read_message_id"]
|
||||
.as_str()
|
||||
.unwrap_or_default()
|
||||
.into(),
|
||||
})
|
||||
.await;
|
||||
}
|
||||
PID_ERROR => {
|
||||
let p: ErrorPayload = serde_json::from_slice(payload)?;
|
||||
let _ = tx
|
||||
.send(NetEvent::Error {
|
||||
code: p.code,
|
||||
message: p.message,
|
||||
})
|
||||
.await;
|
||||
}
|
||||
PID_DISCONNECT => return Err(anyhow!("server disconnected")),
|
||||
PID_TYPING_START => {
|
||||
let p: serde_json::Value = serde_json::from_slice(payload)?;
|
||||
let _ = tx
|
||||
.send(NetEvent::TypingStart {
|
||||
user_id: p["user_id"].as_str().unwrap_or_default().into(),
|
||||
nickname: p["nickname"].as_str().unwrap_or_default().into(),
|
||||
channel_id: p["channel_id"].as_str().unwrap_or_default().into(),
|
||||
})
|
||||
.await;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
152
src/net/internal/dispatch/dm.rs
Normal file
152
src/net/internal/dispatch/dm.rs
Normal file
|
|
@ -0,0 +1,152 @@
|
|||
use anyhow::Result;
|
||||
use serde_json;
|
||||
use tokio::sync::mpsc;
|
||||
use tracing::debug;
|
||||
|
||||
use super::super::super::payloads::*;
|
||||
use super::super::super::types::*;
|
||||
|
||||
pub async fn handle(pid: u16, payload: &[u8], tx: &mpsc::Sender<NetEvent>) -> Result<()> {
|
||||
match pid {
|
||||
PID_DM_START => {
|
||||
let p: DmStartResponsePayload = serde_json::from_slice(payload)?;
|
||||
let _ = tx
|
||||
.send(NetEvent::DmStart {
|
||||
dm_id: p.dm_id,
|
||||
other_user_id: p.other_user_id,
|
||||
other_nickname: p.other_nickname,
|
||||
messages: p
|
||||
.messages
|
||||
.into_iter()
|
||||
.map(|m| ChatMsg {
|
||||
message_id: String::new(),
|
||||
sender_id: m.sender_id,
|
||||
content: m.content,
|
||||
timestamp: m.timestamp,
|
||||
reply_to: None,
|
||||
})
|
||||
.collect(),
|
||||
unread_count: p.unread_count,
|
||||
})
|
||||
.await;
|
||||
}
|
||||
PID_DM_MESSAGE => {
|
||||
let p: DmMessagePayload = serde_json::from_slice(payload)?;
|
||||
let _ = tx
|
||||
.send(NetEvent::DmMessage {
|
||||
dm_id: p.dm_id,
|
||||
sender_id: p.sender_id,
|
||||
content: p.content,
|
||||
timestamp: p.timestamp,
|
||||
})
|
||||
.await;
|
||||
}
|
||||
PID_DM_HISTORY => {
|
||||
let p: DmHistoryPayload = serde_json::from_slice(payload)?;
|
||||
let _ = tx
|
||||
.send(NetEvent::DmHistory {
|
||||
dm_id: p.dm_id,
|
||||
messages: p
|
||||
.messages
|
||||
.into_iter()
|
||||
.map(|m| ChatMsg {
|
||||
message_id: String::new(),
|
||||
sender_id: m.sender_id,
|
||||
content: m.content,
|
||||
timestamp: m.timestamp,
|
||||
reply_to: None,
|
||||
})
|
||||
.collect(),
|
||||
})
|
||||
.await;
|
||||
}
|
||||
PID_FRIEND_LIST => {
|
||||
let p: FriendListPayload = serde_json::from_slice(payload)?;
|
||||
let _ = tx
|
||||
.send(NetEvent::FriendList {
|
||||
friends: p
|
||||
.friends
|
||||
.into_iter()
|
||||
.map(|f| FriendInfo {
|
||||
user_id: f.user_id,
|
||||
nickname: f.nickname,
|
||||
status: f.status,
|
||||
since: f.since,
|
||||
})
|
||||
.collect(),
|
||||
})
|
||||
.await;
|
||||
}
|
||||
PID_MESSAGE_REACTION_ADD => {
|
||||
let p: ReactionPayload = serde_json::from_slice(payload)?;
|
||||
let _ = tx
|
||||
.send(NetEvent::ReactionAdded {
|
||||
channel_id: p.channel_id,
|
||||
message_id: p.message_id,
|
||||
user_id: p.user_id,
|
||||
emoji: p.emoji,
|
||||
})
|
||||
.await;
|
||||
}
|
||||
PID_MESSAGE_REACTION_REMOVE => {
|
||||
let p: ReactionPayload = serde_json::from_slice(payload)?;
|
||||
let _ = tx
|
||||
.send(NetEvent::ReactionRemoved {
|
||||
channel_id: p.channel_id,
|
||||
message_id: p.message_id,
|
||||
user_id: p.user_id,
|
||||
emoji: p.emoji,
|
||||
})
|
||||
.await;
|
||||
}
|
||||
PID_MESSAGE_EDIT => {
|
||||
let p: ChatMessagePayload = serde_json::from_slice(payload)?;
|
||||
let _ = tx
|
||||
.send(NetEvent::MessageEdited {
|
||||
channel_id: p.channel_id,
|
||||
message_id: p.message_id,
|
||||
content: p.content,
|
||||
edited: p.edited,
|
||||
})
|
||||
.await;
|
||||
}
|
||||
PID_MESSAGE_DELETE => {
|
||||
let p: MessageDeletePayload = serde_json::from_slice(payload)?;
|
||||
let _ = tx
|
||||
.send(NetEvent::MessageDeleted {
|
||||
channel_id: p.channel_id,
|
||||
message_id: p.message_id,
|
||||
})
|
||||
.await;
|
||||
}
|
||||
PID_FRIEND_EVENT => {
|
||||
let p: FriendEventPayload = serde_json::from_slice(payload)?;
|
||||
match p.event_type.as_str() {
|
||||
"request" => {
|
||||
let _ = tx
|
||||
.send(NetEvent::FriendRequested {
|
||||
user_id: p.user_id,
|
||||
nickname: p.nickname.unwrap_or_default(),
|
||||
})
|
||||
.await;
|
||||
}
|
||||
"accepted" => {
|
||||
let _ = tx
|
||||
.send(NetEvent::FriendAccepted {
|
||||
user_id: p.user_id,
|
||||
nickname: p.nickname.unwrap_or_default(),
|
||||
})
|
||||
.await;
|
||||
}
|
||||
"removed" => {
|
||||
let _ = tx
|
||||
.send(NetEvent::FriendRemoved { user_id: p.user_id })
|
||||
.await;
|
||||
}
|
||||
_ => debug!("unhandled friend event: {}", p.event_type),
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
53
src/net/internal/dispatch/mod.rs
Normal file
53
src/net/internal/dispatch/mod.rs
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
use anyhow::Result;
|
||||
use tokio::sync::mpsc;
|
||||
use tracing::debug;
|
||||
|
||||
use super::super::payloads::*;
|
||||
use super::super::types::*;
|
||||
|
||||
mod channel;
|
||||
mod dm;
|
||||
mod social;
|
||||
|
||||
pub async fn incoming(pid: u16, payload: &[u8], tx: &mpsc::Sender<NetEvent>) -> Result<()> {
|
||||
match pid {
|
||||
PID_CHANNEL_STATE
|
||||
| PID_CHAT_MESSAGE
|
||||
| PID_CHAT_HISTORY
|
||||
| PID_USER_JOIN
|
||||
| PID_USER_LEAVE
|
||||
| PID_PONG
|
||||
| PID_READ_RECEIPT_BROADCAST
|
||||
| PID_ERROR
|
||||
| PID_DISCONNECT
|
||||
| PID_TYPING_START => channel::handle(pid, payload, tx).await?,
|
||||
|
||||
PID_DM_START
|
||||
| PID_DM_MESSAGE
|
||||
| PID_DM_HISTORY
|
||||
| PID_FRIEND_LIST
|
||||
| PID_MESSAGE_REACTION_ADD
|
||||
| PID_MESSAGE_REACTION_REMOVE
|
||||
| PID_MESSAGE_EDIT
|
||||
| PID_MESSAGE_DELETE
|
||||
| PID_FRIEND_EVENT => dm::handle(pid, payload, tx).await?,
|
||||
|
||||
PID_GUILD_LIST
|
||||
| PID_GUILD_CREATE
|
||||
| PID_GUILD_DELETE
|
||||
| PID_GUILD_MEMBER_JOIN
|
||||
| PID_GUILD_MEMBER_LEAVE
|
||||
| PID_GUILD_MEMBER_KICK
|
||||
| PID_INVITE_CREATE
|
||||
| PID_INVITE_ACCEPT
|
||||
| PID_INVITE_DELETE
|
||||
| PID_ROLE_CREATE
|
||||
| PID_ROLE_DELETE
|
||||
| PID_USER_ROLE_UPDATE
|
||||
| PID_PRESENCE_SYNC
|
||||
| PID_PRESENCE_EVENT => social::handle(pid, payload, tx).await?,
|
||||
|
||||
other => debug!("unhandled 0x{:04X}", other),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
232
src/net/internal/dispatch/social.rs
Normal file
232
src/net/internal/dispatch/social.rs
Normal file
|
|
@ -0,0 +1,232 @@
|
|||
use anyhow::Result;
|
||||
use serde_json;
|
||||
use tokio::sync::mpsc;
|
||||
|
||||
use super::super::super::payloads::*;
|
||||
use super::super::super::types::*;
|
||||
|
||||
pub async fn handle(pid: u16, payload: &[u8], tx: &mpsc::Sender<NetEvent>) -> Result<()> {
|
||||
match pid {
|
||||
PID_GUILD_LIST => {
|
||||
let p: GuildListPayload = serde_json::from_slice(payload)?;
|
||||
let _ = tx
|
||||
.send(NetEvent::GuildList {
|
||||
guilds: p
|
||||
.guilds
|
||||
.into_iter()
|
||||
.map(|g| GuildInfo {
|
||||
guild_id: g.guild_id,
|
||||
name: g.name,
|
||||
owner_id: g.owner_id,
|
||||
icon_url: g.icon_url,
|
||||
member_count: g.member_count,
|
||||
})
|
||||
.collect(),
|
||||
})
|
||||
.await;
|
||||
}
|
||||
PID_GUILD_CREATE => {
|
||||
let p: GuildInfoPayload = serde_json::from_slice(payload)?;
|
||||
let _ = tx
|
||||
.send(NetEvent::GuildCreated {
|
||||
guild_id: p.guild_id,
|
||||
name: p.name,
|
||||
owner_id: p.owner_id,
|
||||
})
|
||||
.await;
|
||||
}
|
||||
PID_GUILD_DELETE => {
|
||||
let p: GuildDeletePayload = serde_json::from_slice(payload)?;
|
||||
let _ = tx
|
||||
.send(NetEvent::GuildDeleted {
|
||||
guild_id: p.guild_id,
|
||||
})
|
||||
.await;
|
||||
}
|
||||
PID_GUILD_MEMBER_JOIN => {
|
||||
let p: serde_json::Value = serde_json::from_slice(payload)?;
|
||||
let _ = tx
|
||||
.send(NetEvent::GuildMemberJoined {
|
||||
guild_id: p["guild_id"].as_str().unwrap_or_default().into(),
|
||||
user_id: p["user_id"].as_str().unwrap_or_default().into(),
|
||||
nickname: p["nickname"].as_str().unwrap_or_default().into(),
|
||||
})
|
||||
.await;
|
||||
}
|
||||
PID_GUILD_MEMBER_LEAVE => {
|
||||
let p: serde_json::Value = serde_json::from_slice(payload)?;
|
||||
let _ = tx
|
||||
.send(NetEvent::GuildMemberLeft {
|
||||
guild_id: p["guild_id"].as_str().unwrap_or_default().into(),
|
||||
user_id: p["user_id"].as_str().unwrap_or_default().into(),
|
||||
})
|
||||
.await;
|
||||
}
|
||||
PID_USER_ROLE_UPDATE => {
|
||||
let p: UserRoleUpdatePayload = serde_json::from_slice(payload)?;
|
||||
let _ = tx
|
||||
.send(NetEvent::UserColor {
|
||||
user_id: p.user_id,
|
||||
color: p.color,
|
||||
})
|
||||
.await;
|
||||
}
|
||||
PID_PRESENCE_SYNC => {
|
||||
let p: PresenceSyncPayload = serde_json::from_slice(payload)?;
|
||||
let _ = tx
|
||||
.send(NetEvent::PresenceSync {
|
||||
presences: p
|
||||
.presences
|
||||
.into_iter()
|
||||
.map(|p| PresenceInfo {
|
||||
user_id: p.user_id,
|
||||
status: p.status,
|
||||
custom_status: p.custom_status,
|
||||
activity: p.activity,
|
||||
activity_text: p.activity_text,
|
||||
})
|
||||
.collect(),
|
||||
})
|
||||
.await;
|
||||
}
|
||||
PID_PRESENCE_EVENT => {
|
||||
let p: PresenceEventPayload = serde_json::from_slice(payload)?;
|
||||
let _ = tx
|
||||
.send(NetEvent::PresenceUpdated {
|
||||
user_id: p.user_id,
|
||||
status: p.status,
|
||||
custom_status: p.custom_status,
|
||||
activity: p.activity,
|
||||
})
|
||||
.await;
|
||||
}
|
||||
PID_GUILD_MEMBER_KICK => {
|
||||
let p: serde_json::Value = serde_json::from_slice(payload)?;
|
||||
let _ = tx
|
||||
.send(NetEvent::GuildMemberKicked {
|
||||
guild_id: p["guild_id"].as_str().unwrap_or_default().into(),
|
||||
user_id: p["user_id"].as_str().unwrap_or_default().into(),
|
||||
})
|
||||
.await;
|
||||
}
|
||||
PID_INVITE_CREATE => {
|
||||
let p: InviteInfoPayload = serde_json::from_slice(payload)?;
|
||||
let _ = tx
|
||||
.send(NetEvent::InviteCreated {
|
||||
id: p.id,
|
||||
guild_id: p.guild_id,
|
||||
guild_name: p.guild_name,
|
||||
code: p.code,
|
||||
creator_id: p.creator_id,
|
||||
max_uses: p.max_uses,
|
||||
uses: p.uses,
|
||||
expires_at: p.expires_at,
|
||||
created_at: p.created_at,
|
||||
})
|
||||
.await;
|
||||
}
|
||||
PID_INVITE_ACCEPT => {
|
||||
let p: serde_json::Value = serde_json::from_slice(payload)?;
|
||||
let _ = tx
|
||||
.send(NetEvent::InviteAccepted {
|
||||
guild_id: p["guild_id"].as_str().unwrap_or_default().into(),
|
||||
guild_name: p["guild_name"].as_str().unwrap_or_default().into(),
|
||||
})
|
||||
.await;
|
||||
}
|
||||
PID_INVITE_DELETE => {
|
||||
let p: InviteDeletePayload = serde_json::from_slice(payload)?;
|
||||
let _ = tx
|
||||
.send(NetEvent::InviteDeleted {
|
||||
guild_id: p.guild_id,
|
||||
invite_id: p.invite_id,
|
||||
})
|
||||
.await;
|
||||
}
|
||||
PID_ROLE_CREATE => {
|
||||
let p: serde_json::Value = serde_json::from_slice(payload)?;
|
||||
let _ = tx
|
||||
.send(NetEvent::RoleCreated {
|
||||
role_id: p["id"].as_str().unwrap_or_default().into(),
|
||||
guild_id: p["guild_id"].as_str().unwrap_or_default().into(),
|
||||
name: p["name"].as_str().unwrap_or_default().into(),
|
||||
})
|
||||
.await;
|
||||
}
|
||||
PID_ROLE_DELETE => {
|
||||
let p: RoleDeletePayload = serde_json::from_slice(payload)?;
|
||||
let _ = tx
|
||||
.send(NetEvent::RoleDeleted {
|
||||
role_id: p.role_id,
|
||||
guild_id: p.guild_id,
|
||||
})
|
||||
.await;
|
||||
}
|
||||
PID_GUILD_AUDIT_LOG => {
|
||||
let p: GuildAuditLogPayload = serde_json::from_slice(payload)?;
|
||||
let _ = tx
|
||||
.send(NetEvent::GuildAuditLog {
|
||||
guild_id: p.guild_id,
|
||||
entries: p.entries,
|
||||
})
|
||||
.await;
|
||||
}
|
||||
PID_GUILD_MEMBER_LIST => {
|
||||
let p: GuildMemberListPayload = serde_json::from_slice(payload)?;
|
||||
let _ = tx
|
||||
.send(NetEvent::GuildMemberList {
|
||||
guild_id: p.guild_id,
|
||||
members: p.members,
|
||||
})
|
||||
.await;
|
||||
}
|
||||
PID_GUILD_ROLE_LIST => {
|
||||
let p: GuildRoleListPayload = serde_json::from_slice(payload)?;
|
||||
let _ = tx
|
||||
.send(NetEvent::GuildRoleList {
|
||||
guild_id: p.guild_id,
|
||||
roles: p.roles,
|
||||
})
|
||||
.await;
|
||||
}
|
||||
PID_BLOCK_LIST => {
|
||||
// Server may send {"blocked": [...]} — try to parse properly first,
|
||||
// fall back to JSON value extraction.
|
||||
let blocked: Vec<String> = match serde_json::from_slice::<BlockListPayload>(payload) {
|
||||
Ok(p) => p.blocked,
|
||||
Err(_) => {
|
||||
let v: serde_json::Value = serde_json::from_slice(payload)?;
|
||||
v["blocked"]
|
||||
.as_array()
|
||||
.map(|arr| {
|
||||
arr.iter()
|
||||
.filter_map(|x| x.as_str().map(String::from))
|
||||
.collect()
|
||||
})
|
||||
.unwrap_or_default()
|
||||
}
|
||||
};
|
||||
let _ = tx.send(NetEvent::BlockList { blocked }).await;
|
||||
}
|
||||
PID_BLOCK_USER => {
|
||||
let v: serde_json::Value = serde_json::from_slice(payload).unwrap_or_default();
|
||||
let user_id = v["user_id"]
|
||||
.as_str()
|
||||
.or_else(|| v["blocked"].as_str())
|
||||
.unwrap_or_default()
|
||||
.to_string();
|
||||
let _ = tx.send(NetEvent::BlockedUser { user_id }).await;
|
||||
}
|
||||
PID_UNBLOCK_USER => {
|
||||
let v: serde_json::Value = serde_json::from_slice(payload).unwrap_or_default();
|
||||
let user_id = v["user_id"]
|
||||
.as_str()
|
||||
.or_else(|| v["unblocked"].as_str())
|
||||
.unwrap_or_default()
|
||||
.to_string();
|
||||
let _ = tx.send(NetEvent::UnblockedUser { user_id }).await;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
1
src/net/internal/mod.rs
Normal file
1
src/net/internal/mod.rs
Normal file
|
|
@ -0,0 +1 @@
|
|||
pub(super) mod dispatch;
|
||||
Loading…
Add table
Add a link
Reference in a new issue