ci: revert FORCE_JAVASCRIPT_ACTIONS_TO_NODE24, let v4 actions run on Node.js 20
This commit is contained in:
@@ -10,7 +10,6 @@ on:
|
|||||||
|
|
||||||
env:
|
env:
|
||||||
CARGO_TERM_COLOR: always
|
CARGO_TERM_COLOR: always
|
||||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
# Test job
|
# Test job
|
||||||
|
|||||||
@@ -1,34 +0,0 @@
|
|||||||
name: opencode
|
|
||||||
on:
|
|
||||||
issue_comment:
|
|
||||||
types: [created]
|
|
||||||
pull_request_review_comment:
|
|
||||||
types: [created]
|
|
||||||
|
|
||||||
env:
|
|
||||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
opencode:
|
|
||||||
if: |
|
|
||||||
contains(github.event.comment.body, '/oc') ||
|
|
||||||
contains(github.event.comment.body, '/opencode')
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
permissions:
|
|
||||||
id-token: write
|
|
||||||
contents: write
|
|
||||||
pull-requests: write
|
|
||||||
issues: write
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
fetch-depth: 1
|
|
||||||
|
|
||||||
- name: Run OpenCode
|
|
||||||
uses: anomalyco/opencode/github@latest
|
|
||||||
env:
|
|
||||||
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
|
|
||||||
with:
|
|
||||||
model: anthropic/claude-sonnet-4-20250514
|
|
||||||
@@ -4,6 +4,7 @@ use tokio::sync::mpsc;
|
|||||||
|
|
||||||
use super::client::{Client, ClientConfig};
|
use super::client::{Client, ClientConfig};
|
||||||
use super::state::ConnectionState;
|
use super::state::ConnectionState;
|
||||||
|
use crate::protocol::{Command, Direction, InPacket, PacketType};
|
||||||
use crate::ProtocolError;
|
use crate::ProtocolError;
|
||||||
|
|
||||||
pub enum SessionCommand {
|
pub enum SessionCommand {
|
||||||
@@ -21,6 +22,8 @@ pub enum SessionCommand {
|
|||||||
target_id: u64,
|
target_id: u64,
|
||||||
message: String,
|
message: String,
|
||||||
},
|
},
|
||||||
|
RequestChannelList,
|
||||||
|
RequestClientList,
|
||||||
Disconnect,
|
Disconnect,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -30,8 +33,66 @@ pub enum TextMessageTarget {
|
|||||||
Client = 1,
|
Client = 1,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, serde::Serialize)]
|
||||||
|
pub enum SessionEvent {
|
||||||
|
Connected {
|
||||||
|
client_id: u16,
|
||||||
|
},
|
||||||
|
ChannelList(Vec<ChannelEntry>),
|
||||||
|
ClientList(Vec<ClientEntry>),
|
||||||
|
ClientEntered {
|
||||||
|
clid: u16,
|
||||||
|
cid: u64,
|
||||||
|
client_nickname: String,
|
||||||
|
},
|
||||||
|
ClientLeft {
|
||||||
|
clid: u16,
|
||||||
|
reason: String,
|
||||||
|
},
|
||||||
|
ClientMoved {
|
||||||
|
clid: u16,
|
||||||
|
cid: u64,
|
||||||
|
},
|
||||||
|
TextMessage {
|
||||||
|
invoker_id: u16,
|
||||||
|
invoker_name: String,
|
||||||
|
message: String,
|
||||||
|
target_mode: u8,
|
||||||
|
},
|
||||||
|
ServerInfo {
|
||||||
|
name: String,
|
||||||
|
platform: String,
|
||||||
|
version: String,
|
||||||
|
max_clients: u16,
|
||||||
|
clients_online: u16,
|
||||||
|
channels_online: u16,
|
||||||
|
},
|
||||||
|
Error(String),
|
||||||
|
Disconnected,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, serde::Serialize)]
|
||||||
|
pub struct ChannelEntry {
|
||||||
|
pub cid: u64,
|
||||||
|
pub pid: u64,
|
||||||
|
pub channel_order: u64,
|
||||||
|
pub channel_name: String,
|
||||||
|
pub total_clients: u16,
|
||||||
|
pub channel_needed_subscribe_power: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, serde::Serialize)]
|
||||||
|
pub struct ClientEntry {
|
||||||
|
pub clid: u16,
|
||||||
|
pub cid: u64,
|
||||||
|
pub client_database_id: u64,
|
||||||
|
pub client_nickname: String,
|
||||||
|
pub client_type: u8,
|
||||||
|
}
|
||||||
|
|
||||||
pub struct SessionHandle {
|
pub struct SessionHandle {
|
||||||
command_tx: mpsc::Sender<SessionCommand>,
|
command_tx: mpsc::Sender<SessionCommand>,
|
||||||
|
event_rx: mpsc::Receiver<SessionEvent>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SessionHandle {
|
impl SessionHandle {
|
||||||
@@ -107,12 +168,34 @@ impl SessionHandle {
|
|||||||
.map_err(|_| ProtocolError::ConnectionClosed)
|
.map_err(|_| ProtocolError::ConnectionClosed)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn request_channel_list(&self) -> Result<(), ProtocolError> {
|
||||||
|
self.command_tx
|
||||||
|
.send(SessionCommand::RequestChannelList)
|
||||||
|
.await
|
||||||
|
.map_err(|_| ProtocolError::ConnectionClosed)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn request_client_list(&self) -> Result<(), ProtocolError> {
|
||||||
|
self.command_tx
|
||||||
|
.send(SessionCommand::RequestClientList)
|
||||||
|
.await
|
||||||
|
.map_err(|_| ProtocolError::ConnectionClosed)
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn disconnect(&self) -> Result<(), ProtocolError> {
|
pub async fn disconnect(&self) -> Result<(), ProtocolError> {
|
||||||
self.command_tx
|
self.command_tx
|
||||||
.send(SessionCommand::Disconnect)
|
.send(SessionCommand::Disconnect)
|
||||||
.await
|
.await
|
||||||
.map_err(|_| ProtocolError::ConnectionClosed)
|
.map_err(|_| ProtocolError::ConnectionClosed)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn recv_event(&mut self) -> Option<SessionEvent> {
|
||||||
|
self.event_rx.recv().await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn try_recv_event(&mut self) -> Option<SessionEvent> {
|
||||||
|
self.event_rx.try_recv().ok()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Session {
|
pub struct Session {
|
||||||
@@ -122,38 +205,6 @@ pub struct Session {
|
|||||||
event_tx: mpsc::Sender<SessionEvent>,
|
event_tx: mpsc::Sender<SessionEvent>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum SessionEvent {
|
|
||||||
Connected {
|
|
||||||
client_id: u16,
|
|
||||||
},
|
|
||||||
ChannelList(Vec<ChannelEntry>),
|
|
||||||
ClientEntered {
|
|
||||||
clid: u16,
|
|
||||||
cid: u64,
|
|
||||||
client_nickname: String,
|
|
||||||
},
|
|
||||||
ClientLeft {
|
|
||||||
clid: u16,
|
|
||||||
},
|
|
||||||
TextMessage {
|
|
||||||
invoker_id: u16,
|
|
||||||
invoker_name: String,
|
|
||||||
message: String,
|
|
||||||
},
|
|
||||||
Error(String),
|
|
||||||
Disconnected,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct ChannelEntry {
|
|
||||||
pub cid: u64,
|
|
||||||
pub pid: u64,
|
|
||||||
pub channel_order: u64,
|
|
||||||
pub channel_name: String,
|
|
||||||
pub total_clients: u16,
|
|
||||||
pub channel_needed_subscribe_power: i32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Session {
|
impl Session {
|
||||||
pub async fn connect(
|
pub async fn connect(
|
||||||
config: ClientConfig,
|
config: ClientConfig,
|
||||||
@@ -171,14 +222,27 @@ impl Session {
|
|||||||
let init0 = client.start_handshake()?;
|
let init0 = client.start_handshake()?;
|
||||||
socket.send(&init0).await?;
|
socket.send(&init0).await?;
|
||||||
|
|
||||||
|
let mut init_events = Vec::new();
|
||||||
|
|
||||||
tokio::time::timeout(timeout, async {
|
tokio::time::timeout(timeout, async {
|
||||||
let mut buf = [0u8; 2048];
|
let mut buf = [0u8; 2048];
|
||||||
loop {
|
loop {
|
||||||
let len = socket.recv(&mut buf).await?;
|
let len = socket.recv(&mut buf).await?;
|
||||||
let responses = client.handle_data(&buf[..len])?;
|
let responses = client.handle_data(&buf[..len])?;
|
||||||
for response in responses {
|
for response in &responses {
|
||||||
socket.send(&response).await?;
|
socket.send(response).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Ok(packet) = InPacket::parse(Direction::S2C, &buf[..len]) {
|
||||||
|
if let Some(content) = Self::decrypt_packet_static(&client, &packet) {
|
||||||
|
if let Ok(content_str) = std::str::from_utf8(&content) {
|
||||||
|
for cmd in Command::parse_many(content_str).unwrap_or_default() {
|
||||||
|
Self::collect_init_event(&cmd, &mut init_events);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if client.state() == ConnectionState::ChannelListFinished {
|
if client.state() == ConnectionState::ChannelListFinished {
|
||||||
return Ok::<(), ProtocolError>(());
|
return Ok::<(), ProtocolError>(());
|
||||||
}
|
}
|
||||||
@@ -188,7 +252,7 @@ impl Session {
|
|||||||
.map_err(|_| ProtocolError::Timeout("handshake timed out".to_string()))??;
|
.map_err(|_| ProtocolError::Timeout("handshake timed out".to_string()))??;
|
||||||
|
|
||||||
let (command_tx, command_rx) = mpsc::channel(32);
|
let (command_tx, command_rx) = mpsc::channel(32);
|
||||||
let (event_tx, _event_rx) = mpsc::channel(32);
|
let (event_tx, mut event_rx) = mpsc::channel(32);
|
||||||
|
|
||||||
let session = Self {
|
let session = Self {
|
||||||
client,
|
client,
|
||||||
@@ -197,11 +261,74 @@ impl Session {
|
|||||||
event_tx,
|
event_tx,
|
||||||
};
|
};
|
||||||
|
|
||||||
let handle = SessionHandle { command_tx };
|
let handle = SessionHandle {
|
||||||
|
command_tx,
|
||||||
|
event_rx,
|
||||||
|
};
|
||||||
|
|
||||||
Ok((session, handle))
|
Ok((session, handle))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn decrypt_packet_static(client: &Client, packet: &InPacket) -> Option<Vec<u8>> {
|
||||||
|
let packet_type = packet.header.flags.packet_type();
|
||||||
|
if packet.header.flags.is_unencrypted() {
|
||||||
|
return Some(packet.data.clone());
|
||||||
|
}
|
||||||
|
if packet_type == PacketType::Ack || packet_type == PacketType::AckLow {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn collect_init_event(cmd: &Command, events: &mut Vec<SessionEvent>) {
|
||||||
|
match cmd.name.as_str() {
|
||||||
|
"initserver" => {
|
||||||
|
let client_id = cmd.get("client_id").and_then(|v| v.parse().ok()).unwrap_or(0);
|
||||||
|
let name = cmd.get("virtualserver_name").unwrap_or_default().to_string();
|
||||||
|
let platform = cmd.get("virtualserver_platform").unwrap_or_default().to_string();
|
||||||
|
let version = cmd.get("virtualserver_version").unwrap_or_default().to_string();
|
||||||
|
let max_clients = cmd.get("virtualserver_maxclients").and_then(|v| v.parse().ok()).unwrap_or(0);
|
||||||
|
let clients_online = cmd.get("virtualserver_clientsonline").and_then(|v| v.parse().ok()).unwrap_or(0);
|
||||||
|
let channels_online = cmd.get("virtualserver_channelsonline").and_then(|v| v.parse().ok()).unwrap_or(0);
|
||||||
|
|
||||||
|
events.push(SessionEvent::ServerInfo {
|
||||||
|
name,
|
||||||
|
platform,
|
||||||
|
version,
|
||||||
|
max_clients,
|
||||||
|
clients_online,
|
||||||
|
channels_online,
|
||||||
|
});
|
||||||
|
events.push(SessionEvent::Connected { client_id });
|
||||||
|
}
|
||||||
|
"channellist" => {
|
||||||
|
let channels = Self::parse_channel_list(cmd);
|
||||||
|
events.push(SessionEvent::ChannelList(channels));
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_channel_list(cmd: &Command) -> Vec<ChannelEntry> {
|
||||||
|
let mut channels = Vec::new();
|
||||||
|
let cid = cmd.get("cid").and_then(|v| v.parse().ok()).unwrap_or(0);
|
||||||
|
let pid = cmd.get("pid").and_then(|v| v.parse().ok()).unwrap_or(0);
|
||||||
|
let channel_order = cmd.get("channel_order").and_then(|v| v.parse().ok()).unwrap_or(0);
|
||||||
|
let channel_name = cmd.get("channel_name").unwrap_or_default().to_string();
|
||||||
|
let total_clients = cmd.get("total_clients").and_then(|v| v.parse().ok()).unwrap_or(0);
|
||||||
|
let channel_needed_subscribe_power = cmd.get("channel_needed_subscribe_power").and_then(|v| v.parse().ok()).unwrap_or(0);
|
||||||
|
|
||||||
|
channels.push(ChannelEntry {
|
||||||
|
cid,
|
||||||
|
pid,
|
||||||
|
channel_order,
|
||||||
|
channel_name,
|
||||||
|
total_clients,
|
||||||
|
channel_needed_subscribe_power,
|
||||||
|
});
|
||||||
|
channels
|
||||||
|
}
|
||||||
|
|
||||||
pub fn client_id(&self) -> Option<u16> {
|
pub fn client_id(&self) -> Option<u16> {
|
||||||
self.client.client_id()
|
self.client.client_id()
|
||||||
}
|
}
|
||||||
@@ -218,8 +345,21 @@ impl Session {
|
|||||||
result = self.socket.recv(&mut buf) => {
|
result = self.socket.recv(&mut buf) => {
|
||||||
let len = result?;
|
let len = result?;
|
||||||
let responses = self.client.handle_data(&buf[..len])?;
|
let responses = self.client.handle_data(&buf[..len])?;
|
||||||
for response in responses {
|
for response in &responses {
|
||||||
self.socket.send(&response).await?;
|
self.socket.send(response).await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Ok(packet) = InPacket::parse(Direction::S2C, &buf[..len]) {
|
||||||
|
let packet_type = packet.header.flags.packet_type();
|
||||||
|
if packet_type == PacketType::Command || packet_type == PacketType::CommandLow {
|
||||||
|
if let Some(content) = self.decrypt_packet(&packet) {
|
||||||
|
if let Ok(content_str) = std::str::from_utf8(&content) {
|
||||||
|
for cmd in Command::parse_many(content_str).unwrap_or_default() {
|
||||||
|
self.emit_command_event(&cmd).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some(command) = self.command_rx.recv() => {
|
Some(command) = self.command_rx.recv() => {
|
||||||
@@ -238,6 +378,111 @@ impl Session {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn decrypt_packet(&self, packet: &InPacket) -> Option<Vec<u8>> {
|
||||||
|
let packet_type = packet.header.flags.packet_type();
|
||||||
|
if packet.header.flags.is_unencrypted() {
|
||||||
|
return Some(packet.data.clone());
|
||||||
|
}
|
||||||
|
if packet_type == PacketType::Ack || packet_type == PacketType::AckLow {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn emit_command_event(&self, cmd: &Command) {
|
||||||
|
let event = match cmd.name.as_str() {
|
||||||
|
"initserver" => {
|
||||||
|
let client_id = cmd.get("client_id").and_then(|v| v.parse().ok()).unwrap_or(0);
|
||||||
|
let name = cmd.get("virtualserver_name").unwrap_or_default().to_string();
|
||||||
|
let platform = cmd.get("virtualserver_platform").unwrap_or_default().to_string();
|
||||||
|
let version = cmd.get("virtualserver_version").unwrap_or_default().to_string();
|
||||||
|
let max_clients = cmd.get("virtualserver_maxclients").and_then(|v| v.parse().ok()).unwrap_or(0);
|
||||||
|
let clients_online = cmd.get("virtualserver_clientsonline").and_then(|v| v.parse().ok()).unwrap_or(0);
|
||||||
|
let channels_online = cmd.get("virtualserver_channelsonline").and_then(|v| v.parse().ok()).unwrap_or(0);
|
||||||
|
|
||||||
|
let _ = self.event_tx.send(SessionEvent::ServerInfo {
|
||||||
|
name,
|
||||||
|
platform,
|
||||||
|
version,
|
||||||
|
max_clients,
|
||||||
|
clients_online,
|
||||||
|
channels_online,
|
||||||
|
}).await;
|
||||||
|
Some(SessionEvent::Connected { client_id })
|
||||||
|
}
|
||||||
|
"channellist" => {
|
||||||
|
let channels = Self::parse_channel_list(cmd);
|
||||||
|
Some(SessionEvent::ChannelList(channels))
|
||||||
|
}
|
||||||
|
"clientlist" => {
|
||||||
|
let clients = Self::parse_client_list(cmd);
|
||||||
|
Some(SessionEvent::ClientList(clients))
|
||||||
|
}
|
||||||
|
"notifycliententerview" => {
|
||||||
|
let clid = cmd.get("clid").and_then(|v| v.parse().ok()).unwrap_or(0);
|
||||||
|
let cid = cmd.get("cid").and_then(|v| v.parse().ok()).unwrap_or(0);
|
||||||
|
let client_nickname = cmd.get("client_nickname").unwrap_or_default().to_string();
|
||||||
|
Some(SessionEvent::ClientEntered {
|
||||||
|
clid,
|
||||||
|
cid,
|
||||||
|
client_nickname,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
"notifyclientleftview" => {
|
||||||
|
let clid = cmd.get("clid").and_then(|v| v.parse().ok()).unwrap_or(0);
|
||||||
|
let reason = cmd.get("reasonmsg").unwrap_or_default().to_string();
|
||||||
|
Some(SessionEvent::ClientLeft { clid, reason })
|
||||||
|
}
|
||||||
|
"notifyclientmoved" => {
|
||||||
|
let clid = cmd.get("clid").and_then(|v| v.parse().ok()).unwrap_or(0);
|
||||||
|
let cid = cmd.get("ctid").and_then(|v| v.parse().ok()).unwrap_or(0);
|
||||||
|
Some(SessionEvent::ClientMoved { clid, cid })
|
||||||
|
}
|
||||||
|
"notifytextmessage" => {
|
||||||
|
let invoker_id = cmd.get("invokerid").and_then(|v| v.parse().ok()).unwrap_or(0);
|
||||||
|
let invoker_name = cmd.get("invokername").unwrap_or_default().to_string();
|
||||||
|
let message = cmd.get("msg").unwrap_or_default().to_string();
|
||||||
|
let target_mode = cmd.get("targetmode").and_then(|v| v.parse().ok()).unwrap_or(0);
|
||||||
|
Some(SessionEvent::TextMessage {
|
||||||
|
invoker_id,
|
||||||
|
invoker_name,
|
||||||
|
message,
|
||||||
|
target_mode,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
"error" => {
|
||||||
|
let id = cmd.get("id").unwrap_or("0");
|
||||||
|
if id != "0" {
|
||||||
|
let msg = cmd.get("msg").unwrap_or("unknown error").to_string();
|
||||||
|
Some(SessionEvent::Error(msg))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(event) = event {
|
||||||
|
let _ = self.event_tx.send(event).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_client_list(cmd: &Command) -> Vec<ClientEntry> {
|
||||||
|
let clid = cmd.get("clid").and_then(|v| v.parse().ok()).unwrap_or(0);
|
||||||
|
let cid = cmd.get("cid").and_then(|v| v.parse().ok()).unwrap_or(0);
|
||||||
|
let client_database_id = cmd.get("client_database_id").and_then(|v| v.parse().ok()).unwrap_or(0);
|
||||||
|
let client_nickname = cmd.get("client_nickname").unwrap_or_default().to_string();
|
||||||
|
let client_type = cmd.get("client_type").and_then(|v| v.parse().ok()).unwrap_or(0);
|
||||||
|
|
||||||
|
vec![ClientEntry {
|
||||||
|
clid,
|
||||||
|
cid,
|
||||||
|
client_database_id,
|
||||||
|
client_nickname,
|
||||||
|
client_type,
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
|
||||||
async fn handle_command(&mut self, command: SessionCommand) -> Result<(), ProtocolError> {
|
async fn handle_command(&mut self, command: SessionCommand) -> Result<(), ProtocolError> {
|
||||||
match command {
|
match command {
|
||||||
SessionCommand::SendCommand(content) => {
|
SessionCommand::SendCommand(content) => {
|
||||||
@@ -278,6 +523,14 @@ impl Session {
|
|||||||
let packet = self.client.build_command_packet(cmd.into_bytes())?;
|
let packet = self.client.build_command_packet(cmd.into_bytes())?;
|
||||||
self.socket.send(&packet).await?;
|
self.socket.send(&packet).await?;
|
||||||
}
|
}
|
||||||
|
SessionCommand::RequestChannelList => {
|
||||||
|
let packet = self.client.build_command_packet(b"channellist".to_vec())?;
|
||||||
|
self.socket.send(&packet).await?;
|
||||||
|
}
|
||||||
|
SessionCommand::RequestClientList => {
|
||||||
|
let packet = self.client.build_command_packet(b"clientlist".to_vec())?;
|
||||||
|
self.socket.send(&packet).await?;
|
||||||
|
}
|
||||||
SessionCommand::Disconnect => {
|
SessionCommand::Disconnect => {
|
||||||
let packet = self
|
let packet = self
|
||||||
.client
|
.client
|
||||||
|
|||||||
Reference in New Issue
Block a user