feat: 支持从 clash api 获取实时连接信息。
This commit is contained in:
18
src/clash_conn_msg.rs
Normal file
18
src/clash_conn_msg.rs
Normal file
@@ -0,0 +1,18 @@
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ClashConnectionMessage {
|
||||
pub id: String,
|
||||
pub chains: Vec<String>,
|
||||
pub upload: u64,
|
||||
pub download: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ClashConnectionsWrapper {
|
||||
pub connections: Vec<ClashConnectionMessage>,
|
||||
pub download_total: u64,
|
||||
pub upload_total: u64,
|
||||
}
|
82
src/main.rs
Normal file
82
src/main.rs
Normal file
@@ -0,0 +1,82 @@
|
||||
use std::format;
|
||||
|
||||
use clap::Parser;
|
||||
use futures_util::{pin_mut, StreamExt};
|
||||
use tokio::io::{stdout, AsyncWriteExt};
|
||||
use tokio_tungstenite::connect_async;
|
||||
|
||||
mod clash_conn_msg;
|
||||
mod statistics;
|
||||
|
||||
#[derive(Parser)]
|
||||
#[clap(
|
||||
version = "0.1.0",
|
||||
author = "Ivan Li",
|
||||
about = "Watch Clash network traffic"
|
||||
)]
|
||||
struct Args {
|
||||
#[clap(
|
||||
short,
|
||||
long,
|
||||
default_value = "ws://192.168.31.1:9090/connections?token=123456"
|
||||
)]
|
||||
clash_url: String,
|
||||
#[clap(short, long, default_value = "17890")]
|
||||
listen_port: u16,
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
println!("Hello, world!");
|
||||
|
||||
let args = Args::parse();
|
||||
|
||||
let connect_addr = args.clash_url;
|
||||
|
||||
let url = url::Url::parse(&connect_addr).unwrap();
|
||||
|
||||
let (ws_stream, _) = connect_async(url).await.expect("Failed to connect");
|
||||
println!("WebSocket handshake has been successfully completed");
|
||||
|
||||
let (_, read) = ws_stream.split();
|
||||
|
||||
let ws_to_stdout = {
|
||||
read.for_each(|message| async {
|
||||
let data = message.unwrap().into_data();
|
||||
|
||||
let wrapper = serde_json::from_slice::<clash_conn_msg::ClashConnectionsWrapper>(&data);
|
||||
|
||||
if let Err(err) = wrapper {
|
||||
stdout()
|
||||
.write_all(format!("Error: {}\n", err).as_bytes())
|
||||
.await
|
||||
.unwrap();
|
||||
return;
|
||||
}
|
||||
let wrapper = wrapper.unwrap();
|
||||
|
||||
let statistics_manager = statistics::StatisticsManager::global().await;
|
||||
|
||||
statistics_manager.update(&wrapper).await;
|
||||
|
||||
let state = statistics_manager.get_state().await;
|
||||
|
||||
stdout()
|
||||
.write_all(
|
||||
format!(
|
||||
"upload: {}, download: {}. speed_upload: {}, speed_download: {}\n",
|
||||
wrapper.upload_total,
|
||||
wrapper.download_total,
|
||||
state.speed_upload,
|
||||
state.speed_download
|
||||
)
|
||||
.as_bytes(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
})
|
||||
};
|
||||
|
||||
pin_mut!(ws_to_stdout);
|
||||
ws_to_stdout.await;
|
||||
}
|
68
src/statistics.rs
Normal file
68
src/statistics.rs
Normal file
@@ -0,0 +1,68 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use tokio::sync::{Mutex, OnceCell};
|
||||
|
||||
use crate::clash_conn_msg::ClashConnectionsWrapper;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct StatisticsState {
|
||||
pub connections: usize,
|
||||
pub download_total: u64,
|
||||
pub upload_total: u64,
|
||||
pub speed_upload: u64,
|
||||
pub speed_download: u64,
|
||||
}
|
||||
|
||||
impl StatisticsState {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
connections: 0,
|
||||
download_total: 0,
|
||||
upload_total: 0,
|
||||
speed_upload: 0,
|
||||
speed_download: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update(&mut self, wrapper: &ClashConnectionsWrapper) {
|
||||
self.connections = wrapper.connections.len();
|
||||
self.update_speed(wrapper.upload_total, wrapper.download_total);
|
||||
self.download_total = wrapper.download_total;
|
||||
self.upload_total = wrapper.upload_total;
|
||||
}
|
||||
|
||||
pub fn update_speed(&mut self, curr_upload: u64, curr_download: u64) {
|
||||
self.speed_upload = curr_upload - self.upload_total;
|
||||
self.speed_download = curr_download - self.download_total;
|
||||
}
|
||||
}
|
||||
|
||||
pub struct StatisticsManager {
|
||||
state: Arc<Mutex<StatisticsState>>,
|
||||
}
|
||||
|
||||
impl StatisticsManager {
|
||||
pub async fn global() -> &'static Self {
|
||||
static STATISTICS_MANAGER: OnceCell<StatisticsManager> = OnceCell::const_new();
|
||||
|
||||
STATISTICS_MANAGER
|
||||
.get_or_init(|| async { StatisticsManager::new() })
|
||||
.await
|
||||
}
|
||||
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
state: Arc::new(Mutex::new(StatisticsState::new())),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn update(&self, wrapper: &ClashConnectionsWrapper) {
|
||||
let mut state = self.state.lock().await;
|
||||
state.update(wrapper);
|
||||
}
|
||||
|
||||
pub async fn get_state(&self) -> StatisticsState {
|
||||
let state = self.state.lock().await;
|
||||
state.clone()
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user