feat: 支持从 clash api 获取实时连接信息。

This commit is contained in:
2023-05-13 19:01:27 +08:00
commit 0932cf1283
6 changed files with 1149 additions and 0 deletions

18
src/clash_conn_msg.rs Normal file
View 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
View 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
View 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()
}
}