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:
|
||||
CARGO_TERM_COLOR: always
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
|
||||
|
||||
jobs:
|
||||
# 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::state::ConnectionState;
|
||||
use crate::protocol::{Command, Direction, InPacket, PacketType};
|
||||
use crate::ProtocolError;
|
||||
|
||||
pub enum SessionCommand {
|
||||
@@ -21,6 +22,8 @@ pub enum SessionCommand {
|
||||
target_id: u64,
|
||||
message: String,
|
||||
},
|
||||
RequestChannelList,
|
||||
RequestClientList,
|
||||
Disconnect,
|
||||
}
|
||||
|
||||
@@ -30,8 +33,66 @@ pub enum TextMessageTarget {
|
||||
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 {
|
||||
command_tx: mpsc::Sender<SessionCommand>,
|
||||
event_rx: mpsc::Receiver<SessionEvent>,
|
||||
}
|
||||
|
||||
impl SessionHandle {
|
||||
@@ -107,12 +168,34 @@ impl SessionHandle {
|
||||
.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> {
|
||||
self.command_tx
|
||||
.send(SessionCommand::Disconnect)
|
||||
.await
|
||||
.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 {
|
||||
@@ -122,38 +205,6 @@ pub struct Session {
|
||||
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 {
|
||||
pub async fn connect(
|
||||
config: ClientConfig,
|
||||
@@ -171,14 +222,27 @@ impl Session {
|
||||
let init0 = client.start_handshake()?;
|
||||
socket.send(&init0).await?;
|
||||
|
||||
let mut init_events = Vec::new();
|
||||
|
||||
tokio::time::timeout(timeout, async {
|
||||
let mut buf = [0u8; 2048];
|
||||
loop {
|
||||
let len = socket.recv(&mut buf).await?;
|
||||
let responses = client.handle_data(&buf[..len])?;
|
||||
for response in responses {
|
||||
socket.send(&response).await?;
|
||||
for response in &responses {
|
||||
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 {
|
||||
return Ok::<(), ProtocolError>(());
|
||||
}
|
||||
@@ -188,7 +252,7 @@ impl Session {
|
||||
.map_err(|_| ProtocolError::Timeout("handshake timed out".to_string()))??;
|
||||
|
||||
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 {
|
||||
client,
|
||||
@@ -197,11 +261,74 @@ impl Session {
|
||||
event_tx,
|
||||
};
|
||||
|
||||
let handle = SessionHandle { command_tx };
|
||||
let handle = SessionHandle {
|
||||
command_tx,
|
||||
event_rx,
|
||||
};
|
||||
|
||||
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> {
|
||||
self.client.client_id()
|
||||
}
|
||||
@@ -218,8 +345,21 @@ impl Session {
|
||||
result = self.socket.recv(&mut buf) => {
|
||||
let len = result?;
|
||||
let responses = self.client.handle_data(&buf[..len])?;
|
||||
for response in responses {
|
||||
self.socket.send(&response).await?;
|
||||
for response in &responses {
|
||||
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() => {
|
||||
@@ -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> {
|
||||
match command {
|
||||
SessionCommand::SendCommand(content) => {
|
||||
@@ -278,6 +523,14 @@ impl Session {
|
||||
let packet = self.client.build_command_packet(cmd.into_bytes())?;
|
||||
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 => {
|
||||
let packet = self
|
||||
.client
|
||||
|
||||
Reference in New Issue
Block a user