├── .gitignore ├── LICENSE ├── Cargo.toml ├── src ├── config_tool.rs ├── msgid_tool.rs ├── onebot_http_rev.rs ├── onebot_ws.rs ├── onebot_http.rs ├── main.rs ├── onebot_ws_rev.rs ├── cqtool.rs └── kook_onebot.rs ├── .github └── workflows │ └── rust.yml ├── readme.md └── Cargo.lock /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 super1207 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "kook_onebot" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | reqwest = {version = "0.11.18",default-features = false,features = ["rustls-tls","multipart"]} 10 | tokio = {version = "1.29.1",features = ["macros","rt-multi-thread","fs"]} 11 | serde_json = "1.0.100" 12 | serde_derive = "1.0.166" 13 | serde = {version = "1.0.166",features = ["derive"]} 14 | tungstenite = { version ="0.19.0",default-features = false,features = ["rustls-tls-webpki-roots"] } 15 | tokio-tungstenite = { version ="0.19.0",default-features = false,features = ["rustls-tls-webpki-roots"] } 16 | futures-util = "0.3.28" 17 | flate2 = "1.0.26" 18 | hyper-tungstenite = "0.10.0" 19 | hyper = {version = "0.14.27",features = ["server"]} 20 | http-body-util = "0.1.0-rc.2" 21 | lazy_static = "1.4.0" 22 | uuid = {version = "1.3.0",features = ["v4","fast-rng"]} 23 | scopeguard = "1.1.0" 24 | regex = "1.8.4" 25 | base64 = "0.21.0" 26 | urlencoding = "2.1.2" 27 | url = "2.4.0" 28 | log = "0.4.17" 29 | tracing = "0.1.37" 30 | tracing-subscriber = { version = "0.3.16", features = ["env-filter","time","local-time"]} 31 | time = { version = "0.3", features = ["formatting", "macros"] } 32 | hmac = "0.12.1" 33 | sha1 = "0.10.5" 34 | hex = "0.4.3" -------------------------------------------------------------------------------- /src/config_tool.rs: -------------------------------------------------------------------------------- 1 | 2 | use tokio::sync::RwLock; 3 | 4 | fn deal_path_str(path_str:&str) -> &str { 5 | if path_str.starts_with("\\\\?\\") { 6 | return &path_str[4..]; 7 | }else{ 8 | return path_str; 9 | } 10 | } 11 | 12 | fn get_run_dir() -> Result>{ 13 | let exe_dir = std::env::current_exe()?; 14 | let exe_path = exe_dir.parent().ok_or("无法获得运行目录")?; 15 | let mut exe_path_str = exe_path.to_string_lossy().to_string(); 16 | if !exe_path_str.ends_with(std::path::MAIN_SEPARATOR) 17 | { 18 | exe_path_str.push(std::path::MAIN_SEPARATOR); 19 | } 20 | return Ok(deal_path_str(&exe_path_str).to_string()); 21 | } 22 | 23 | pub async fn read_config() -> Result> { 24 | lazy_static! { 25 | static ref JS_VAL: RwLock> = RwLock::new(None); 26 | } 27 | 28 | { 29 | let lk = JS_VAL.read().await; 30 | if lk.is_some() { 31 | return Ok(lk.clone().unwrap().clone()); 32 | } 33 | } 34 | 35 | let run_dir = get_run_dir()?; 36 | let config_file_dir = run_dir + "config.json"; 37 | 38 | let mut is_file_exists = false; 39 | if let Ok(metadata) = tokio::fs::metadata(config_file_dir.clone()).await { 40 | if metadata.is_file() { 41 | is_file_exists = true; 42 | } 43 | } 44 | if !is_file_exists{ 45 | tokio::fs::write(config_file_dir.clone(), "{\"web_port\":8080,\"kook_token\":\"\",\"access_token\":\"\",\"web_host\":\"127.0.0.1\",\"reverse_uri\":[],\"secret\":\"\"}").await?; 46 | log::error!("config.json文件不存在,为您自动生成!请自行修改后重新运行!!!"); 47 | loop { 48 | tokio::time::sleep(std::time::Duration::from_secs(30)).await; 49 | } 50 | } 51 | 52 | 53 | let file_str = tokio::fs::read_to_string(config_file_dir).await?; 54 | let json_val:serde_json::Value = serde_json::from_str(&file_str)?; 55 | { 56 | let mut lk = JS_VAL.write().await; 57 | (*lk) = Some(json_val.clone()) 58 | } 59 | Ok(json_val) 60 | } 61 | -------------------------------------------------------------------------------- /src/msgid_tool.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | 4 | lazy_static! { 5 | static ref MSG_ID_LIST : std::sync::RwLock> = std::sync::RwLock::new(HashMap::new()); 6 | } 7 | 8 | #[derive(Clone)] 9 | pub struct QMessageStruct { 10 | pub raw_ids:Vec, 11 | pub user_id:u64 12 | } 13 | 14 | fn crc32(data: &[u8]) -> u32 { 15 | let mut crc = 0xFFFFFFFFu32; 16 | let table = generate_crc32_table(); 17 | 18 | for byte in data.iter() { 19 | let index = ((crc ^ u32::from(*byte)) & 0xFF) as usize; 20 | crc = (crc >> 8) ^ table[index]; 21 | } 22 | 23 | !crc 24 | } 25 | 26 | fn generate_crc32_table() -> [u32; 256] { 27 | const POLY: u32 = 0xEDB88320; 28 | 29 | let mut table = [0u32; 256]; 30 | 31 | for i in 0..256 { 32 | let mut crc = i as u32; 33 | for _ in 0..8 { 34 | if crc & 1 != 0 { 35 | crc = POLY ^ (crc >> 1); 36 | } else { 37 | crc >>= 1; 38 | } 39 | } 40 | table[i] = crc; 41 | } 42 | 43 | table 44 | } 45 | 46 | 47 | 48 | pub fn add_msg_id(msg_ids:QMessageStruct) -> i32 { 49 | if msg_ids.raw_ids.len() == 0 { 50 | return 0; 51 | } 52 | let msg0 = msg_ids.raw_ids.get(0).unwrap(); 53 | let crc_num = crc32(msg0.as_bytes()) as i32; 54 | let mut lk = MSG_ID_LIST.write().unwrap(); 55 | if lk.len() > 9999999 { 56 | lk.clear(); 57 | } 58 | lk.insert(crc_num,msg_ids); 59 | crc_num 60 | } 61 | 62 | pub fn get_msg_id(crc_num:i32) -> QMessageStruct { 63 | let lk = MSG_ID_LIST.read().unwrap(); 64 | if let Some(v) = lk.get(&crc_num) { 65 | v.to_owned() 66 | }else { 67 | QMessageStruct{ raw_ids: vec![] ,user_id: 0 } 68 | } 69 | } 70 | 71 | pub fn get_cq_msg_id(raw_msg_id:&str) -> (i32,u64) { 72 | let lk = MSG_ID_LIST.read().unwrap(); 73 | for (cq_id,msg) in lk.iter() { 74 | for it in &msg.raw_ids { 75 | if raw_msg_id == it { 76 | return (cq_id.to_owned(),msg.user_id); 77 | } 78 | } 79 | } 80 | return (0,0); 81 | } -------------------------------------------------------------------------------- /src/onebot_http_rev.rs: -------------------------------------------------------------------------------- 1 | use std::str::FromStr; 2 | 3 | use hyper::http::{HeaderName, HeaderValue}; 4 | 5 | use crate::{G_REVERSE_URL, kook_onebot::KookOnebot, G_SECERT}; 6 | 7 | use hmac::{Hmac, Mac}; 8 | use sha1::Sha1; 9 | 10 | pub async fn post_to_client(url:&str,json_str:&str,self_id:u64) -> Result> { 11 | let secert = G_SECERT.read().await.clone(); 12 | let uri = reqwest::Url::from_str(url)?; 13 | let client = reqwest::Client::builder().danger_accept_invalid_certs(true).no_proxy().build()?; 14 | let mut req = client.post(uri).body(reqwest::Body::from(json_str.to_owned())).build()?; 15 | if secert != "" { 16 | type HmacSha1 = Hmac; 17 | let secert = G_SECERT.read().await.clone(); 18 | let secert_str = secert.as_bytes(); 19 | let mut mac = HmacSha1::new_from_slice(&secert_str).expect("HMAC can take key of any size"); 20 | mac.update(json_str.as_bytes()); 21 | let result = mac.finalize(); 22 | let code_bytes = result.into_bytes(); 23 | let sha1_str = hex::encode(code_bytes); 24 | req.headers_mut().append(HeaderName::from_str("X-Signature")?, HeaderValue::from_str(&format!("sha1={sha1_str}"))?); 25 | } 26 | req.headers_mut().append(HeaderName::from_str("Content-type")?, HeaderValue::from_str("application/json")?); 27 | req.headers_mut().append(HeaderName::from_str("X-Self-ID")?, HeaderValue::from_str(&self_id.to_string())?); 28 | let res= client.execute(req).await?; 29 | let res_code = res.status(); 30 | let mut res_json = serde_json::Value::Null; 31 | if res_code != reqwest::StatusCode::NO_CONTENT { 32 | let res_content = res.bytes().await?; 33 | if res_content.len() != 0 { 34 | res_json = serde_json::from_slice(&res_content)?; 35 | } 36 | } 37 | Ok(res_json) 38 | } 39 | 40 | 41 | pub async fn deal_heartbeat(kb2:KookOnebot) -> ! { 42 | loop { 43 | tokio::time::sleep(tokio::time::Duration::from_secs(5)).await; 44 | { 45 | let json_str = kb2.get_heartbeat_event().await.unwrap(); 46 | let lk = G_REVERSE_URL.read().await; 47 | for uri in &*lk { 48 | if !uri.starts_with("http") { 49 | continue; 50 | } 51 | let rst = post_to_client(uri,&json_str,kb2.self_id).await; 52 | if rst.is_err() { 53 | log::error!("发送心跳事件到HTTP:`{uri}`失败"); 54 | } 55 | } 56 | } 57 | } 58 | } -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Build Test 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | paths: 8 | - .github/workflows/** 9 | - src/** 10 | - Cargo.toml 11 | 12 | jobs: 13 | test: 14 | name: build project 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: Checkout 18 | uses: actions/checkout@master 19 | 20 | - name: rust_install 21 | uses: actions-rs/toolchain@v1 22 | with: 23 | profile: minimal 24 | toolchain: stable 25 | override: true 26 | 27 | - name: install_cross 28 | run: | 29 | cargo install cross --git https://github.com/cross-rs/cross 30 | 31 | - name: build 32 | run: | 33 | cross build --target i686-pc-windows-gnu --release 34 | cross build --target aarch64-unknown-linux-musl --release 35 | cross build --target i686-unknown-linux-musl --release 36 | cross build --target armv7-unknown-linux-musleabi --release 37 | cross build --target aarch64-linux-android --release 38 | cross build --target x86_64-linux-android --release 39 | 40 | - name: before_upload 41 | run: | 42 | mkdir Release 43 | cp target/i686-pc-windows-gnu/release/kook_onebot.exe Release/kook_onebot_windows_i686.exe 44 | cp target/aarch64-unknown-linux-musl/release/kook_onebot Release/kook_onebot_linux_aarch64 45 | cp target/i686-unknown-linux-musl/release/kook_onebot Release/kook_onebot_linux_i686 46 | cp target/armv7-unknown-linux-musleabi/release/kook_onebot Release/kook_onebot_linux_armv7 47 | cp target/aarch64-linux-android/release/kook_onebot Release/kook_onebot_android_aarch64 48 | cp target/x86_64-linux-android/release/kook_onebot Release/kook_onebot_android_x86_64 49 | 50 | - name: upload file1 51 | uses: actions/upload-artifact@v3 52 | with: 53 | name: kook_onebot_windows_i686.exe 54 | path: 55 | Release/kook_onebot_windows_i686.exe 56 | 57 | - name: upload file2 58 | uses: actions/upload-artifact@v3 59 | with: 60 | name: kook_onebot_linux_aarch64 61 | path: 62 | Release/kook_onebot_linux_aarch64 63 | 64 | - name: upload file3 65 | uses: actions/upload-artifact@v3 66 | with: 67 | name: kook_onebot_linux_i686 68 | path: 69 | Release/kook_onebot_linux_i686 70 | 71 | - name: upload file4 72 | uses: actions/upload-artifact@v3 73 | with: 74 | name: kook_onebot_linux_armv7 75 | path: 76 | Release/kook_onebot_linux_armv7 77 | 78 | - name: upload file file5 79 | uses: actions/upload-artifact@v3 80 | with: 81 | name: kook_onebot_android_aarch64 82 | path: 83 | Release/kook_onebot_android_aarch64 84 | 85 | - name: upload file file6 86 | uses: actions/upload-artifact@v3 87 | with: 88 | name: kook_onebot_android_x86_64 89 | path: 90 | Release/kook_onebot_android_x86_64 91 | -------------------------------------------------------------------------------- /src/onebot_ws.rs: -------------------------------------------------------------------------------- 1 | use std::sync::{Arc, atomic::AtomicI64}; 2 | 3 | use futures_util::{SinkExt, StreamExt}; 4 | 5 | use crate::{G_KOOK_TOKEN, G_SELF_ID, G_ONEBOT_RX}; 6 | 7 | // 正向ws 8 | async fn deal_ws(uid:&str, 9 | mut write_half: futures_util::stream::SplitSink, hyper_tungstenite::tungstenite::Message>, 10 | mut read_half: futures_util::stream::SplitStream> 11 | ) -> Result<(), Box> { 12 | 13 | let kb = crate::kook_onebot::KookOnebot { 14 | token: G_KOOK_TOKEN.read().await.to_owned(), 15 | self_id: G_SELF_ID.read().await.to_owned(), 16 | sn: Arc::new(AtomicI64::new(0)), 17 | }; 18 | 19 | // 获得升级后的ws流 20 | let (tx, mut rx) = tokio::sync::mpsc::channel::(60); 21 | { 22 | let mut lk = G_ONEBOT_RX.write().await; 23 | lk.insert(uid.to_owned(), (tx.clone(),"".to_owned())); 24 | } 25 | let _guard = scopeguard::guard(uid.to_owned(), |uid: String| { 26 | tokio::spawn(async move { 27 | let mut lk = G_ONEBOT_RX.write().await; 28 | lk.remove(&uid); 29 | }); 30 | }); 31 | 32 | 33 | // 向onebot客户端发送生命周期包 34 | let life_event = kb.get_lifecycle_event().await?; 35 | write_half.send(hyper_tungstenite::tungstenite::Message::Text(life_event)).await?; 36 | 37 | let heartbeat = kb.get_heartbeat_event().await?; 38 | let tx_copy = tx.clone(); 39 | tokio::spawn(async move { 40 | loop { 41 | tokio::time::sleep(tokio::time::Duration::from_secs(5)).await; 42 | let ret = tx_copy.send(heartbeat.clone()).await; 43 | if ret.is_err() { 44 | log::error!("ONEBOT_WS心跳包发送出错:{}",ret.err().unwrap()); 45 | break; 46 | } 47 | } 48 | }); 49 | 50 | tokio::spawn(async move { 51 | // 将收到的事件发送到onebot客户端 52 | while let Some(msg) = rx.recv().await { 53 | let ret = write_half.send(hyper_tungstenite::tungstenite::Message::Text(msg)).await; 54 | if ret.is_err() { 55 | log::error!("ONEBOT_WS数据发送出错:{}",ret.err().unwrap()); 56 | break; 57 | } 58 | } 59 | }); 60 | 61 | // 接收来自onebot客户端的调用 62 | while let Some(msg_t) = read_half.next().await { 63 | let msg = msg_t?; 64 | if ! msg.is_text() { 65 | continue; 66 | } 67 | let msg_text = msg.to_text()?; 68 | // 处理onebot的api调用 69 | let (_,ret) = kb.deal_onebot(msg_text).await; 70 | tx.send(ret).await?; 71 | } 72 | Ok(()) 73 | } 74 | 75 | 76 | pub async fn deal_onebot_ws(uid:&str,websocket: hyper_tungstenite::HyperWebsocket) -> Result<(), Box> { 77 | let ws_stream: hyper_tungstenite::WebSocketStream = websocket.await?; 78 | let (write_half, read_half ) = futures_util::StreamExt::split(ws_stream); 79 | deal_ws(uid,write_half,read_half).await?; 80 | Ok(()) 81 | } 82 | 83 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # 此项目停止维护 2 | 3 | 此项目停止维护,您有如下4种迁移方法: 4 | 5 | 1:寻找其它同类项目 6 | 7 | 2:fork一份此项目自己维护 8 | 9 | 3:继续使用,并每天祈祷不会出问题 10 | 11 | onebot-11并不是一个完善的协议,在错误的路上走得越远,越难以实现one bot。 12 | 13 | 应该完善协议,而不是继续加强生态。 14 | 15 | 为一些原作者都已经放弃的插件或协议编写兼容器不是我的初衷。 16 | 17 | # KookOnebot 18 | 19 | 为kook实现[onebot11](https://github.com/botuniverse/onebot-11)协议! 20 | 21 | 注意,群聊==频道,好友==私信 22 | 23 | 我们为此项目创建了一个KOOK群,欢迎来玩,邀请链接:https://kook.top/3SEwQj 24 | 25 | [红色问答](https://github.com/super1207/redreply)、[MiraiCQ](https://github.com/super1207/MiraiCQ) 与此项目配合更佳,欢迎加入为它们创建的QQ群:920220179、556515826 26 | 27 | ### 如果你想在[Mirai & MrXiaoM/Overflow](https://github.com/MrXiaoM/Overflow)中体验kookonebot,暂时请使用[github actions](https://github.com/super1207/KookOneBot/actions)中的版本。 28 | 29 | 30 | ## 配置文件 31 | 32 | config.json 例子: 33 | 34 | ```json 35 | { 36 | "web_port": 8080, 37 | "web_host": "127.0.0.1", 38 | "kook_token": "1/MTUyNDY=/snqjxHpGZFdEM50wyZLOpg==", 39 | "access_token": "123456", 40 | "reverse_uri": [ 41 | "http://127.0.0.1:55001/OlivOSMsgApi/pp/onebot/default", 42 | "ws://127.0.0.1:5555/some" 43 | ], 44 | "secret":"" 45 | } 46 | ``` 47 | 48 | 解释: 49 | 50 | web_port:正向http和正向websocket需要这个端口号,若不使用正向http和正向websocket,填0即可。 51 | 52 | web_host:正向http和正向websocket需要这个,若想要外网访问,填`"0.0.0.0"`,若不使用正向http和正向websocket,填`""`即可。 53 | 54 | kook_token:kook的token,请到此处去获得:[KOOK 开发者中心 - 机器人 (kookapp.cn)](https://developer.kookapp.cn/app/index) 55 | 56 | access_token:正向http、正向websocket、反向websocket需要,若不需要访问密码,填`""`即可。 57 | 58 | reverse_uri:反向http和反向websocket需要这个,若不需要反向http或反向ws,填`[]`即可。 59 | 60 | secret:反向http需要的HMAC签名,用来验证上报的数据确实来自OneBot,若不需要,填`""`即可。 61 | 62 | **注意:所有的字段都是必填的,不可省略!!!** 63 | 64 | ## 网络协议实现 65 | 66 | 正向ws 67 | 68 | 正向http,端口号和正向ws相同,自动识别! 69 | 70 | 反向ws 71 | 72 | 反向 http 73 | 74 | ## API 75 | 76 | ### 已实现 77 | 78 | #### send_group_msg 发送群消息 79 | 80 | 目前支持文字、图片、at、回复、自定义音乐分享、qq/网易云音乐分享(使用[故梦api](https://blog.gumengya.com/api.html))、语音 81 | 82 | #### send_private_msg 发送私聊消息 83 | 84 | 目前支持文字、图片、回复、自定义音乐分享、qq/网易云音乐分享(使用[故梦api](https://blog.gumengya.com/api.html))、语音 85 | 86 | #### get_login_info 获取登录号信息 87 | 88 | #### get_stranger_info 获取陌生人信息 89 | 90 | 年龄为0,性别为unknown 91 | 92 | #### get_group_info 获取群信息 93 | 94 | 成员数和最大成员数暂时为0,待研究。 95 | 96 | #### get_group_list 获取群列表 97 | 98 | 成员数和最大成员数暂时为0,待研究。如果你拥有的频道数量大于150,则此api调用失败。 99 | 100 | #### get_group_member_info 获取群成员信息 101 | 102 | 成员信息尽力提供,服务器拥有者,被认为是owner,若有加入某角色,被认为是admin,否则被认为是member。 103 | 104 | #### get_group_member_list 获取群成员列表 105 | 106 | 成员信息尽力提供,服务器拥有者,被认为是owner,若有加入某角色,被认为是admin,否则被认为是member。 107 | 108 | #### send_msg 发送消息 109 | 110 | #### can_send_image 检查是否可以发送图片 111 | 112 | 直接返回可以 113 | 114 | #### get_status 获取运行状态 115 | 116 | #### get_version_info 获取版本信息 117 | 118 | #### set_group_kick 群组踢人 119 | 120 | 实际上是踢出服务器 121 | 122 | #### delete_msg 撤回消息 123 | 124 | #### set_group_leave 退出群组 125 | 126 | 实际上会退出服务器 127 | 128 | #### can_send_record 检查是否可以发送语音 129 | 130 | 直接返回可以 131 | 132 | #### set_group_name 设置群名 133 | 134 | #### set_group_card 设置群名片(群备注) 135 | 136 | 实际上会设置该用户在服务器中的名字 137 | 138 | #### get_friend_list 获取好友列表 139 | 140 | 实际上获取在bot的私信列表上的人 141 | 142 | #### get_cookies 获取机器人的cookies 143 | 144 | `domain`传`token`,响应数据中的`cookies`为kook的token,你可以用这个api来实现一些其它非onebot标准的功能。 145 | 146 | 147 | ### 正在研究 148 | 149 | 150 | get_msg 获取消息(可能需要数据库支持才行) 151 | 152 | set_group_add_request 处理加群请求 153 | 154 | ### 不实现 155 | 156 | send_like 发送好友赞(kook没有这个) 157 | 158 | get_group_honor_info 获取群荣誉信息(kook没有群荣誉) 159 | 160 | get_cookies 获取 Cookies(kook没有这个) 161 | 162 | get_csrf_token 获取 CSRF Token(kook没有这个) 163 | 164 | get_credentials 获取 QQ 相关接口凭证(kook没有这个) 165 | 166 | clean_cache 清理缓存(没必要) 167 | 168 | set_restart 重启 OneBot 实现(没必要) 169 | 170 | set_group_anonymous 群组匿名(kook没有匿名) 171 | 172 | get_forward_msg 获取合并转发消息(kook没有合并转发) 173 | 174 | get_image 获取图片(此api已经过时) 175 | 176 | set_group_anonymous_ban 群组匿名用户禁言(kook没有匿名) 177 | 178 | set_friend_add_request 处理加好友请求(bot不能被加好友) 179 | 180 | get_record 获取语音(此api已经过时) 181 | 182 | set_group_special_title 设置群组专属头衔(kook没有这个) 183 | 184 | set_group_ban 群组单人禁言(kook的权限机制不好实现这个) 185 | 186 | set_group_whole_ban 群组全员禁言(kook的权限机制不好实现这个) 187 | 188 | set_group_admin 群组设置管理员(kook的权限机制不好实现这个) 189 | 190 | set_group_add_request 处理加群邀请(kook的bot被邀请就会同意,不需要处理) 191 | 192 | ## 事件 193 | 194 | ### 已实现 195 | 196 | #### 群消息 197 | 198 | 目前接收文字、图片、at、回复、语音 199 | 200 | #### 私聊消息 201 | 202 | 目前接收文字、图片、回复、语音 203 | 204 | #### 生命周期 205 | 206 | 仅connect(反向http没有此事件)。 207 | 208 | #### 群成员减少 209 | 210 | sub_type只支持leave,无论是被踢还是自己退出,都为leave,operator_id与user_id相同,均为退出的人 211 | 212 | bot自己被踢不会触发此事件。 213 | 214 | #### 群成员增加 215 | 216 | sub_type只支持approve,无论是被邀请还是自己加入,均为approve,operator_id与user_id相同,均为加入的人 217 | 218 | #### 群消息撤回 219 | 220 | 无法获得正确的operator_id,kook没有提供 221 | 222 | #### 好友消息撤回 223 | 224 | #### 群文件上传 225 | 226 | busid 始终为0,也没啥用 227 | 228 | #### 心跳 229 | 230 | 目前固定为5秒一次 231 | 232 | ### 正在研究 233 | 234 | 加群请求 235 | 236 | ### 不实现 237 | 238 | 好友添加(bot不能被加好友) 239 | 240 | 群内戳一戳(kook没有这个) 241 | 242 | 群红包运气王(kook没有这个) 243 | 244 | 群成员荣誉变更(kook没有这个) 245 | 246 | 加好友请求(kook没有这个) 247 | 248 | 群管理员变动(kook的权限机制不好实现这个) 249 | 250 | 群禁言(kook的权限机制不好实现这个) 251 | 252 | 加群邀请(bot被邀请就会自己同意) 253 | 254 | ## 自行编译 255 | 256 | 注意,通常情况下,如果您不打算参与此项目的开发,就无需自行编译,请直接到release(或者github action)中去下载。 257 | 258 | - 安装好[rust编译环境](https://www.rust-lang.org/)。 259 | 260 | - 在windows下(powershell): 261 | 262 | ```powershell 263 | $ENV:RUSTFLAGS='-C target-feature=+crt-static';cargo run --target=i686-pc-windows-msvc --release 264 | ``` 265 | - 在linux下(需要先[安装docker](https://docs.docker.com/engine/install/)): 266 | ```bash 267 | cargo install cross --git https://github.com/cross-rs/cross 268 | cross build --target i686-unknown-linux-musl --release 269 | ``` 270 | -------------------------------------------------------------------------------- /src/onebot_http.rs: -------------------------------------------------------------------------------- 1 | use std::{collections::HashMap, sync::{atomic::AtomicI64, Arc}}; 2 | 3 | use crate::{G_KOOK_TOKEN, G_SELF_ID, G_ACCESS_TOKEN}; 4 | 5 | 6 | pub fn get_params_from_uri(uri:&hyper::Uri) -> HashMap { 7 | let mut ret_map = HashMap::new(); 8 | if uri.query().is_none() { 9 | return ret_map; 10 | } 11 | let query_str = uri.query().unwrap(); 12 | let query_vec = query_str.split("&"); 13 | for it in query_vec { 14 | if it == "" { 15 | continue; 16 | } 17 | let index_opt = it.find("="); 18 | if index_opt.is_some() { 19 | let k_rst = urlencoding::decode(it.get(0..index_opt.unwrap()).unwrap()); 20 | let v_rst = urlencoding::decode(it.get(index_opt.unwrap() + 1..).unwrap()); 21 | if k_rst.is_err() || v_rst.is_err() { 22 | continue; 23 | } 24 | ret_map.insert(k_rst.unwrap().to_string(), v_rst.unwrap().to_string()); 25 | } 26 | else { 27 | let k_rst = urlencoding::decode(it); 28 | if k_rst.is_err() { 29 | continue; 30 | } 31 | ret_map.insert(k_rst.unwrap().to_string(),"".to_owned()); 32 | } 33 | } 34 | ret_map 35 | } 36 | 37 | 38 | pub async fn deal_onebot_http(mut request: hyper::Request) -> Result, Box> { 39 | let url_path = request.uri().path().to_owned(); 40 | let action = url_path.get(1..).ok_or("get action from url_path err")?; 41 | let kb = crate::kook_onebot::KookOnebot { 42 | token: G_KOOK_TOKEN.read().await.to_owned(), 43 | self_id: G_SELF_ID.read().await.to_owned(), 44 | sn: Arc::new(AtomicI64::new(0)), 45 | }; 46 | let method = request.method().to_string(); 47 | let params; 48 | if method == "GET" { 49 | let mp = get_params_from_uri(request.uri()); 50 | params = serde_json::json!(mp); 51 | }else if method == "POST" { 52 | let headers_map = request.headers(); 53 | if let Some(content_type) = headers_map.get("content-type") { 54 | if content_type.to_str()? == "application/json" { 55 | let body = hyper::body::to_bytes(request.body_mut()).await?; 56 | match serde_json::from_slice(&body) { 57 | Ok(v) => { 58 | params = v; 59 | } , 60 | Err(_) => { 61 | let ret = serde_json::json!({ 62 | "status":"failed", 63 | "retcode":1400, 64 | }); 65 | log::error!("ONEBOT动作调用出错:`INVALID JSON`"); 66 | let mut res = hyper::Response::new(hyper::Body::from(ret.to_string())); 67 | (*res.status_mut()) = hyper::StatusCode::BAD_REQUEST; 68 | res.headers_mut().insert("Content-Type", hyper::http::HeaderValue::from_static("application/json")); 69 | return Ok(res); 70 | }, 71 | } 72 | } else if content_type.to_str()? == "application/x-www-form-urlencoded" { 73 | let body = hyper::body::to_bytes(request.body_mut()).await?; 74 | params = url::form_urlencoded::parse(&body).collect::(); 75 | } else { 76 | let ret = serde_json::json!({ 77 | "status":"failed", 78 | "retcode":1406, 79 | }); 80 | log::error!("ONEBOT动作调用出错:`HTTP 406`"); 81 | let mut res = hyper::Response::new(hyper::Body::from(ret.to_string())); 82 | (*res.status_mut()) = hyper::StatusCode::NOT_ACCEPTABLE; 83 | res.headers_mut().insert("Content-Type", hyper::http::HeaderValue::from_static("application/json")); 84 | return Ok(res); 85 | } 86 | } else { 87 | let body = hyper::body::to_bytes(request.body_mut()).await?; 88 | params = url::form_urlencoded::parse(&body).collect::(); 89 | } 90 | } else { 91 | let res = hyper::Response::new(hyper::Body::from(vec![])); 92 | return Ok(res); 93 | } 94 | let js = serde_json::json!({ 95 | "action":action, 96 | "params": params 97 | }); 98 | let (http_code,ret) = kb.deal_onebot(&js.to_string()).await; 99 | let mut res = hyper::Response::new(hyper::Body::from(ret)); 100 | if http_code == 404 { 101 | (*res.status_mut()) = hyper::StatusCode::NOT_FOUND; 102 | } 103 | res.headers_mut().insert("Content-Type", hyper::http::HeaderValue::from_static("application/json")); 104 | return Ok(res); 105 | } 106 | 107 | 108 | pub async fn check_auth(request: &hyper::Request) -> Result> { 109 | // 获得当前的访问密钥 110 | let mut is_pass = false; 111 | let g_access_token = G_ACCESS_TOKEN.read().await.clone(); 112 | let headers_map = request.headers(); 113 | if !g_access_token.is_empty() { 114 | // 两个地方任何有一个满足要求,则通过 115 | { 116 | let access_token:String; 117 | if let Some(token) = headers_map.get("Authorization") { 118 | access_token = token.to_str()?.to_owned(); 119 | } 120 | else { 121 | access_token = "".to_owned(); 122 | } 123 | if access_token == "Bearer ".to_owned() + &g_access_token { 124 | is_pass = true; 125 | } 126 | } 127 | { 128 | let uri = request.uri().clone(); 129 | let mp = get_params_from_uri(&uri); 130 | if let Some(val) = mp.get("access_token") { 131 | if &g_access_token == val { 132 | is_pass = true; 133 | } 134 | } 135 | 136 | } 137 | 138 | } else { 139 | is_pass = true; 140 | } 141 | Ok(is_pass) 142 | } -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | 2 | 3 | pub mod kook_onebot; 4 | pub mod cqtool; 5 | mod msgid_tool; 6 | mod config_tool; 7 | mod onebot_http; 8 | mod onebot_ws; 9 | mod onebot_http_rev; 10 | mod onebot_ws_rev; 11 | 12 | #[macro_use] 13 | extern crate lazy_static; 14 | 15 | lazy_static! { 16 | pub static ref G_SELF_ID:RwLock = RwLock::new(0); 17 | pub static ref G_KOOK_TOKEN:RwLock = RwLock::new(String::new()); 18 | pub static ref G_ONEBOT_RX:RwLock,String)>> = RwLock::new(HashMap::new()); 19 | pub static ref G_ACCESS_TOKEN:RwLock = RwLock::new(String::new()); 20 | pub static ref G_SECERT:RwLock = RwLock::new(String::new()); 21 | pub static ref G_REVERSE_URL:RwLock> = RwLock::new(Vec::new()); 22 | } 23 | 24 | use std::{collections::HashMap, sync::{Arc, atomic::AtomicI64}}; 25 | use hyper_tungstenite::hyper; 26 | use kook_onebot::KookOnebot; 27 | use time::UtcOffset; 28 | use ::time::format_description; 29 | use tokio::sync::RwLock; 30 | use hyper::service::make_service_fn; 31 | use crate::config_tool::read_config; 32 | 33 | 34 | async fn connect_handle(request: hyper::Request) -> Result, Box> { 35 | 36 | let is_pass = onebot_http::check_auth(&request).await?; 37 | 38 | if is_pass == false { 39 | log::error!("WS或HTTP鉴权失败!"); 40 | let mut res = hyper::Response::new(hyper::Body::from(vec![])); 41 | *res.status_mut() = hyper::StatusCode::FORBIDDEN; 42 | return Ok(res); 43 | } 44 | // 处理正向ws 45 | if hyper_tungstenite::is_upgrade_request(&request) { 46 | let uid = uuid::Uuid::new_v4().to_string(); 47 | log::warn!("接收到WS连接`{uid}`"); 48 | let (response, websocket): (hyper::Response, hyper_tungstenite::HyperWebsocket) = hyper_tungstenite::upgrade(request, None)?; 49 | tokio::spawn(async move { 50 | let ret = onebot_ws::deal_onebot_ws(&uid,websocket).await; 51 | log::error!("WS断开连接:`{uid}`,`{ret:?}`"); 52 | }); 53 | return Ok(response); 54 | } else { 55 | // 处理正向http 56 | let rst = onebot_http::deal_onebot_http(request).await?; 57 | return Ok(rst); 58 | } 59 | } 60 | 61 | 62 | #[tokio::main] 63 | async fn main() -> Result<(), Box> { 64 | 65 | // 初始化日志 66 | let format = "[year]-[month]-[day] [hour]:[minute]:[second].[subsecond digits:3]"; 67 | 68 | // 获得utc偏移 69 | let utc_offset; 70 | if let Ok(v) = UtcOffset::current_local_offset() { 71 | utc_offset = v; 72 | } else { 73 | // 中国是东八区,所以这里写8 hour 74 | utc_offset = UtcOffset::from_hms(8,0,0).unwrap(); 75 | } 76 | 77 | tracing_subscriber::fmt() 78 | .with_timer(tracing_subscriber::fmt::time::OffsetTime::new( 79 | utc_offset, 80 | format_description::parse(format).unwrap(), 81 | )).with_max_level(tracing::Level::INFO) 82 | .init(); 83 | 84 | log::warn!("欢迎使用KookOnebot by super1207!!! v0.1.0"); 85 | 86 | log::warn!("开源地址:https://github.com/super1207/KookOneBot"); 87 | 88 | log::warn!("正在加载配置文件..."); 89 | 90 | let config_file = read_config().await.unwrap(); 91 | 92 | let kook_token = config_file.get("kook_token").expect("配置文件缺少 kook_token 字段").as_str().unwrap(); 93 | let mut web_host = config_file.get("web_host").expect("配置文件缺少 web_host 字段").as_str().unwrap(); 94 | let web_port = config_file.get("web_port").expect("配置文件缺少 web_port 字段").as_u64().unwrap(); 95 | let secret = config_file.get("secret").expect("配置文件缺少 secret 字段").as_str().unwrap(); 96 | let access_token = config_file.get("access_token").expect("配置文件缺少 access_token 字段").as_str().unwrap(); 97 | let reverse_url = config_file.get("reverse_uri").expect("配置文件缺少 reverse_uri 字段").as_array().unwrap(); 98 | 99 | for url in reverse_url { 100 | let url_str = url.as_str().unwrap(); 101 | G_REVERSE_URL.write().await.push(url_str.to_owned()); 102 | } 103 | 104 | if web_host == "localhost" { 105 | web_host = "127.0.0.1"; 106 | } 107 | 108 | *G_ACCESS_TOKEN.write().await = access_token.to_owned(); 109 | 110 | *G_SECERT.write().await = secret.to_owned(); 111 | 112 | 113 | log::warn!("加载配置文件成功"); 114 | 115 | let mut kb = KookOnebot { 116 | token:kook_token.to_owned(), 117 | self_id:0, 118 | sn: Arc::new(AtomicI64::new(0)), 119 | }; 120 | log::warn!("正在登录中..."); 121 | let login_info = kb.get_login_info().await?; 122 | 123 | 124 | log::warn!("欢迎 `{}`({})!",login_info.nickname,login_info.user_id); 125 | let self_id = login_info.user_id; 126 | kb.self_id = self_id; 127 | { 128 | let mut lk = G_SELF_ID.write().await; 129 | (*lk) = kb.self_id; 130 | let mut lk = G_KOOK_TOKEN.write().await; 131 | (*lk) = kb.token.to_owned(); 132 | } 133 | 134 | 135 | let kb_t = kb.clone(); 136 | tokio::spawn(async move { 137 | loop { 138 | let err = kb_t.connect().await; 139 | log::error!("KOOK连接断开:{err:?}"); 140 | tokio::time::sleep(tokio::time::Duration::from_secs(5)).await; 141 | } 142 | }); 143 | 144 | 145 | // 反向 http 心跳 146 | tokio::spawn(async move { 147 | onebot_http_rev::deal_heartbeat(kb).await; 148 | }); 149 | 150 | // 反向ws 151 | tokio::spawn(async move { 152 | onebot_ws_rev::deal_ws_rev().await; 153 | }); 154 | 155 | 156 | if web_host != "" && web_port != 0{ 157 | let web_uri = format!("{web_host}:{web_port}"); 158 | log::warn!("监听地址:{web_uri}"); 159 | let addr = web_uri.parse::()?; 160 | let bd_rst = hyper::Server::try_bind(&addr); 161 | if bd_rst.is_ok() { 162 | // 启动服务 163 | let ret = bd_rst.unwrap().serve(make_service_fn(|_conn| async { 164 | Ok::<_, std::convert::Infallible>(hyper::service::service_fn(connect_handle)) 165 | })).await; 166 | if let Err(err) = ret{ 167 | panic!("绑定端口号失败:{}",err) 168 | } 169 | }else { 170 | panic!("绑定端口号失败"); 171 | } 172 | } 173 | loop { 174 | tokio::time::sleep(tokio::time::Duration::from_secs(5)).await; 175 | } 176 | } -------------------------------------------------------------------------------- /src/onebot_ws_rev.rs: -------------------------------------------------------------------------------- 1 | use std::{sync::{Arc, atomic::AtomicI64}, str::FromStr}; 2 | 3 | use futures_util::{StreamExt, SinkExt}; 4 | use hyper::http::{HeaderValue, HeaderName}; 5 | use tokio::net::TcpStream; 6 | 7 | use crate::{G_REVERSE_URL, G_ONEBOT_RX, G_KOOK_TOKEN, G_SELF_ID, G_ACCESS_TOKEN, onebot_http::get_params_from_uri}; 8 | 9 | 10 | // 反向ws 11 | async fn deal_ws2(url:&str, 12 | mut write_half:futures_util::stream::SplitSink>, tungstenite::Message>, 13 | mut read_half: futures_util::stream::SplitStream>> 14 | ) -> Result<(), Box> { 15 | 16 | let kb = crate::kook_onebot::KookOnebot { 17 | token: G_KOOK_TOKEN.read().await.to_owned(), 18 | self_id: G_SELF_ID.read().await.to_owned(), 19 | sn: Arc::new(AtomicI64::new(0)), 20 | }; 21 | let uid = uuid::Uuid::new_v4().to_string(); 22 | 23 | // 获得升级后的ws流 24 | let (tx, mut rx) = tokio::sync::mpsc::channel::(60); 25 | { 26 | let mut lk = G_ONEBOT_RX.write().await; 27 | lk.insert(url.to_string(), (tx.clone(),uid.clone())); 28 | } 29 | 30 | let url_t = url.to_owned(); 31 | let _guard = scopeguard::guard(uid.to_owned(), |uid: String| { 32 | tokio::spawn(async move { 33 | let mut lk = G_ONEBOT_RX.write().await; 34 | if let Some(v) = lk.get(&url_t) { 35 | if v.1 == uid { 36 | lk.remove(&url_t); 37 | } 38 | } 39 | }); 40 | }); 41 | 42 | 43 | // 向onebot客户端发送生命周期包 44 | let life_event = kb.get_lifecycle_event().await?; 45 | write_half.send(tungstenite::Message::Text(life_event)).await?; 46 | 47 | let heartbeat = kb.get_heartbeat_event().await?; 48 | let tx_copy = tx.clone(); 49 | let url_t = url.to_owned(); 50 | let uid_t = uid.to_owned(); 51 | tokio::spawn(async move { 52 | loop { 53 | tokio::time::sleep(tokio::time::Duration::from_secs(5)).await; 54 | let ret = tx_copy.send(heartbeat.clone()).await; 55 | if ret.is_err() { 56 | let mut lk = G_ONEBOT_RX.write().await; 57 | if let Some(v) = lk.get(&url_t) { 58 | if v.1 == uid_t { 59 | lk.remove(&url_t); 60 | } 61 | } 62 | log::error!("ONEBOT_WS_REV心跳包发送出错:{}",ret.err().unwrap()); 63 | break; 64 | } 65 | } 66 | }); 67 | 68 | let url_t = url.to_owned(); 69 | let uid_t = uid.to_owned(); 70 | tokio::spawn(async move { 71 | // 将收到的事件发送到onebot客户端 72 | while let Some(msg) = rx.recv().await { 73 | let ret = write_half.send(tungstenite::Message::Text(msg)).await; 74 | if ret.is_err() { 75 | let mut lk = G_ONEBOT_RX.write().await; 76 | if let Some(v) = lk.get(&url_t) { 77 | if v.1 == uid_t { 78 | lk.remove(&url_t); 79 | } 80 | } 81 | log::error!("ONEBOT_WS_REV数据发送出错:{}",ret.err().unwrap()); 82 | break; 83 | } 84 | } 85 | }); 86 | 87 | // 接收来自onebot客户端的调用 88 | while let Some(msg_t) = read_half.next().await { 89 | 90 | // 不存在连接,这退出接收 91 | { 92 | let lk = G_ONEBOT_RX.read().await; 93 | if let Some(v) = lk.get(url) { 94 | if v.1 != uid { 95 | break; 96 | } 97 | }else{ 98 | break; 99 | } 100 | } 101 | 102 | let msg = msg_t?; 103 | if ! msg.is_text() { 104 | continue; 105 | } 106 | let msg_text = msg.to_text()?; 107 | // 处理onebot的api调用 108 | let (_,ret) = kb.deal_onebot(msg_text).await; 109 | tx.send(ret).await?; 110 | } 111 | Ok(()) 112 | } 113 | 114 | 115 | async fn onebot_rev_ws(ws_url:String) { 116 | loop { 117 | let mut request = tungstenite::client::IntoClientRequest::into_client_request(ws_url.clone()).unwrap(); 118 | 119 | // 配置文件 120 | let mut access_token = G_ACCESS_TOKEN.read().await.clone(); 121 | 122 | // url 123 | let mp: std::collections::HashMap = get_params_from_uri(&hyper::Uri::from_str(&ws_url).unwrap()); 124 | if let Some(val) = mp.get("access_token") { 125 | access_token = val.to_owned(); 126 | } 127 | 128 | // 无论如何,都添加Authorization头 129 | if access_token != "" { 130 | request.headers_mut().insert("Authorization", HeaderValue::from_str(&format!("Token {}",access_token)).unwrap()); 131 | } 132 | 133 | let self_id = G_SELF_ID.read().await.clone(); 134 | request.headers_mut().append(HeaderName::from_str("X-Self-ID").unwrap(), HeaderValue::from_str(&self_id.to_string()).unwrap()); 135 | request.headers_mut().append(HeaderName::from_str("X-Client-Role").unwrap(), HeaderValue::from_str("Universal").unwrap()); 136 | request.headers_mut().append(HeaderName::from_str("user-agent").unwrap(), HeaderValue::from_str("CQHttp/4.15.0").unwrap()); 137 | let rst; 138 | if ws_url.starts_with("wss://") { 139 | let port_opt = request.uri().port(); 140 | let port; 141 | if port_opt.is_none() { 142 | port = 443; 143 | }else { 144 | port = port_opt.unwrap().into(); 145 | } 146 | let addr = format!("{}:{}",request.uri().host().unwrap(),port); 147 | let socket = TcpStream::connect(addr).await.unwrap(); 148 | rst = tokio_tungstenite::client_async_tls(request, socket).await; 149 | }else { 150 | rst = tokio_tungstenite::connect_async(request).await; 151 | } 152 | 153 | if rst.is_err() { 154 | log::error!("连接到WS_REV:{ws_url} 失败"); 155 | tokio::time::sleep(tokio::time::Duration::from_secs(5)).await; 156 | continue; 157 | } 158 | let (ws_stream, _) = rst.unwrap(); 159 | let (write_halt,read_halt) = ws_stream.split(); 160 | let rst = deal_ws2(&ws_url,write_halt,read_halt).await; 161 | if rst.is_err() { 162 | log::error!("WS_REV:{ws_url} 断开连接"); 163 | } 164 | tokio::time::sleep(tokio::time::Duration::from_secs(5)).await; 165 | } 166 | } 167 | 168 | 169 | pub async fn deal_ws_rev() { 170 | let urls = G_REVERSE_URL.read().await.clone(); 171 | for url in &urls { 172 | if !url.starts_with("ws") { 173 | continue; 174 | } 175 | let ws_url = url.clone(); 176 | tokio::spawn(async { 177 | onebot_rev_ws(ws_url).await; 178 | }); 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /src/cqtool.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | 4 | fn cq_text_encode(data:&str) -> String { 5 | let mut ret_str:String = String::new(); 6 | for ch in data.chars() { 7 | if ch == '&' { 8 | ret_str += "&"; 9 | } 10 | else if ch == '[' { 11 | ret_str += "["; 12 | } 13 | else if ch == ']' { 14 | ret_str += "]"; 15 | } 16 | else{ 17 | ret_str.push(ch); 18 | } 19 | } 20 | return ret_str; 21 | } 22 | 23 | pub fn cq_params_encode(data:&str) -> String { 24 | let mut ret_str:String = String::new(); 25 | for ch in data.chars() { 26 | if ch == '&' { 27 | ret_str += "&"; 28 | } 29 | else if ch == '[' { 30 | ret_str += "["; 31 | } 32 | else if ch == ']' { 33 | ret_str += "]"; 34 | } 35 | else if ch == ',' { 36 | ret_str += ","; 37 | } 38 | else{ 39 | ret_str.push(ch); 40 | } 41 | } 42 | return ret_str; 43 | } 44 | 45 | pub fn to_json_str(val:&serde_json::Value) -> String { 46 | if val.is_i64() { 47 | return val.as_i64().unwrap().to_string(); 48 | } 49 | if val.is_u64() { 50 | return val.as_u64().unwrap().to_string(); 51 | } 52 | if val.is_string() { 53 | return val.as_str().unwrap().to_string(); 54 | } 55 | return "".to_owned(); 56 | } 57 | 58 | pub fn arr_to_cq_str(msg_json: & serde_json::Value) -> Result> { 59 | let mut ret:String = String::new(); 60 | if msg_json.is_string() { 61 | return Ok(msg_json.as_str().unwrap().to_owned()); 62 | } 63 | for i in 0..msg_json.as_array().ok_or("message不是array")?.len() { 64 | let tp = msg_json[i].get("type").ok_or("消息中缺少type字段")?.as_str().ok_or("type字段不是str")?; 65 | let nodes = &msg_json[i].get("data").ok_or("json中缺少data字段")?; 66 | if tp == "text" { 67 | let temp = nodes.get("text").ok_or("消息中缺少text字段")?.as_str().ok_or("消息中text字段不是str")?; 68 | ret.push_str(cq_text_encode(temp).as_str()); 69 | }else{ 70 | let mut cqcode = String::from("[CQ:".to_owned() + tp + ","); 71 | if nodes.is_object() { 72 | for j in nodes.as_object().ok_or("msg nodes 不是object")? { 73 | let k = j.0; 74 | let v = to_json_str(j.1); 75 | cqcode.push_str(k); 76 | cqcode.push('='); 77 | cqcode.push_str(cq_params_encode(&v).as_str()); 78 | cqcode.push(','); 79 | } 80 | } 81 | let n= &cqcode[0..cqcode.len()-1]; 82 | let cqcode_out = n.to_owned() + "]"; 83 | ret.push_str(cqcode_out.as_str()); 84 | } 85 | } 86 | return Ok(ret); 87 | } 88 | 89 | pub fn str_msg_to_arr(js:&serde_json::Value) -> Result> { 90 | let cqstr = js.as_str().ok_or("can not get str msg")?.chars().collect::>(); 91 | let mut text = "".to_owned(); 92 | let mut type_ = "".to_owned(); 93 | let mut val = "".to_owned(); 94 | let mut key = "".to_owned(); 95 | let mut jsonarr:Vec = vec![]; 96 | let mut cqcode:HashMap = HashMap::new(); 97 | let mut stat = 0; 98 | let mut i = 0usize; 99 | while i < cqstr.len() { 100 | let cur_ch = cqstr[i]; 101 | if stat == 0 { 102 | if cur_ch == '[' { 103 | if i + 4 <= cqstr.len() { 104 | let t = &cqstr[i..i+4]; 105 | if t.starts_with(&['[','C','Q',':']) { 106 | if text.len() != 0 { 107 | let mut node:HashMap = HashMap::new(); 108 | node.insert("type".to_string(), serde_json::json!("text")); 109 | node.insert("data".to_string(), serde_json::json!({"text": text})); 110 | jsonarr.push(serde_json::json!(node)); 111 | text.clear(); 112 | } 113 | stat = 1; 114 | i += 3; 115 | }else { 116 | text.push(cqstr[i]); 117 | } 118 | }else{ 119 | text.push(cqstr[i]); 120 | } 121 | }else if cur_ch == '&' { 122 | if i + 5 <= cqstr.len() { 123 | let t = &cqstr[i..i+5]; 124 | if t.starts_with(&['&','#','9','1',';']) { 125 | text.push('['); 126 | i += 4; 127 | }else if t.starts_with(&['&','#','9','3',';']) { 128 | text.push(']'); 129 | i += 4; 130 | }else if t.starts_with(&['&','a','m','p',';']) { 131 | text.push('&'); 132 | i += 4; 133 | }else { 134 | text.push(cqstr[i]); 135 | } 136 | }else{ 137 | text.push(cqstr[i]); 138 | } 139 | }else{ 140 | text.push(cqstr[i]); 141 | } 142 | }else if stat == 1 { 143 | if cur_ch == ',' { 144 | stat = 2; 145 | }else if cur_ch == '&' { 146 | if i + 5 <= cqstr.len() { 147 | let t = &cqstr[i..i+5]; 148 | if t.starts_with(&['&','#','9','1',';']) { 149 | type_.push('['); 150 | i += 4; 151 | }else if t.starts_with(&['&','#','9','3',';']) { 152 | type_.push(']'); 153 | i += 4; 154 | }else if t.starts_with(&['&','a','m','p',';']) { 155 | type_.push('&'); 156 | i += 4; 157 | }else if t.starts_with(&['&','#','4','4',';']) { 158 | type_.push(','); 159 | i += 4; 160 | }else { 161 | type_.push(cqstr[i]); 162 | } 163 | }else{ 164 | type_.push(cqstr[i]); 165 | } 166 | }else { 167 | type_.push(cqstr[i]); 168 | } 169 | }else if stat == 2 { 170 | if cur_ch == '=' { 171 | stat = 3; 172 | }else if cur_ch == '&' { 173 | if i + 5 <= cqstr.len() { 174 | let t = &cqstr[i..i+5]; 175 | if t.starts_with(&['&','#','9','1',';']) { 176 | key.push('['); 177 | i += 4; 178 | }else if t.starts_with(&['&','#','9','3',';']) { 179 | key.push(']'); 180 | i += 4; 181 | }else if t.starts_with(&['&','a','m','p',';']) { 182 | key.push('&'); 183 | i += 4; 184 | }else if t.starts_with(&['&','#','4','4',';']) { 185 | key.push(','); 186 | i += 4; 187 | }else { 188 | key.push(cqstr[i]); 189 | } 190 | }else{ 191 | key.push(cqstr[i]); 192 | } 193 | }else { 194 | key .push(cqstr[i]); 195 | } 196 | }else if stat == 3 { 197 | if cur_ch == ']'{ 198 | let mut node:HashMap = HashMap::new(); 199 | cqcode.insert(key.clone(), serde_json::json!(val)); 200 | node.insert("type".to_string(), serde_json::json!(type_)); 201 | node.insert("data".to_string(), serde_json::json!(cqcode)); 202 | jsonarr.push(serde_json::json!(node)); 203 | key.clear(); 204 | val.clear(); 205 | text.clear(); 206 | type_.clear(); 207 | cqcode.clear(); 208 | stat = 0; 209 | }else if cur_ch == ',' { 210 | cqcode.insert(key.clone(), serde_json::json!(val)); 211 | key.clear(); 212 | val.clear(); 213 | stat = 2; 214 | }else if cur_ch == '&' { 215 | if i + 5 <= cqstr.len() { 216 | let t = &cqstr[i..i+5]; 217 | if t.starts_with(&['&','#','9','1',';']) { 218 | val.push('['); 219 | i += 4; 220 | }else if t.starts_with(&['&','#','9','3',';']) { 221 | val.push(']'); 222 | i += 4; 223 | }else if t.starts_with(&['&','a','m','p',';']) { 224 | val.push('&'); 225 | i += 4; 226 | }else if t.starts_with(&['&','#','4','4',';']) { 227 | val.push(','); 228 | i += 4; 229 | }else { 230 | val.push(cqstr[i]); 231 | } 232 | }else{ 233 | val.push(cqstr[i]); 234 | } 235 | }else { 236 | val.push(cqstr[i]); 237 | } 238 | } 239 | i += 1; 240 | } 241 | if text.len() != 0 { 242 | let mut node:HashMap = HashMap::new(); 243 | node.insert("type".to_string(), serde_json::json!("text")); 244 | node.insert("data".to_string(), serde_json::json!({"text": text})); 245 | jsonarr.push(serde_json::json!(node)); 246 | } 247 | Ok(serde_json::Value::Array(jsonarr)) 248 | } 249 | 250 | pub fn make_kook_text(text:&str) -> String { 251 | let mut s = String::new(); 252 | for it in text.chars() { 253 | if it == '\\' || it == '*' || it == '~' || it == '[' || it == '(' || it == ')' || it == ']' || it == '-' || it == '>' || it == '`'{ 254 | s.push('\\'); 255 | } 256 | s.push(it); 257 | } 258 | s 259 | } 260 | 261 | fn reformat_dates(before: &str) -> String { 262 | 263 | fn kook_msg_f(msg: &str) -> String { 264 | let mut ret = String::new(); 265 | let mut is_f = false; 266 | for ch in msg.chars() { 267 | if is_f { 268 | is_f = false; 269 | ret.push(ch); 270 | }else if ch == '\\' { 271 | is_f = true 272 | }else { 273 | ret.push(ch); 274 | } 275 | } 276 | return ret; 277 | } 278 | 279 | let mut ret = String::new(); 280 | let sp = before.split("(met)"); 281 | let mut index = 0; 282 | for it in sp{ 283 | if index % 2 == 0 { 284 | ret.push_str(&cq_text_encode(&kook_msg_f(it))); 285 | } else { 286 | if it == "all" { 287 | ret.push_str("[CQ:at,qq=all]"); 288 | }else{ 289 | ret.push_str(&format!("[CQ:at,qq={}]", it)); 290 | } 291 | } 292 | index += 1; 293 | } 294 | ret 295 | } 296 | 297 | pub fn kook_msg_to_cq(msg_type:i64,message:&str) -> Result> { 298 | 299 | let ret_msg; 300 | if msg_type == 2 { // 图片消息 301 | ret_msg = format!("[CQ:image,file={},url={}]",cq_params_encode(&message),cq_params_encode(&message)); 302 | } else { 303 | ret_msg = reformat_dates(message); 304 | } 305 | 306 | Ok(ret_msg) 307 | } -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "addr2line" 7 | version = "0.20.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "f4fa78e18c64fce05e902adecd7a5eed15a5e0a3439f7b0e169f0252214865e3" 10 | dependencies = [ 11 | "gimli", 12 | ] 13 | 14 | [[package]] 15 | name = "adler" 16 | version = "1.0.2" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" 19 | 20 | [[package]] 21 | name = "aho-corasick" 22 | version = "1.0.2" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "43f6cb1bf222025340178f382c426f13757b2960e89779dfcb319c32542a5a41" 25 | dependencies = [ 26 | "memchr", 27 | ] 28 | 29 | [[package]] 30 | name = "autocfg" 31 | version = "1.1.0" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 34 | 35 | [[package]] 36 | name = "backtrace" 37 | version = "0.3.68" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "4319208da049c43661739c5fade2ba182f09d1dc2299b32298d3a31692b17e12" 40 | dependencies = [ 41 | "addr2line", 42 | "cc", 43 | "cfg-if", 44 | "libc", 45 | "miniz_oxide", 46 | "object", 47 | "rustc-demangle", 48 | ] 49 | 50 | [[package]] 51 | name = "base64" 52 | version = "0.21.2" 53 | source = "registry+https://github.com/rust-lang/crates.io-index" 54 | checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d" 55 | 56 | [[package]] 57 | name = "block-buffer" 58 | version = "0.10.4" 59 | source = "registry+https://github.com/rust-lang/crates.io-index" 60 | checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" 61 | dependencies = [ 62 | "generic-array", 63 | ] 64 | 65 | [[package]] 66 | name = "bumpalo" 67 | version = "3.13.0" 68 | source = "registry+https://github.com/rust-lang/crates.io-index" 69 | checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" 70 | 71 | [[package]] 72 | name = "byteorder" 73 | version = "1.4.3" 74 | source = "registry+https://github.com/rust-lang/crates.io-index" 75 | checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" 76 | 77 | [[package]] 78 | name = "bytes" 79 | version = "1.4.0" 80 | source = "registry+https://github.com/rust-lang/crates.io-index" 81 | checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" 82 | 83 | [[package]] 84 | name = "cc" 85 | version = "1.0.79" 86 | source = "registry+https://github.com/rust-lang/crates.io-index" 87 | checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" 88 | 89 | [[package]] 90 | name = "cfg-if" 91 | version = "1.0.0" 92 | source = "registry+https://github.com/rust-lang/crates.io-index" 93 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 94 | 95 | [[package]] 96 | name = "cpufeatures" 97 | version = "0.2.7" 98 | source = "registry+https://github.com/rust-lang/crates.io-index" 99 | checksum = "3e4c1eaa2012c47becbbad2ab175484c2a84d1185b566fb2cc5b8707343dfe58" 100 | dependencies = [ 101 | "libc", 102 | ] 103 | 104 | [[package]] 105 | name = "crc32fast" 106 | version = "1.3.2" 107 | source = "registry+https://github.com/rust-lang/crates.io-index" 108 | checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" 109 | dependencies = [ 110 | "cfg-if", 111 | ] 112 | 113 | [[package]] 114 | name = "crypto-common" 115 | version = "0.1.6" 116 | source = "registry+https://github.com/rust-lang/crates.io-index" 117 | checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" 118 | dependencies = [ 119 | "generic-array", 120 | "typenum", 121 | ] 122 | 123 | [[package]] 124 | name = "data-encoding" 125 | version = "2.4.0" 126 | source = "registry+https://github.com/rust-lang/crates.io-index" 127 | checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308" 128 | 129 | [[package]] 130 | name = "digest" 131 | version = "0.10.7" 132 | source = "registry+https://github.com/rust-lang/crates.io-index" 133 | checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" 134 | dependencies = [ 135 | "block-buffer", 136 | "crypto-common", 137 | "subtle", 138 | ] 139 | 140 | [[package]] 141 | name = "encoding_rs" 142 | version = "0.8.32" 143 | source = "registry+https://github.com/rust-lang/crates.io-index" 144 | checksum = "071a31f4ee85403370b58aca746f01041ede6f0da2730960ad001edc2b71b394" 145 | dependencies = [ 146 | "cfg-if", 147 | ] 148 | 149 | [[package]] 150 | name = "flate2" 151 | version = "1.0.26" 152 | source = "registry+https://github.com/rust-lang/crates.io-index" 153 | checksum = "3b9429470923de8e8cbd4d2dc513535400b4b3fef0319fb5c4e1f520a7bef743" 154 | dependencies = [ 155 | "crc32fast", 156 | "miniz_oxide", 157 | ] 158 | 159 | [[package]] 160 | name = "fnv" 161 | version = "1.0.7" 162 | source = "registry+https://github.com/rust-lang/crates.io-index" 163 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 164 | 165 | [[package]] 166 | name = "form_urlencoded" 167 | version = "1.2.0" 168 | source = "registry+https://github.com/rust-lang/crates.io-index" 169 | checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652" 170 | dependencies = [ 171 | "percent-encoding", 172 | ] 173 | 174 | [[package]] 175 | name = "futures-channel" 176 | version = "0.3.28" 177 | source = "registry+https://github.com/rust-lang/crates.io-index" 178 | checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" 179 | dependencies = [ 180 | "futures-core", 181 | ] 182 | 183 | [[package]] 184 | name = "futures-core" 185 | version = "0.3.28" 186 | source = "registry+https://github.com/rust-lang/crates.io-index" 187 | checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" 188 | 189 | [[package]] 190 | name = "futures-macro" 191 | version = "0.3.28" 192 | source = "registry+https://github.com/rust-lang/crates.io-index" 193 | checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" 194 | dependencies = [ 195 | "proc-macro2", 196 | "quote", 197 | "syn", 198 | ] 199 | 200 | [[package]] 201 | name = "futures-sink" 202 | version = "0.3.28" 203 | source = "registry+https://github.com/rust-lang/crates.io-index" 204 | checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" 205 | 206 | [[package]] 207 | name = "futures-task" 208 | version = "0.3.28" 209 | source = "registry+https://github.com/rust-lang/crates.io-index" 210 | checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" 211 | 212 | [[package]] 213 | name = "futures-util" 214 | version = "0.3.28" 215 | source = "registry+https://github.com/rust-lang/crates.io-index" 216 | checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" 217 | dependencies = [ 218 | "futures-core", 219 | "futures-macro", 220 | "futures-sink", 221 | "futures-task", 222 | "pin-project-lite", 223 | "pin-utils", 224 | "slab", 225 | ] 226 | 227 | [[package]] 228 | name = "generic-array" 229 | version = "0.14.7" 230 | source = "registry+https://github.com/rust-lang/crates.io-index" 231 | checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" 232 | dependencies = [ 233 | "typenum", 234 | "version_check", 235 | ] 236 | 237 | [[package]] 238 | name = "getrandom" 239 | version = "0.2.9" 240 | source = "registry+https://github.com/rust-lang/crates.io-index" 241 | checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4" 242 | dependencies = [ 243 | "cfg-if", 244 | "libc", 245 | "wasi", 246 | ] 247 | 248 | [[package]] 249 | name = "gimli" 250 | version = "0.27.3" 251 | source = "registry+https://github.com/rust-lang/crates.io-index" 252 | checksum = "b6c80984affa11d98d1b88b66ac8853f143217b399d3c74116778ff8fdb4ed2e" 253 | 254 | [[package]] 255 | name = "h2" 256 | version = "0.3.19" 257 | source = "registry+https://github.com/rust-lang/crates.io-index" 258 | checksum = "d357c7ae988e7d2182f7d7871d0b963962420b0678b0997ce7de72001aeab782" 259 | dependencies = [ 260 | "bytes", 261 | "fnv", 262 | "futures-core", 263 | "futures-sink", 264 | "futures-util", 265 | "http", 266 | "indexmap", 267 | "slab", 268 | "tokio", 269 | "tokio-util", 270 | "tracing", 271 | ] 272 | 273 | [[package]] 274 | name = "hashbrown" 275 | version = "0.12.3" 276 | source = "registry+https://github.com/rust-lang/crates.io-index" 277 | checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" 278 | 279 | [[package]] 280 | name = "hermit-abi" 281 | version = "0.3.2" 282 | source = "registry+https://github.com/rust-lang/crates.io-index" 283 | checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b" 284 | 285 | [[package]] 286 | name = "hex" 287 | version = "0.4.3" 288 | source = "registry+https://github.com/rust-lang/crates.io-index" 289 | checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" 290 | 291 | [[package]] 292 | name = "hmac" 293 | version = "0.12.1" 294 | source = "registry+https://github.com/rust-lang/crates.io-index" 295 | checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" 296 | dependencies = [ 297 | "digest", 298 | ] 299 | 300 | [[package]] 301 | name = "http" 302 | version = "0.2.9" 303 | source = "registry+https://github.com/rust-lang/crates.io-index" 304 | checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" 305 | dependencies = [ 306 | "bytes", 307 | "fnv", 308 | "itoa", 309 | ] 310 | 311 | [[package]] 312 | name = "http-body" 313 | version = "0.4.5" 314 | source = "registry+https://github.com/rust-lang/crates.io-index" 315 | checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" 316 | dependencies = [ 317 | "bytes", 318 | "http", 319 | "pin-project-lite", 320 | ] 321 | 322 | [[package]] 323 | name = "http-body" 324 | version = "1.0.0-rc.2" 325 | source = "registry+https://github.com/rust-lang/crates.io-index" 326 | checksum = "951dfc2e32ac02d67c90c0d65bd27009a635dc9b381a2cc7d284ab01e3a0150d" 327 | dependencies = [ 328 | "bytes", 329 | "http", 330 | ] 331 | 332 | [[package]] 333 | name = "http-body-util" 334 | version = "0.1.0-rc.2" 335 | source = "registry+https://github.com/rust-lang/crates.io-index" 336 | checksum = "92445bc9cc14bfa0a3ce56817dc3b5bcc227a168781a356b702410789cec0d10" 337 | dependencies = [ 338 | "bytes", 339 | "futures-util", 340 | "http", 341 | "http-body 1.0.0-rc.2", 342 | "pin-project-lite", 343 | ] 344 | 345 | [[package]] 346 | name = "httparse" 347 | version = "1.8.0" 348 | source = "registry+https://github.com/rust-lang/crates.io-index" 349 | checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" 350 | 351 | [[package]] 352 | name = "httpdate" 353 | version = "1.0.2" 354 | source = "registry+https://github.com/rust-lang/crates.io-index" 355 | checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" 356 | 357 | [[package]] 358 | name = "hyper" 359 | version = "0.14.27" 360 | source = "registry+https://github.com/rust-lang/crates.io-index" 361 | checksum = "ffb1cfd654a8219eaef89881fdb3bb3b1cdc5fa75ded05d6933b2b382e395468" 362 | dependencies = [ 363 | "bytes", 364 | "futures-channel", 365 | "futures-core", 366 | "futures-util", 367 | "h2", 368 | "http", 369 | "http-body 0.4.5", 370 | "httparse", 371 | "httpdate", 372 | "itoa", 373 | "pin-project-lite", 374 | "socket2", 375 | "tokio", 376 | "tower-service", 377 | "tracing", 378 | "want", 379 | ] 380 | 381 | [[package]] 382 | name = "hyper-rustls" 383 | version = "0.24.0" 384 | source = "registry+https://github.com/rust-lang/crates.io-index" 385 | checksum = "0646026eb1b3eea4cd9ba47912ea5ce9cc07713d105b1a14698f4e6433d348b7" 386 | dependencies = [ 387 | "http", 388 | "hyper", 389 | "rustls", 390 | "tokio", 391 | "tokio-rustls", 392 | ] 393 | 394 | [[package]] 395 | name = "hyper-tungstenite" 396 | version = "0.10.0" 397 | source = "registry+https://github.com/rust-lang/crates.io-index" 398 | checksum = "226df6fd0aece319a325419d770aa9d947defa60463f142cd82b329121f906a3" 399 | dependencies = [ 400 | "hyper", 401 | "pin-project", 402 | "tokio", 403 | "tokio-tungstenite", 404 | "tungstenite", 405 | ] 406 | 407 | [[package]] 408 | name = "idna" 409 | version = "0.4.0" 410 | source = "registry+https://github.com/rust-lang/crates.io-index" 411 | checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" 412 | dependencies = [ 413 | "unicode-bidi", 414 | "unicode-normalization", 415 | ] 416 | 417 | [[package]] 418 | name = "indexmap" 419 | version = "1.9.3" 420 | source = "registry+https://github.com/rust-lang/crates.io-index" 421 | checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" 422 | dependencies = [ 423 | "autocfg", 424 | "hashbrown", 425 | ] 426 | 427 | [[package]] 428 | name = "ipnet" 429 | version = "2.7.2" 430 | source = "registry+https://github.com/rust-lang/crates.io-index" 431 | checksum = "12b6ee2129af8d4fb011108c73d99a1b83a85977f23b82460c0ae2e25bb4b57f" 432 | 433 | [[package]] 434 | name = "itoa" 435 | version = "1.0.6" 436 | source = "registry+https://github.com/rust-lang/crates.io-index" 437 | checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" 438 | 439 | [[package]] 440 | name = "js-sys" 441 | version = "0.3.63" 442 | source = "registry+https://github.com/rust-lang/crates.io-index" 443 | checksum = "2f37a4a5928311ac501dee68b3c7613a1037d0edb30c8e5427bd832d55d1b790" 444 | dependencies = [ 445 | "wasm-bindgen", 446 | ] 447 | 448 | [[package]] 449 | name = "kook_onebot" 450 | version = "0.1.0" 451 | dependencies = [ 452 | "base64", 453 | "flate2", 454 | "futures-util", 455 | "hex", 456 | "hmac", 457 | "http-body-util", 458 | "hyper", 459 | "hyper-tungstenite", 460 | "lazy_static", 461 | "log", 462 | "regex", 463 | "reqwest", 464 | "scopeguard", 465 | "serde", 466 | "serde_derive", 467 | "serde_json", 468 | "sha1", 469 | "time", 470 | "tokio", 471 | "tokio-tungstenite", 472 | "tracing", 473 | "tracing-subscriber", 474 | "tungstenite", 475 | "url", 476 | "urlencoding", 477 | "uuid", 478 | ] 479 | 480 | [[package]] 481 | name = "lazy_static" 482 | version = "1.4.0" 483 | source = "registry+https://github.com/rust-lang/crates.io-index" 484 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 485 | 486 | [[package]] 487 | name = "libc" 488 | version = "0.2.147" 489 | source = "registry+https://github.com/rust-lang/crates.io-index" 490 | checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" 491 | 492 | [[package]] 493 | name = "log" 494 | version = "0.4.18" 495 | source = "registry+https://github.com/rust-lang/crates.io-index" 496 | checksum = "518ef76f2f87365916b142844c16d8fefd85039bc5699050210a7778ee1cd1de" 497 | 498 | [[package]] 499 | name = "matchers" 500 | version = "0.1.0" 501 | source = "registry+https://github.com/rust-lang/crates.io-index" 502 | checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" 503 | dependencies = [ 504 | "regex-automata", 505 | ] 506 | 507 | [[package]] 508 | name = "memchr" 509 | version = "2.5.0" 510 | source = "registry+https://github.com/rust-lang/crates.io-index" 511 | checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" 512 | 513 | [[package]] 514 | name = "mime" 515 | version = "0.3.17" 516 | source = "registry+https://github.com/rust-lang/crates.io-index" 517 | checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" 518 | 519 | [[package]] 520 | name = "mime_guess" 521 | version = "2.0.4" 522 | source = "registry+https://github.com/rust-lang/crates.io-index" 523 | checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef" 524 | dependencies = [ 525 | "mime", 526 | "unicase", 527 | ] 528 | 529 | [[package]] 530 | name = "miniz_oxide" 531 | version = "0.7.1" 532 | source = "registry+https://github.com/rust-lang/crates.io-index" 533 | checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" 534 | dependencies = [ 535 | "adler", 536 | ] 537 | 538 | [[package]] 539 | name = "mio" 540 | version = "0.8.8" 541 | source = "registry+https://github.com/rust-lang/crates.io-index" 542 | checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" 543 | dependencies = [ 544 | "libc", 545 | "wasi", 546 | "windows-sys", 547 | ] 548 | 549 | [[package]] 550 | name = "nu-ansi-term" 551 | version = "0.46.0" 552 | source = "registry+https://github.com/rust-lang/crates.io-index" 553 | checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" 554 | dependencies = [ 555 | "overload", 556 | "winapi", 557 | ] 558 | 559 | [[package]] 560 | name = "num_cpus" 561 | version = "1.16.0" 562 | source = "registry+https://github.com/rust-lang/crates.io-index" 563 | checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" 564 | dependencies = [ 565 | "hermit-abi", 566 | "libc", 567 | ] 568 | 569 | [[package]] 570 | name = "num_threads" 571 | version = "0.1.6" 572 | source = "registry+https://github.com/rust-lang/crates.io-index" 573 | checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44" 574 | dependencies = [ 575 | "libc", 576 | ] 577 | 578 | [[package]] 579 | name = "object" 580 | version = "0.31.1" 581 | source = "registry+https://github.com/rust-lang/crates.io-index" 582 | checksum = "8bda667d9f2b5051b8833f59f3bf748b28ef54f850f4fcb389a252aa383866d1" 583 | dependencies = [ 584 | "memchr", 585 | ] 586 | 587 | [[package]] 588 | name = "once_cell" 589 | version = "1.17.2" 590 | source = "registry+https://github.com/rust-lang/crates.io-index" 591 | checksum = "9670a07f94779e00908f3e686eab508878ebb390ba6e604d3a284c00e8d0487b" 592 | 593 | [[package]] 594 | name = "overload" 595 | version = "0.1.1" 596 | source = "registry+https://github.com/rust-lang/crates.io-index" 597 | checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" 598 | 599 | [[package]] 600 | name = "percent-encoding" 601 | version = "2.3.0" 602 | source = "registry+https://github.com/rust-lang/crates.io-index" 603 | checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" 604 | 605 | [[package]] 606 | name = "pin-project" 607 | version = "1.1.0" 608 | source = "registry+https://github.com/rust-lang/crates.io-index" 609 | checksum = "c95a7476719eab1e366eaf73d0260af3021184f18177925b07f54b30089ceead" 610 | dependencies = [ 611 | "pin-project-internal", 612 | ] 613 | 614 | [[package]] 615 | name = "pin-project-internal" 616 | version = "1.1.0" 617 | source = "registry+https://github.com/rust-lang/crates.io-index" 618 | checksum = "39407670928234ebc5e6e580247dd567ad73a3578460c5990f9503df207e8f07" 619 | dependencies = [ 620 | "proc-macro2", 621 | "quote", 622 | "syn", 623 | ] 624 | 625 | [[package]] 626 | name = "pin-project-lite" 627 | version = "0.2.9" 628 | source = "registry+https://github.com/rust-lang/crates.io-index" 629 | checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" 630 | 631 | [[package]] 632 | name = "pin-utils" 633 | version = "0.1.0" 634 | source = "registry+https://github.com/rust-lang/crates.io-index" 635 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 636 | 637 | [[package]] 638 | name = "ppv-lite86" 639 | version = "0.2.17" 640 | source = "registry+https://github.com/rust-lang/crates.io-index" 641 | checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" 642 | 643 | [[package]] 644 | name = "proc-macro2" 645 | version = "1.0.63" 646 | source = "registry+https://github.com/rust-lang/crates.io-index" 647 | checksum = "7b368fba921b0dce7e60f5e04ec15e565b3303972b42bcfde1d0713b881959eb" 648 | dependencies = [ 649 | "unicode-ident", 650 | ] 651 | 652 | [[package]] 653 | name = "quote" 654 | version = "1.0.28" 655 | source = "registry+https://github.com/rust-lang/crates.io-index" 656 | checksum = "1b9ab9c7eadfd8df19006f1cf1a4aed13540ed5cbc047010ece5826e10825488" 657 | dependencies = [ 658 | "proc-macro2", 659 | ] 660 | 661 | [[package]] 662 | name = "rand" 663 | version = "0.8.5" 664 | source = "registry+https://github.com/rust-lang/crates.io-index" 665 | checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" 666 | dependencies = [ 667 | "libc", 668 | "rand_chacha", 669 | "rand_core", 670 | ] 671 | 672 | [[package]] 673 | name = "rand_chacha" 674 | version = "0.3.1" 675 | source = "registry+https://github.com/rust-lang/crates.io-index" 676 | checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 677 | dependencies = [ 678 | "ppv-lite86", 679 | "rand_core", 680 | ] 681 | 682 | [[package]] 683 | name = "rand_core" 684 | version = "0.6.4" 685 | source = "registry+https://github.com/rust-lang/crates.io-index" 686 | checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" 687 | dependencies = [ 688 | "getrandom", 689 | ] 690 | 691 | [[package]] 692 | name = "regex" 693 | version = "1.8.4" 694 | source = "registry+https://github.com/rust-lang/crates.io-index" 695 | checksum = "d0ab3ca65655bb1e41f2a8c8cd662eb4fb035e67c3f78da1d61dffe89d07300f" 696 | dependencies = [ 697 | "aho-corasick", 698 | "memchr", 699 | "regex-syntax 0.7.2", 700 | ] 701 | 702 | [[package]] 703 | name = "regex-automata" 704 | version = "0.1.10" 705 | source = "registry+https://github.com/rust-lang/crates.io-index" 706 | checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" 707 | dependencies = [ 708 | "regex-syntax 0.6.29", 709 | ] 710 | 711 | [[package]] 712 | name = "regex-syntax" 713 | version = "0.6.29" 714 | source = "registry+https://github.com/rust-lang/crates.io-index" 715 | checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" 716 | 717 | [[package]] 718 | name = "regex-syntax" 719 | version = "0.7.2" 720 | source = "registry+https://github.com/rust-lang/crates.io-index" 721 | checksum = "436b050e76ed2903236f032a59761c1eb99e1b0aead2c257922771dab1fc8c78" 722 | 723 | [[package]] 724 | name = "reqwest" 725 | version = "0.11.18" 726 | source = "registry+https://github.com/rust-lang/crates.io-index" 727 | checksum = "cde824a14b7c14f85caff81225f411faacc04a2013f41670f41443742b1c1c55" 728 | dependencies = [ 729 | "base64", 730 | "bytes", 731 | "encoding_rs", 732 | "futures-core", 733 | "futures-util", 734 | "h2", 735 | "http", 736 | "http-body 0.4.5", 737 | "hyper", 738 | "hyper-rustls", 739 | "ipnet", 740 | "js-sys", 741 | "log", 742 | "mime", 743 | "mime_guess", 744 | "once_cell", 745 | "percent-encoding", 746 | "pin-project-lite", 747 | "rustls", 748 | "rustls-pemfile", 749 | "serde", 750 | "serde_json", 751 | "serde_urlencoded", 752 | "tokio", 753 | "tokio-rustls", 754 | "tower-service", 755 | "url", 756 | "wasm-bindgen", 757 | "wasm-bindgen-futures", 758 | "web-sys", 759 | "webpki-roots 0.22.6", 760 | "winreg", 761 | ] 762 | 763 | [[package]] 764 | name = "ring" 765 | version = "0.16.20" 766 | source = "registry+https://github.com/rust-lang/crates.io-index" 767 | checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" 768 | dependencies = [ 769 | "cc", 770 | "libc", 771 | "once_cell", 772 | "spin", 773 | "untrusted", 774 | "web-sys", 775 | "winapi", 776 | ] 777 | 778 | [[package]] 779 | name = "rustc-demangle" 780 | version = "0.1.23" 781 | source = "registry+https://github.com/rust-lang/crates.io-index" 782 | checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" 783 | 784 | [[package]] 785 | name = "rustls" 786 | version = "0.21.1" 787 | source = "registry+https://github.com/rust-lang/crates.io-index" 788 | checksum = "c911ba11bc8433e811ce56fde130ccf32f5127cab0e0194e9c68c5a5b671791e" 789 | dependencies = [ 790 | "log", 791 | "ring", 792 | "rustls-webpki", 793 | "sct", 794 | ] 795 | 796 | [[package]] 797 | name = "rustls-pemfile" 798 | version = "1.0.2" 799 | source = "registry+https://github.com/rust-lang/crates.io-index" 800 | checksum = "d194b56d58803a43635bdc398cd17e383d6f71f9182b9a192c127ca42494a59b" 801 | dependencies = [ 802 | "base64", 803 | ] 804 | 805 | [[package]] 806 | name = "rustls-webpki" 807 | version = "0.100.1" 808 | source = "registry+https://github.com/rust-lang/crates.io-index" 809 | checksum = "d6207cd5ed3d8dca7816f8f3725513a34609c0c765bf652b8c3cb4cfd87db46b" 810 | dependencies = [ 811 | "ring", 812 | "untrusted", 813 | ] 814 | 815 | [[package]] 816 | name = "ryu" 817 | version = "1.0.13" 818 | source = "registry+https://github.com/rust-lang/crates.io-index" 819 | checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" 820 | 821 | [[package]] 822 | name = "scopeguard" 823 | version = "1.1.0" 824 | source = "registry+https://github.com/rust-lang/crates.io-index" 825 | checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" 826 | 827 | [[package]] 828 | name = "sct" 829 | version = "0.7.0" 830 | source = "registry+https://github.com/rust-lang/crates.io-index" 831 | checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" 832 | dependencies = [ 833 | "ring", 834 | "untrusted", 835 | ] 836 | 837 | [[package]] 838 | name = "serde" 839 | version = "1.0.166" 840 | source = "registry+https://github.com/rust-lang/crates.io-index" 841 | checksum = "d01b7404f9d441d3ad40e6a636a7782c377d2abdbe4fa2440e2edcc2f4f10db8" 842 | dependencies = [ 843 | "serde_derive", 844 | ] 845 | 846 | [[package]] 847 | name = "serde_derive" 848 | version = "1.0.166" 849 | source = "registry+https://github.com/rust-lang/crates.io-index" 850 | checksum = "5dd83d6dde2b6b2d466e14d9d1acce8816dedee94f735eac6395808b3483c6d6" 851 | dependencies = [ 852 | "proc-macro2", 853 | "quote", 854 | "syn", 855 | ] 856 | 857 | [[package]] 858 | name = "serde_json" 859 | version = "1.0.100" 860 | source = "registry+https://github.com/rust-lang/crates.io-index" 861 | checksum = "0f1e14e89be7aa4c4b78bdbdc9eb5bf8517829a600ae8eaa39a6e1d960b5185c" 862 | dependencies = [ 863 | "itoa", 864 | "ryu", 865 | "serde", 866 | ] 867 | 868 | [[package]] 869 | name = "serde_urlencoded" 870 | version = "0.7.1" 871 | source = "registry+https://github.com/rust-lang/crates.io-index" 872 | checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" 873 | dependencies = [ 874 | "form_urlencoded", 875 | "itoa", 876 | "ryu", 877 | "serde", 878 | ] 879 | 880 | [[package]] 881 | name = "sha1" 882 | version = "0.10.5" 883 | source = "registry+https://github.com/rust-lang/crates.io-index" 884 | checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" 885 | dependencies = [ 886 | "cfg-if", 887 | "cpufeatures", 888 | "digest", 889 | ] 890 | 891 | [[package]] 892 | name = "sharded-slab" 893 | version = "0.1.4" 894 | source = "registry+https://github.com/rust-lang/crates.io-index" 895 | checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" 896 | dependencies = [ 897 | "lazy_static", 898 | ] 899 | 900 | [[package]] 901 | name = "slab" 902 | version = "0.4.8" 903 | source = "registry+https://github.com/rust-lang/crates.io-index" 904 | checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" 905 | dependencies = [ 906 | "autocfg", 907 | ] 908 | 909 | [[package]] 910 | name = "smallvec" 911 | version = "1.10.0" 912 | source = "registry+https://github.com/rust-lang/crates.io-index" 913 | checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" 914 | 915 | [[package]] 916 | name = "socket2" 917 | version = "0.4.9" 918 | source = "registry+https://github.com/rust-lang/crates.io-index" 919 | checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" 920 | dependencies = [ 921 | "libc", 922 | "winapi", 923 | ] 924 | 925 | [[package]] 926 | name = "spin" 927 | version = "0.5.2" 928 | source = "registry+https://github.com/rust-lang/crates.io-index" 929 | checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" 930 | 931 | [[package]] 932 | name = "subtle" 933 | version = "2.5.0" 934 | source = "registry+https://github.com/rust-lang/crates.io-index" 935 | checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" 936 | 937 | [[package]] 938 | name = "syn" 939 | version = "2.0.23" 940 | source = "registry+https://github.com/rust-lang/crates.io-index" 941 | checksum = "59fb7d6d8281a51045d62b8eb3a7d1ce347b76f312af50cd3dc0af39c87c1737" 942 | dependencies = [ 943 | "proc-macro2", 944 | "quote", 945 | "unicode-ident", 946 | ] 947 | 948 | [[package]] 949 | name = "thiserror" 950 | version = "1.0.40" 951 | source = "registry+https://github.com/rust-lang/crates.io-index" 952 | checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac" 953 | dependencies = [ 954 | "thiserror-impl", 955 | ] 956 | 957 | [[package]] 958 | name = "thiserror-impl" 959 | version = "1.0.40" 960 | source = "registry+https://github.com/rust-lang/crates.io-index" 961 | checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" 962 | dependencies = [ 963 | "proc-macro2", 964 | "quote", 965 | "syn", 966 | ] 967 | 968 | [[package]] 969 | name = "thread_local" 970 | version = "1.1.7" 971 | source = "registry+https://github.com/rust-lang/crates.io-index" 972 | checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" 973 | dependencies = [ 974 | "cfg-if", 975 | "once_cell", 976 | ] 977 | 978 | [[package]] 979 | name = "time" 980 | version = "0.3.22" 981 | source = "registry+https://github.com/rust-lang/crates.io-index" 982 | checksum = "ea9e1b3cf1243ae005d9e74085d4d542f3125458f3a81af210d901dcd7411efd" 983 | dependencies = [ 984 | "itoa", 985 | "libc", 986 | "num_threads", 987 | "serde", 988 | "time-core", 989 | "time-macros", 990 | ] 991 | 992 | [[package]] 993 | name = "time-core" 994 | version = "0.1.1" 995 | source = "registry+https://github.com/rust-lang/crates.io-index" 996 | checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb" 997 | 998 | [[package]] 999 | name = "time-macros" 1000 | version = "0.2.9" 1001 | source = "registry+https://github.com/rust-lang/crates.io-index" 1002 | checksum = "372950940a5f07bf38dbe211d7283c9e6d7327df53794992d293e534c733d09b" 1003 | dependencies = [ 1004 | "time-core", 1005 | ] 1006 | 1007 | [[package]] 1008 | name = "tinyvec" 1009 | version = "1.6.0" 1010 | source = "registry+https://github.com/rust-lang/crates.io-index" 1011 | checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" 1012 | dependencies = [ 1013 | "tinyvec_macros", 1014 | ] 1015 | 1016 | [[package]] 1017 | name = "tinyvec_macros" 1018 | version = "0.1.1" 1019 | source = "registry+https://github.com/rust-lang/crates.io-index" 1020 | checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" 1021 | 1022 | [[package]] 1023 | name = "tokio" 1024 | version = "1.29.1" 1025 | source = "registry+https://github.com/rust-lang/crates.io-index" 1026 | checksum = "532826ff75199d5833b9d2c5fe410f29235e25704ee5f0ef599fb51c21f4a4da" 1027 | dependencies = [ 1028 | "autocfg", 1029 | "backtrace", 1030 | "bytes", 1031 | "libc", 1032 | "mio", 1033 | "num_cpus", 1034 | "pin-project-lite", 1035 | "socket2", 1036 | "tokio-macros", 1037 | "windows-sys", 1038 | ] 1039 | 1040 | [[package]] 1041 | name = "tokio-macros" 1042 | version = "2.1.0" 1043 | source = "registry+https://github.com/rust-lang/crates.io-index" 1044 | checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" 1045 | dependencies = [ 1046 | "proc-macro2", 1047 | "quote", 1048 | "syn", 1049 | ] 1050 | 1051 | [[package]] 1052 | name = "tokio-rustls" 1053 | version = "0.24.0" 1054 | source = "registry+https://github.com/rust-lang/crates.io-index" 1055 | checksum = "e0d409377ff5b1e3ca6437aa86c1eb7d40c134bfec254e44c830defa92669db5" 1056 | dependencies = [ 1057 | "rustls", 1058 | "tokio", 1059 | ] 1060 | 1061 | [[package]] 1062 | name = "tokio-tungstenite" 1063 | version = "0.19.0" 1064 | source = "registry+https://github.com/rust-lang/crates.io-index" 1065 | checksum = "ec509ac96e9a0c43427c74f003127d953a265737636129424288d27cb5c4b12c" 1066 | dependencies = [ 1067 | "futures-util", 1068 | "log", 1069 | "rustls", 1070 | "tokio", 1071 | "tokio-rustls", 1072 | "tungstenite", 1073 | "webpki-roots 0.23.1", 1074 | ] 1075 | 1076 | [[package]] 1077 | name = "tokio-util" 1078 | version = "0.7.8" 1079 | source = "registry+https://github.com/rust-lang/crates.io-index" 1080 | checksum = "806fe8c2c87eccc8b3267cbae29ed3ab2d0bd37fca70ab622e46aaa9375ddb7d" 1081 | dependencies = [ 1082 | "bytes", 1083 | "futures-core", 1084 | "futures-sink", 1085 | "pin-project-lite", 1086 | "tokio", 1087 | "tracing", 1088 | ] 1089 | 1090 | [[package]] 1091 | name = "tower-service" 1092 | version = "0.3.2" 1093 | source = "registry+https://github.com/rust-lang/crates.io-index" 1094 | checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" 1095 | 1096 | [[package]] 1097 | name = "tracing" 1098 | version = "0.1.37" 1099 | source = "registry+https://github.com/rust-lang/crates.io-index" 1100 | checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" 1101 | dependencies = [ 1102 | "cfg-if", 1103 | "pin-project-lite", 1104 | "tracing-attributes", 1105 | "tracing-core", 1106 | ] 1107 | 1108 | [[package]] 1109 | name = "tracing-attributes" 1110 | version = "0.1.26" 1111 | source = "registry+https://github.com/rust-lang/crates.io-index" 1112 | checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab" 1113 | dependencies = [ 1114 | "proc-macro2", 1115 | "quote", 1116 | "syn", 1117 | ] 1118 | 1119 | [[package]] 1120 | name = "tracing-core" 1121 | version = "0.1.31" 1122 | source = "registry+https://github.com/rust-lang/crates.io-index" 1123 | checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a" 1124 | dependencies = [ 1125 | "once_cell", 1126 | "valuable", 1127 | ] 1128 | 1129 | [[package]] 1130 | name = "tracing-log" 1131 | version = "0.1.3" 1132 | source = "registry+https://github.com/rust-lang/crates.io-index" 1133 | checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922" 1134 | dependencies = [ 1135 | "lazy_static", 1136 | "log", 1137 | "tracing-core", 1138 | ] 1139 | 1140 | [[package]] 1141 | name = "tracing-subscriber" 1142 | version = "0.3.17" 1143 | source = "registry+https://github.com/rust-lang/crates.io-index" 1144 | checksum = "30a651bc37f915e81f087d86e62a18eec5f79550c7faff886f7090b4ea757c77" 1145 | dependencies = [ 1146 | "matchers", 1147 | "nu-ansi-term", 1148 | "once_cell", 1149 | "regex", 1150 | "sharded-slab", 1151 | "smallvec", 1152 | "thread_local", 1153 | "time", 1154 | "tracing", 1155 | "tracing-core", 1156 | "tracing-log", 1157 | ] 1158 | 1159 | [[package]] 1160 | name = "try-lock" 1161 | version = "0.2.4" 1162 | source = "registry+https://github.com/rust-lang/crates.io-index" 1163 | checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" 1164 | 1165 | [[package]] 1166 | name = "tungstenite" 1167 | version = "0.19.0" 1168 | source = "registry+https://github.com/rust-lang/crates.io-index" 1169 | checksum = "15fba1a6d6bb030745759a9a2a588bfe8490fc8b4751a277db3a0be1c9ebbf67" 1170 | dependencies = [ 1171 | "byteorder", 1172 | "bytes", 1173 | "data-encoding", 1174 | "http", 1175 | "httparse", 1176 | "log", 1177 | "rand", 1178 | "rustls", 1179 | "sha1", 1180 | "thiserror", 1181 | "url", 1182 | "utf-8", 1183 | "webpki", 1184 | "webpki-roots 0.23.1", 1185 | ] 1186 | 1187 | [[package]] 1188 | name = "typenum" 1189 | version = "1.16.0" 1190 | source = "registry+https://github.com/rust-lang/crates.io-index" 1191 | checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" 1192 | 1193 | [[package]] 1194 | name = "unicase" 1195 | version = "2.6.0" 1196 | source = "registry+https://github.com/rust-lang/crates.io-index" 1197 | checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" 1198 | dependencies = [ 1199 | "version_check", 1200 | ] 1201 | 1202 | [[package]] 1203 | name = "unicode-bidi" 1204 | version = "0.3.13" 1205 | source = "registry+https://github.com/rust-lang/crates.io-index" 1206 | checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" 1207 | 1208 | [[package]] 1209 | name = "unicode-ident" 1210 | version = "1.0.9" 1211 | source = "registry+https://github.com/rust-lang/crates.io-index" 1212 | checksum = "b15811caf2415fb889178633e7724bad2509101cde276048e013b9def5e51fa0" 1213 | 1214 | [[package]] 1215 | name = "unicode-normalization" 1216 | version = "0.1.22" 1217 | source = "registry+https://github.com/rust-lang/crates.io-index" 1218 | checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" 1219 | dependencies = [ 1220 | "tinyvec", 1221 | ] 1222 | 1223 | [[package]] 1224 | name = "untrusted" 1225 | version = "0.7.1" 1226 | source = "registry+https://github.com/rust-lang/crates.io-index" 1227 | checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" 1228 | 1229 | [[package]] 1230 | name = "url" 1231 | version = "2.4.0" 1232 | source = "registry+https://github.com/rust-lang/crates.io-index" 1233 | checksum = "50bff7831e19200a85b17131d085c25d7811bc4e186efdaf54bbd132994a88cb" 1234 | dependencies = [ 1235 | "form_urlencoded", 1236 | "idna", 1237 | "percent-encoding", 1238 | ] 1239 | 1240 | [[package]] 1241 | name = "urlencoding" 1242 | version = "2.1.2" 1243 | source = "registry+https://github.com/rust-lang/crates.io-index" 1244 | checksum = "e8db7427f936968176eaa7cdf81b7f98b980b18495ec28f1b5791ac3bfe3eea9" 1245 | 1246 | [[package]] 1247 | name = "utf-8" 1248 | version = "0.7.6" 1249 | source = "registry+https://github.com/rust-lang/crates.io-index" 1250 | checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" 1251 | 1252 | [[package]] 1253 | name = "uuid" 1254 | version = "1.3.3" 1255 | source = "registry+https://github.com/rust-lang/crates.io-index" 1256 | checksum = "345444e32442451b267fc254ae85a209c64be56d2890e601a0c37ff0c3c5ecd2" 1257 | dependencies = [ 1258 | "getrandom", 1259 | "rand", 1260 | ] 1261 | 1262 | [[package]] 1263 | name = "valuable" 1264 | version = "0.1.0" 1265 | source = "registry+https://github.com/rust-lang/crates.io-index" 1266 | checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" 1267 | 1268 | [[package]] 1269 | name = "version_check" 1270 | version = "0.9.4" 1271 | source = "registry+https://github.com/rust-lang/crates.io-index" 1272 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 1273 | 1274 | [[package]] 1275 | name = "want" 1276 | version = "0.3.0" 1277 | source = "registry+https://github.com/rust-lang/crates.io-index" 1278 | checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" 1279 | dependencies = [ 1280 | "log", 1281 | "try-lock", 1282 | ] 1283 | 1284 | [[package]] 1285 | name = "wasi" 1286 | version = "0.11.0+wasi-snapshot-preview1" 1287 | source = "registry+https://github.com/rust-lang/crates.io-index" 1288 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 1289 | 1290 | [[package]] 1291 | name = "wasm-bindgen" 1292 | version = "0.2.86" 1293 | source = "registry+https://github.com/rust-lang/crates.io-index" 1294 | checksum = "5bba0e8cb82ba49ff4e229459ff22a191bbe9a1cb3a341610c9c33efc27ddf73" 1295 | dependencies = [ 1296 | "cfg-if", 1297 | "wasm-bindgen-macro", 1298 | ] 1299 | 1300 | [[package]] 1301 | name = "wasm-bindgen-backend" 1302 | version = "0.2.86" 1303 | source = "registry+https://github.com/rust-lang/crates.io-index" 1304 | checksum = "19b04bc93f9d6bdee709f6bd2118f57dd6679cf1176a1af464fca3ab0d66d8fb" 1305 | dependencies = [ 1306 | "bumpalo", 1307 | "log", 1308 | "once_cell", 1309 | "proc-macro2", 1310 | "quote", 1311 | "syn", 1312 | "wasm-bindgen-shared", 1313 | ] 1314 | 1315 | [[package]] 1316 | name = "wasm-bindgen-futures" 1317 | version = "0.4.36" 1318 | source = "registry+https://github.com/rust-lang/crates.io-index" 1319 | checksum = "2d1985d03709c53167ce907ff394f5316aa22cb4e12761295c5dc57dacb6297e" 1320 | dependencies = [ 1321 | "cfg-if", 1322 | "js-sys", 1323 | "wasm-bindgen", 1324 | "web-sys", 1325 | ] 1326 | 1327 | [[package]] 1328 | name = "wasm-bindgen-macro" 1329 | version = "0.2.86" 1330 | source = "registry+https://github.com/rust-lang/crates.io-index" 1331 | checksum = "14d6b024f1a526bb0234f52840389927257beb670610081360e5a03c5df9c258" 1332 | dependencies = [ 1333 | "quote", 1334 | "wasm-bindgen-macro-support", 1335 | ] 1336 | 1337 | [[package]] 1338 | name = "wasm-bindgen-macro-support" 1339 | version = "0.2.86" 1340 | source = "registry+https://github.com/rust-lang/crates.io-index" 1341 | checksum = "e128beba882dd1eb6200e1dc92ae6c5dbaa4311aa7bb211ca035779e5efc39f8" 1342 | dependencies = [ 1343 | "proc-macro2", 1344 | "quote", 1345 | "syn", 1346 | "wasm-bindgen-backend", 1347 | "wasm-bindgen-shared", 1348 | ] 1349 | 1350 | [[package]] 1351 | name = "wasm-bindgen-shared" 1352 | version = "0.2.86" 1353 | source = "registry+https://github.com/rust-lang/crates.io-index" 1354 | checksum = "ed9d5b4305409d1fc9482fee2d7f9bcbf24b3972bf59817ef757e23982242a93" 1355 | 1356 | [[package]] 1357 | name = "web-sys" 1358 | version = "0.3.63" 1359 | source = "registry+https://github.com/rust-lang/crates.io-index" 1360 | checksum = "3bdd9ef4e984da1187bf8110c5cf5b845fbc87a23602cdf912386a76fcd3a7c2" 1361 | dependencies = [ 1362 | "js-sys", 1363 | "wasm-bindgen", 1364 | ] 1365 | 1366 | [[package]] 1367 | name = "webpki" 1368 | version = "0.22.0" 1369 | source = "registry+https://github.com/rust-lang/crates.io-index" 1370 | checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd" 1371 | dependencies = [ 1372 | "ring", 1373 | "untrusted", 1374 | ] 1375 | 1376 | [[package]] 1377 | name = "webpki-roots" 1378 | version = "0.22.6" 1379 | source = "registry+https://github.com/rust-lang/crates.io-index" 1380 | checksum = "b6c71e40d7d2c34a5106301fb632274ca37242cd0c9d3e64dbece371a40a2d87" 1381 | dependencies = [ 1382 | "webpki", 1383 | ] 1384 | 1385 | [[package]] 1386 | name = "webpki-roots" 1387 | version = "0.23.1" 1388 | source = "registry+https://github.com/rust-lang/crates.io-index" 1389 | checksum = "b03058f88386e5ff5310d9111d53f48b17d732b401aeb83a8d5190f2ac459338" 1390 | dependencies = [ 1391 | "rustls-webpki", 1392 | ] 1393 | 1394 | [[package]] 1395 | name = "winapi" 1396 | version = "0.3.9" 1397 | source = "registry+https://github.com/rust-lang/crates.io-index" 1398 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 1399 | dependencies = [ 1400 | "winapi-i686-pc-windows-gnu", 1401 | "winapi-x86_64-pc-windows-gnu", 1402 | ] 1403 | 1404 | [[package]] 1405 | name = "winapi-i686-pc-windows-gnu" 1406 | version = "0.4.0" 1407 | source = "registry+https://github.com/rust-lang/crates.io-index" 1408 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 1409 | 1410 | [[package]] 1411 | name = "winapi-x86_64-pc-windows-gnu" 1412 | version = "0.4.0" 1413 | source = "registry+https://github.com/rust-lang/crates.io-index" 1414 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 1415 | 1416 | [[package]] 1417 | name = "windows-sys" 1418 | version = "0.48.0" 1419 | source = "registry+https://github.com/rust-lang/crates.io-index" 1420 | checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" 1421 | dependencies = [ 1422 | "windows-targets", 1423 | ] 1424 | 1425 | [[package]] 1426 | name = "windows-targets" 1427 | version = "0.48.0" 1428 | source = "registry+https://github.com/rust-lang/crates.io-index" 1429 | checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" 1430 | dependencies = [ 1431 | "windows_aarch64_gnullvm", 1432 | "windows_aarch64_msvc", 1433 | "windows_i686_gnu", 1434 | "windows_i686_msvc", 1435 | "windows_x86_64_gnu", 1436 | "windows_x86_64_gnullvm", 1437 | "windows_x86_64_msvc", 1438 | ] 1439 | 1440 | [[package]] 1441 | name = "windows_aarch64_gnullvm" 1442 | version = "0.48.0" 1443 | source = "registry+https://github.com/rust-lang/crates.io-index" 1444 | checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" 1445 | 1446 | [[package]] 1447 | name = "windows_aarch64_msvc" 1448 | version = "0.48.0" 1449 | source = "registry+https://github.com/rust-lang/crates.io-index" 1450 | checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" 1451 | 1452 | [[package]] 1453 | name = "windows_i686_gnu" 1454 | version = "0.48.0" 1455 | source = "registry+https://github.com/rust-lang/crates.io-index" 1456 | checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" 1457 | 1458 | [[package]] 1459 | name = "windows_i686_msvc" 1460 | version = "0.48.0" 1461 | source = "registry+https://github.com/rust-lang/crates.io-index" 1462 | checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" 1463 | 1464 | [[package]] 1465 | name = "windows_x86_64_gnu" 1466 | version = "0.48.0" 1467 | source = "registry+https://github.com/rust-lang/crates.io-index" 1468 | checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" 1469 | 1470 | [[package]] 1471 | name = "windows_x86_64_gnullvm" 1472 | version = "0.48.0" 1473 | source = "registry+https://github.com/rust-lang/crates.io-index" 1474 | checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" 1475 | 1476 | [[package]] 1477 | name = "windows_x86_64_msvc" 1478 | version = "0.48.0" 1479 | source = "registry+https://github.com/rust-lang/crates.io-index" 1480 | checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" 1481 | 1482 | [[package]] 1483 | name = "winreg" 1484 | version = "0.10.1" 1485 | source = "registry+https://github.com/rust-lang/crates.io-index" 1486 | checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" 1487 | dependencies = [ 1488 | "winapi", 1489 | ] 1490 | -------------------------------------------------------------------------------- /src/kook_onebot.rs: -------------------------------------------------------------------------------- 1 | 2 | 3 | use std::{str::FromStr, io::Read, collections::{HashMap, VecDeque}, time::Duration, sync::{atomic::AtomicI64, Arc}, path::Path}; 4 | 5 | use flate2::read::ZlibDecoder; 6 | use futures_util::{StreamExt, SinkExt}; 7 | use hyper::http::{HeaderName, HeaderValue}; 8 | use regex::Regex; 9 | use serde_derive::{Serialize, Deserialize}; 10 | use tokio_tungstenite::connect_async; 11 | use std::time::SystemTime; 12 | use crate::{G_ONEBOT_RX, cqtool::{str_msg_to_arr, arr_to_cq_str, make_kook_text, kook_msg_to_cq, to_json_str}, msgid_tool::QMessageStruct, G_REVERSE_URL, G_KOOK_TOKEN, G_SELF_ID}; 13 | 14 | 15 | #[derive(Clone)] 16 | pub struct KookOnebot { 17 | pub token:String, 18 | pub self_id:u64, 19 | pub sn:Arc 20 | } 21 | 22 | impl KookOnebot { 23 | async fn send_to_onebot_client(&self,js:&serde_json::Value) { 24 | let json_str = js.to_string(); 25 | log::info!("发送ONEBOT事件:{json_str}"); 26 | { 27 | let lk = G_ONEBOT_RX.read().await; 28 | for (_,v) in &*lk { 29 | let rst = v.0.send(json_str.to_string()).await; 30 | if rst.is_err() { 31 | log::error!("发送事件到ONEBOT_WS客户端出错:`{}`",rst.err().unwrap()); 32 | } 33 | } 34 | } 35 | let lk = G_REVERSE_URL.read().await; 36 | for uri in &*lk { 37 | if !uri.starts_with("http") { 38 | continue; 39 | } 40 | let uri_t = uri.to_owned(); 41 | let json_str_t = json_str.to_owned(); 42 | let self_id_t = self.self_id; 43 | let js_t = js.clone(); 44 | tokio::spawn(async move{ 45 | match crate::onebot_http_rev::post_to_client(&uri_t,&json_str_t,self_id_t).await { 46 | Ok(res) => { 47 | if !res.is_null() { // 执行快速操作 48 | if let Err(err) = KookOnebot::fast_http_operator(&res,&js_t).await { 49 | log::error!("HTTP_POST快速操作出错:`{}`",err); 50 | } 51 | } 52 | }, 53 | Err(err) => { 54 | log::error!("发送事件到ONEBOT_HTTP客户端出错:`{}`",err); 55 | } 56 | } 57 | }); 58 | } 59 | 60 | } 61 | 62 | async fn fast_http_operator(res_js:&serde_json::Value,send_js:&serde_json::Value) -> Result<(), Box> { 63 | log::info!("HTTP_POST快速操作:`{res_js}`"); 64 | let kb = crate::kook_onebot::KookOnebot { 65 | token: G_KOOK_TOKEN.read().await.to_owned(), 66 | self_id: G_SELF_ID.read().await.to_owned(), 67 | sn: Arc::new(AtomicI64::new(0)), 68 | }; 69 | 70 | let message_type = get_json_str(send_js, "message_type"); 71 | if message_type == "group" { 72 | let reply = get_json_str(res_js, "reply"); 73 | let auto_escape = get_json_bool(res_js, "auto_escape"); 74 | let at_sender = get_json_bool(res_js, "at_sender"); 75 | let delete = get_json_bool(res_js, "delete"); 76 | let kick = get_json_bool(res_js, "kick"); 77 | let ban = get_json_bool(res_js, "ban"); 78 | let group_id = send_js.get("group_id").ok_or("group_id not found")?.as_u64().ok_or("group_id not u64")?; 79 | let user_id = send_js.get("user_id").ok_or("user_id not found")?.as_u64().ok_or("user_id not u64")?; 80 | if reply != "" { 81 | let message; 82 | if at_sender { 83 | message = format!("[CQ:at,qq={user_id}]") + &reply; 84 | }else { 85 | message = reply; 86 | } 87 | let to_send = serde_json::json!({ 88 | "action":"send_group_msg", 89 | "params":{ 90 | "group_id":group_id, 91 | "message":message, 92 | "auto_escape":auto_escape 93 | } 94 | }); 95 | kb.deal_onebot(&to_send.to_string()).await; 96 | } 97 | if kick { 98 | let to_send = serde_json::json!({ 99 | "action":"set_group_kick", 100 | "params":{ 101 | "group_id":group_id, 102 | "message":user_id 103 | } 104 | }); 105 | kb.deal_onebot(&to_send.to_string()).await; 106 | } 107 | if delete { 108 | let message_id = send_js.get("message_id").ok_or("message_id not found")?.as_i64().ok_or("message_id not u64")?; 109 | let to_send = serde_json::json!({ 110 | "action":"delete_msg", 111 | "params":{ 112 | "message_id":message_id 113 | } 114 | }); 115 | kb.deal_onebot(&to_send.to_string()).await; 116 | } 117 | if ban { 118 | let ban_duration_str = get_json_str(res_js, "ban_duration"); 119 | let ban_duration:u64; 120 | if ban_duration_str != "" { 121 | ban_duration = 60 * 30; 122 | } else { 123 | ban_duration = ban_duration_str.parse::()?; 124 | } 125 | let to_send = serde_json::json!({ 126 | "action":"set_group_ban", 127 | "params":{ 128 | "group_id":group_id, 129 | "user_id":user_id, 130 | "duration":ban_duration 131 | } 132 | }); 133 | kb.deal_onebot(&to_send.to_string()).await; 134 | } 135 | } else if message_type == "send_private_msg" { 136 | let reply = get_json_str(res_js, "reply"); 137 | let auto_escape = get_json_bool(res_js, "auto_escape"); 138 | let user_id = send_js.get("user_id").ok_or("user_id not found")?.as_u64().ok_or("user_id not u64")?; 139 | let to_send = serde_json::json!({ 140 | "action":"send_group_msg", 141 | "params":{ 142 | "message":reply, 143 | "user_id":user_id, 144 | "auto_escape":auto_escape 145 | } 146 | }); 147 | kb.deal_onebot(&to_send.to_string()).await; 148 | } 149 | 150 | Ok(()) 151 | } 152 | 153 | async fn http_get_json_t(&self,uri:&str) -> Result> { 154 | log::info!("发送KOOK_GET:{uri}"); 155 | let uri = reqwest::Url::from_str(&format!("https://www.kookapp.cn/api/v3{uri}"))?; 156 | let client = reqwest::Client::builder().danger_accept_invalid_certs(true).no_proxy().build()?; 157 | let mut req = client.get(uri).build()?; 158 | let token = &self.token; 159 | req.headers_mut().append(HeaderName::from_str("Authorization")?, HeaderValue::from_str(&format!("Bot {token}"))?); 160 | let ret = client.execute(req).await?; 161 | let retbin = ret.bytes().await?.to_vec(); 162 | let ret_str = String::from_utf8(retbin)?; 163 | log::info!("KOOK_GET响应:{ret_str}"); 164 | let js:serde_json::Value = serde_json::from_str(&ret_str)?; 165 | let ret = js.get("data").ok_or("get data err")?; 166 | Ok(ret.to_owned()) 167 | } 168 | 169 | async fn http_get_json(&self,uri:&str,use_cache:bool) -> Result> { 170 | lazy_static! { 171 | static ref CACHE : std::sync::RwLock> = std::sync::RwLock::new(VecDeque::from([])); 172 | } 173 | let tm = SystemTime::now().duration_since(std::time::UNIX_EPOCH)?.as_secs(); 174 | // 清除久远的记录 175 | { 176 | let mut lk = CACHE.write().unwrap(); 177 | loop { 178 | let mut remove_index = 0; 179 | for it in &*lk { 180 | if tm - it.2 > 60 { 181 | break; 182 | } 183 | remove_index += 1; 184 | } 185 | if remove_index == lk.len() { 186 | break; 187 | } 188 | lk.remove(remove_index); 189 | } 190 | } 191 | // 从缓存中返回数据 192 | if use_cache { 193 | let lk = CACHE.read().unwrap(); 194 | for it in &*lk { 195 | if it.0 ==uri { 196 | return Ok(it.1.clone()); 197 | } 198 | } 199 | } 200 | // 缓存失效或者不使用缓存 201 | let ret_val = self.http_get_json_t(uri).await?; 202 | // 更新缓存 203 | { 204 | let mut lk = CACHE.write().unwrap(); 205 | lk.push_back((uri.to_string(),ret_val.clone(),tm)); 206 | } 207 | return Ok(ret_val) 208 | 209 | } 210 | 211 | async fn http_post_json(&self,uri:&str,json:&serde_json::Value) -> Result>{ 212 | let json_str = json.to_string(); 213 | log::info!("发送KOOK_POST:{uri}\n{}",json_str); 214 | let uri = reqwest::Url::from_str(&format!("https://www.kookapp.cn/api/v3{uri}"))?; 215 | let client = reqwest::Client::builder().danger_accept_invalid_certs(true).no_proxy().build()?; 216 | let mut req = client.post(uri).body(reqwest::Body::from(json_str)).build()?; 217 | let token = &self.token; 218 | req.headers_mut().append(HeaderName::from_str("Authorization")?, HeaderValue::from_str(&format!("Bot {token}"))?); 219 | req.headers_mut().append(HeaderName::from_str("Content-type")?, HeaderValue::from_str("application/json")?); 220 | let ret = client.execute(req).await?; 221 | let retbin = ret.bytes().await?.to_vec(); 222 | let ret_str = String::from_utf8(retbin)?; 223 | log::info!("KOOK_POST响应:{ret_str}"); 224 | let js:serde_json::Value = serde_json::from_str(&ret_str)?; 225 | let ret = js.get("data").ok_or("get data err")?; 226 | Ok(ret.to_owned()) 227 | } 228 | 229 | 230 | async fn get_group_list(&self) -> Result, Box> { 231 | let ret_json = self.http_get_json("/guild/list",false).await?; 232 | let guild_arr = ret_json.get("items").ok_or("get items err")?.as_array().ok_or("items not arr")?; 233 | let mut guild_arr_t = vec![]; 234 | let mut ret_arr = vec![]; 235 | for it in guild_arr { 236 | let id = it.get("id").ok_or("get id err")?.as_str().ok_or("id not str")?; 237 | guild_arr_t.push(id.to_string()); 238 | } 239 | // 查询分页数据 240 | let meta = ret_json.get("meta").ok_or("meta not found")?; 241 | let page_total = meta.get("page_total").ok_or("page_total not found")?.as_i64().ok_or("page_total not i32")?; 242 | let total = meta.get("total").ok_or("total not found")?.as_i64().ok_or("total not i32")?; 243 | 244 | // 太多guild,onebot无法处理这种情况 245 | if total > 150 { 246 | log::warn!("too many guild(>150),can't use get_group_list,only partial data will return"); 247 | return Ok(ret_arr); 248 | } 249 | 250 | for page in 1..page_total{ 251 | let guild_list = self.http_get_json(&format!("/guild/list?page={page}"),false).await?; 252 | for it in guild_list.get("items").ok_or("items not found")?.as_array().ok_or("items not arr")? { 253 | let id = it.get("id").ok_or("get id err")?.as_str().ok_or("id not str")?; 254 | guild_arr_t.push(id.to_string()); 255 | } 256 | } 257 | 258 | for it in guild_arr_t { 259 | let ret_json = self.http_get_json(&format!("/channel/list?guild_id={it}"),false).await?; 260 | let channel_arr = ret_json.get("items").ok_or("get items err")?.as_array().ok_or("items not arr")?; 261 | for it2 in channel_arr { 262 | let id = it2.get("id").ok_or("get id err")?.as_str().ok_or("id not str")?; 263 | 264 | let group_name = it2.get("name").ok_or("get name err")?.as_str().ok_or("name not str")?; 265 | 266 | let tp = it2.get("type").ok_or("get type err")?.as_i64().ok_or("type not i64")?; 267 | let is_category = get_json_bool(it2, "is_category"); 268 | 269 | if !is_category && tp == 1 { 270 | ret_arr.push(GroupInfo { 271 | group_id:id.parse::()?, 272 | group_name:group_name.to_owned(), 273 | member_count:0, 274 | max_member_count:0 275 | }); 276 | } 277 | } 278 | } 279 | Ok(ret_arr) 280 | } 281 | 282 | async fn get_group_member_list(&self,group_id:&str) -> Result, Box> { 283 | let group_info = self.http_get_json(&format!("/channel/view?target_id={group_id}"),true).await?; 284 | let guild_id = group_info.get("guild_id").ok_or("get guild_id err")?.as_str().ok_or("guild_id not str")?; 285 | let mut ret_vec:Vec = vec![]; 286 | let ret_json = self.http_get_json(&format!("/guild/user-list?guild_id={guild_id}"),false).await?; 287 | let items = ret_json.get("items").ok_or("get items err")?.as_array().ok_or("items not arr")?; 288 | for it in items { 289 | let role; 290 | let is_master = get_json_bool(it, "is_master"); 291 | if is_master { 292 | role = "owner"; 293 | }else{ 294 | let roles = it.get("roles").ok_or("get roles err")?.as_array().ok_or("roles not arr")?; 295 | if roles.len() != 0 { 296 | role = "admin"; 297 | } else { 298 | role = "member"; 299 | } 300 | } 301 | let user_id = get_json_str(it, "id"); 302 | let info = GroupMemberInfo { 303 | group_id:group_id.parse::()?, 304 | user_id:user_id.parse::()?, 305 | nickname:it.get("username").ok_or("get username err")?.as_str().ok_or("username not str")?.to_owned(), 306 | card:it.get("nickname").ok_or("get nickname err")?.as_str().ok_or("nickname not str")?.to_owned(), 307 | sex:"unknown".to_owned(), 308 | age:0, 309 | area:"".to_owned(), 310 | join_time:(it.get("joined_at").ok_or("get joined_at err")?.as_u64().ok_or("joined_at not u64")? / 1000) as i32, 311 | last_sent_time:(it.get("active_time").ok_or("get active_time err")?.as_u64().ok_or("active_time not u64")? / 1000) as i32, 312 | level:"0".to_owned(), 313 | role:role.to_owned(), 314 | unfriendly:false, 315 | title:"".to_owned(), 316 | title_expire_time:0, 317 | card_changeable:false, 318 | avatar:it.get("avatar").ok_or("avatar not found")?.as_str().ok_or("avatar not str")?.to_owned() 319 | }; 320 | ret_vec.push(info); 321 | } 322 | let meta = ret_json.get("meta").ok_or("meta not found")?; 323 | let page_total = meta.get("page_total").ok_or("page_total not found")?.as_i64().ok_or("page_total not i32")?; 324 | for page in 1..page_total { 325 | let ret_json = self.http_get_json(&format!("/guild/user-list?guild_id={guild_id}&page={page}"),false).await?; 326 | for it in ret_json.get("items").ok_or("items not found")?.as_array().ok_or("items not arr")? { 327 | let role; 328 | let is_master = get_json_bool(it, "is_master"); 329 | if is_master { 330 | role = "owner"; 331 | }else{ 332 | let roles = it.get("roles").ok_or("get roles err")?.as_array().ok_or("roles not arr")?; 333 | if roles.len() != 0 { 334 | role = "admin"; 335 | } else { 336 | role = "member"; 337 | } 338 | } 339 | let user_id = get_json_str(it, "id"); 340 | let info = GroupMemberInfo { 341 | group_id:group_id.parse::()?, 342 | user_id:user_id.parse::()?, 343 | nickname:it.get("username").ok_or("get username err")?.as_str().ok_or("username not str")?.to_owned(), 344 | card:it.get("nickname").ok_or("get nickname err")?.as_str().ok_or("nickname not str")?.to_owned(), 345 | sex:"unknown".to_owned(), 346 | age:0, 347 | area:"".to_owned(), 348 | join_time:(it.get("joined_at").ok_or("get joined_at err")?.as_u64().ok_or("joined_at not u64")? / 1000) as i32, 349 | last_sent_time:(it.get("active_time").ok_or("get active_time err")?.as_u64().ok_or("active_time not u64")? / 1000) as i32, 350 | level:"0".to_owned(), 351 | role:role.to_owned(), 352 | unfriendly:false, 353 | title:"".to_owned(), 354 | title_expire_time:0, 355 | card_changeable:false, 356 | avatar:it.get("avatar").ok_or("avatar not found")?.as_str().ok_or("avatar not str")?.to_owned() 357 | }; 358 | ret_vec.push(info); 359 | } 360 | } 361 | Ok(ret_vec) 362 | } 363 | 364 | async fn get_channel_list(&self,guild_id:&str)-> Result, Box> { 365 | let mut ret_arr = vec![]; 366 | let ret_json = self.http_get_json(&format!("/channel/list?guild_id={guild_id}"),false).await?; 367 | let channel_arr = ret_json.get("items").ok_or("get items err")?.as_array().ok_or("items not arr")?; 368 | for it2 in channel_arr { 369 | let id = it2.get("id").ok_or("get id err")?.as_str().ok_or("id not str")?; 370 | 371 | let group_name = it2.get("name").ok_or("get name err")?.as_str().ok_or("name not str")?; 372 | 373 | let tp = it2.get("type").ok_or("get type err")?.as_i64().ok_or("type not i64")?; 374 | let is_category = get_json_bool(it2, "is_category"); 375 | 376 | if !is_category && tp == 1 { 377 | ret_arr.push(GroupInfo { 378 | group_id:id.parse::()?, 379 | group_name:group_name.to_owned(), 380 | member_count:0, 381 | max_member_count:0 382 | }); 383 | } 384 | } 385 | Ok(ret_arr) 386 | } 387 | 388 | pub async fn get_login_info(&self)-> Result> { 389 | let login_info = self.http_get_json("/user/me",true).await?; 390 | let user_id = login_info.get("id").ok_or("get id err")?.as_str().ok_or("id not str")?; 391 | let nickname = login_info.get("username").ok_or("get username err")?.as_str().ok_or("username not str")?; 392 | let avatar = login_info.get("avatar").ok_or("get avatar err")?.as_str().ok_or("avatar not str")?; 393 | Ok(LoginInfo { 394 | user_id:user_id.parse::()?, 395 | nickname:nickname.to_owned(), 396 | avatar:avatar.to_owned() 397 | }) 398 | } 399 | 400 | async fn http_post(url:&str,data:Vec,headers:&HashMap,is_post:bool) -> Result, Box> { 401 | let client; 402 | let uri = reqwest::Url::from_str(url)?; 403 | if uri.scheme() == "http" { 404 | client = reqwest::Client::builder().no_proxy().build()?; 405 | } else { 406 | client = reqwest::Client::builder().danger_accept_invalid_certs(true).no_proxy().build()?; 407 | } 408 | let mut req; 409 | if is_post { 410 | req = client.post(uri).body(reqwest::Body::from(data)).build()?; 411 | }else { 412 | req = client.get(uri).build()?; 413 | } 414 | for (key,val) in headers { 415 | req.headers_mut().append(HeaderName::from_str(key)?, HeaderValue::from_str(val)?); 416 | } 417 | let retbin; 418 | let ret = client.execute(req).await?; 419 | retbin = ret.bytes().await?.to_vec(); 420 | return Ok(retbin); 421 | } 422 | 423 | 424 | async fn upload_asset(&self,uri:&str)-> Result> { 425 | let file_bin; 426 | if uri.starts_with("http") { 427 | file_bin = Self::http_post(uri,vec![],&HashMap::new(),false).await?; 428 | }else if uri.starts_with("base64://") { 429 | let b64_str = uri.get(9..).unwrap(); 430 | file_bin = base64::Engine::decode(&base64::engine::GeneralPurpose::new( 431 | &base64::alphabet::STANDARD, 432 | base64::engine::general_purpose::PAD), b64_str)?; 433 | }else { 434 | let file_path; 435 | if cfg!(target_os = "windows") { 436 | file_path = uri.get(8..).ok_or("can't get file_path")?; 437 | } else { 438 | file_path = uri.get(7..).ok_or("can't get file_path")?; 439 | } 440 | let path = Path::new(&file_path); 441 | file_bin = tokio::fs::read(path).await?; 442 | } 443 | 444 | let uri = reqwest::Url::from_str(&format!("https://www.kookapp.cn/api/v3/asset/create"))?; 445 | let client = reqwest::Client::builder().danger_accept_invalid_certs(true).no_proxy().build()?; 446 | let form = reqwest::multipart::Form::new().part("file", reqwest::multipart::Part::bytes(file_bin).file_name("test")); 447 | let mut req = client.post(uri).multipart(form).build()?; 448 | let token = &self.token; 449 | req.headers_mut().append(HeaderName::from_str("Authorization")?, HeaderValue::from_str(&format!("Bot {token}"))?); 450 | let ret = client.execute(req).await?; 451 | let retbin = ret.bytes().await?.to_vec(); 452 | let ret_str = String::from_utf8(retbin)?; 453 | let js:serde_json::Value = serde_json::from_str(&ret_str)?; 454 | let ret = js.get("data").ok_or("get data err")?.get("url").ok_or("url not found")?.as_str().ok_or("url not str")?; 455 | Ok(ret.to_owned()) 456 | } 457 | 458 | 459 | async fn get_stranger_info(&self,user_id:&str,use_cache:bool)-> Result> { 460 | let stranger_info = self.http_get_json(&format!("/user/view?user_id={user_id}"),use_cache).await?; 461 | let user_id = stranger_info.get("id").ok_or("get id err")?.as_str().ok_or("id not str")?; 462 | let nickname = stranger_info.get("username").ok_or("get username err")?.as_str().ok_or("username not str")?; 463 | Ok(StrangerInfo { 464 | user_id:user_id.parse::()?, 465 | nickname:nickname.to_owned(), 466 | sex:"unknown".to_owned(), 467 | age:0, 468 | avatar:stranger_info.get("avatar").ok_or("avatar not found")?.as_str().ok_or("avatar not str")?.to_owned() 469 | }) 470 | } 471 | 472 | 473 | async fn get_group_info(&self,group_id:&str,use_cache:bool)-> Result> { 474 | let stranger_info = self.http_get_json(&format!("/channel/view?target_id={group_id}"),use_cache).await?; 475 | let group_id = stranger_info.get("id").ok_or("get id err")?.as_str().ok_or("id not str")?; 476 | let group_name = stranger_info.get("name").ok_or("get name err")?.as_str().ok_or("name not str")?; 477 | Ok(GroupInfo { 478 | group_id:group_id.parse::()?, 479 | group_name:group_name.to_owned(), 480 | member_count:0, 481 | max_member_count:0 482 | }) 483 | } 484 | 485 | #[allow(dead_code)] 486 | async fn get_msg(&self,msg_id:&str)-> Result<(), Box> { 487 | let _msg_info = self.http_get_json(&format!("/message/view?msg_id={msg_id}"),true).await?; 488 | Ok(()) 489 | } 490 | 491 | 492 | async fn get_group_member_info(&self,group_id:&str,user_id:&str,use_cache:bool)-> Result> { 493 | let group_info = self.http_get_json(&format!("/channel/view?target_id={group_id}"),use_cache).await?; 494 | let guild_id = group_info.get("guild_id").ok_or("get guild_id err")?.as_str().ok_or("guild_id not str")?; 495 | let stranger_info = self.http_get_json(&format!("/user/view?user_id={user_id}&guild_id={guild_id}"),use_cache).await?; 496 | let guild_info = self.http_get_json(&format!("/guild/view?guild_id={guild_id}"),use_cache).await?; 497 | let owner_id = guild_info.get("user_id").ok_or("get user_id err")?.as_str().ok_or("user_id not str")?; 498 | let role; 499 | if owner_id == user_id { 500 | role = "owner"; 501 | }else { 502 | let roles = stranger_info.get("roles").ok_or("get roles err")?.as_array().ok_or("roles not arr")?; 503 | if roles.len() != 0 { 504 | role = "admin"; 505 | } else { 506 | role = "member"; 507 | } 508 | } 509 | Ok(GroupMemberInfo { 510 | group_id:group_id.parse::()?, 511 | user_id:user_id.parse::()?, 512 | nickname:stranger_info.get("username").ok_or("get username err")?.as_str().ok_or("username not str")?.to_owned(), 513 | card:stranger_info.get("nickname").ok_or("get nickname err")?.as_str().ok_or("nickname not str")?.to_owned(), 514 | sex:"unknown".to_owned(), 515 | age:0, 516 | area:"".to_owned(), 517 | join_time:(stranger_info.get("joined_at").ok_or("get joined_at err")?.as_u64().ok_or("joined_at not u64")? / 1000) as i32, 518 | last_sent_time:(stranger_info.get("active_time").ok_or("get active_time err")?.as_u64().ok_or("active_time not u64")? / 1000) as i32, 519 | level:"0".to_owned(), 520 | role:role.to_owned(), 521 | unfriendly:false, 522 | title:"".to_owned(), 523 | title_expire_time:0, 524 | card_changeable:false, 525 | avatar:stranger_info.get("avatar").ok_or("avatar not found")?.as_str().ok_or("avatar not str")?.to_owned() 526 | }) 527 | } 528 | 529 | 530 | async fn set_group_kick(&self,group_id:&str,user_id:&str)-> Result<(), Box> { 531 | let group_info = self.http_get_json(&format!("/channel/view?target_id={group_id}"),true).await?; 532 | let guild_id = group_info.get("guild_id").ok_or("get guild_id err")?.as_str().ok_or("guild_id not str")?; 533 | let mut json:serde_json::Value = serde_json::from_str("{}")?; 534 | json["guild_id"] = guild_id.into(); 535 | json["target_id"] = user_id.into(); 536 | let _ret_json = self.http_post_json("/guild/kickout",&json).await?; 537 | Ok(()) 538 | } 539 | 540 | async fn delete_msg(&self,msg_id:&str)-> Result<(), Box> { 541 | let mut json:serde_json::Value = serde_json::from_str("{}")?; 542 | json["msg_id"] = msg_id.into(); 543 | let _ret_json = self.http_post_json("/message/delete",&json).await?; 544 | Ok(()) 545 | } 546 | 547 | async fn set_group_leave(&self,group_id:&str)-> Result<(), Box> { 548 | let group_info = self.http_get_json(&format!("/channel/view?target_id={group_id}"),true).await?; 549 | let guild_id = group_info.get("guild_id").ok_or("get guild_id err")?.as_str().ok_or("guild_id not str")?; 550 | let mut json:serde_json::Value = serde_json::from_str("{}")?; 551 | json["guild_id"] = guild_id.into(); 552 | let _ret_json = self.http_post_json("/guild/leave",&json).await?; 553 | Ok(()) 554 | } 555 | 556 | pub async fn get_friend_list(&self)-> Result, Box> { 557 | 558 | let mut ret_vec = vec![]; 559 | let friend_list = self.http_get_json(&format!("/user-chat/list"),false).await?; 560 | for it in friend_list.get("items").ok_or("items not found")?.as_array().ok_or("items not arr")? { 561 | let target_info = it.get("target_info").ok_or("target_info not found")?; 562 | let id = target_info.get("id").ok_or("id not found")?.as_str().ok_or("id not str")?; 563 | let username = target_info.get("username").ok_or("username not found")?.as_str().ok_or("username not str")?; 564 | let avatar = target_info.get("avatar").ok_or("avatar not found")?.as_str().ok_or("avatar not str")?; 565 | ret_vec.push(FriendInfo { 566 | user_id: id.parse::()?, 567 | nickname: username.to_owned(), 568 | remark: username.to_owned(), 569 | avatar: avatar.to_owned() 570 | }); 571 | } 572 | let meta = friend_list.get("meta").ok_or("meta not found")?; 573 | let page_total = meta.get("page_total").ok_or("page_total not found")?.as_i64().ok_or("page_total not i32")?; 574 | for page in 1..page_total{ 575 | let friend_list = self.http_get_json(&format!("/user-chat/list?page={page}"),false).await?; 576 | for it in friend_list.get("items").ok_or("items not found")?.as_array().ok_or("items not arr")? { 577 | let target_info = it.get("target_info").ok_or("target_info not found")?; 578 | let id = target_info.get("id").ok_or("id not found")?.as_str().ok_or("id not str")?; 579 | let username = target_info.get("username").ok_or("username not found")?.as_str().ok_or("username not str")?; 580 | let avatar = target_info.get("avatar").ok_or("avatar not found")?.as_str().ok_or("avatar not str")?; 581 | ret_vec.push(FriendInfo { 582 | user_id: id.parse::()?, 583 | nickname: username.to_owned(), 584 | remark: username.to_owned(), 585 | avatar: avatar.to_owned() 586 | }); 587 | } 588 | } 589 | Ok(ret_vec) 590 | } 591 | 592 | async fn set_group_name(&self,group_id:&str,name:&str)-> Result<(), Box> { 593 | let mut json:serde_json::Value = serde_json::from_str("{}")?; 594 | json["channel_id"] = group_id.into(); 595 | json["name"] = name.into(); 596 | let _ret_json = self.http_post_json("/channel/update",&json).await?; 597 | Ok(()) 598 | } 599 | 600 | async fn set_group_card(&self,group_id:&str,user_id:&str,card:&str)-> Result<(), Box> { 601 | let group_info = self.http_get_json(&format!("/channel/view?target_id={group_id}"),true).await?; 602 | let guild_id = group_info.get("guild_id").ok_or("get guild_id err")?.as_str().ok_or("guild_id not str")?; 603 | let mut json:serde_json::Value = serde_json::from_str("{}")?; 604 | json["guild_id"] = guild_id.into(); 605 | json["user_id"] = user_id.into(); 606 | json["nickname"] = card.into(); 607 | let _ret_json = self.http_post_json("/guild/nickname",&json).await?; 608 | Ok(()) 609 | } 610 | 611 | 612 | async fn send_group_msg(&self,tp:i32,group_id:&str,message:&str,quote:&str)-> Result> { 613 | let mut json:serde_json::Value = serde_json::from_str("{}")?; 614 | json["content"] = message.into(); 615 | json["target_id"] = group_id.into(); 616 | json["type"] = tp.into(); 617 | if quote != "" { 618 | json["quote"] = quote.into(); 619 | } 620 | let ret_json = self.http_post_json("/message/create",&json).await?; 621 | let msg_id = ret_json.get("msg_id").ok_or("msg_id not found")?.as_str().ok_or("msg_id not str")?; 622 | Ok(msg_id.to_owned()) 623 | } 624 | 625 | 626 | async fn send_private_msg(&self,tp:i32,user_id:&str,message:&str,quote:&str)-> Result> { 627 | let mut json:serde_json::Value = serde_json::from_str("{}")?; 628 | json["content"] = message.into(); 629 | json["target_id"] = user_id.into(); 630 | json["type"] = tp.into(); 631 | if quote != "" { 632 | json["quote"] = quote.into(); 633 | } 634 | let ret_json = self.http_post_json("/direct-message/create",&json).await?; 635 | let msg_id = ret_json.get("msg_id").ok_or("msg_id not found")?.as_str().ok_or("msg_id not str")?; 636 | Ok(msg_id.to_owned()) 637 | } 638 | 639 | 640 | async fn get_gateway(&self)-> Result> { 641 | let ret_json = self.http_get_json(&format!("/gateway/index?compress=1"),false).await?; 642 | Ok(ret_json.get("url").ok_or("get url err")?.as_str().ok_or("url not str")?.to_owned()) 643 | } 644 | 645 | pub async fn connect(&self)-> Result<(), Box> { 646 | let wss_url = self.get_gateway().await?; 647 | log::warn!("正在连接KOOK端口..."); 648 | let (ws_stream, _) = connect_async(wss_url).await?; 649 | let (mut write_halt,mut read_halt) = ws_stream.split(); 650 | let sn_ptr = self.sn.clone(); 651 | tokio::spawn(async move { 652 | loop { 653 | tokio::time::sleep(Duration::from_secs(30)).await; 654 | let json_str = serde_json::json!({ 655 | "s": 2, 656 | "sn": sn_ptr.load(std::sync::atomic::Ordering::Relaxed) 657 | }).to_string(); 658 | log::info!("发送KOOK心跳:{json_str}"); 659 | let foo = write_halt.send(tungstenite::Message::Text(json_str)).await; 660 | if foo.is_err() { 661 | log::error!("发送KOOK心跳失败"); 662 | break; 663 | } 664 | } 665 | }); 666 | while let Some(msg) = read_halt.next().await { 667 | let raw_msg = msg?; 668 | if raw_msg.is_binary() { 669 | // kook返回的数据是压缩的,需要先解压 670 | let bin = raw_msg.into_data(); 671 | let mut d = ZlibDecoder::new(bin.as_slice()); 672 | let mut s = String::new(); 673 | d.read_to_string(&mut s).unwrap(); 674 | let js:serde_json::Value = serde_json::from_str(&s)?; 675 | 676 | let s = js.get("s").ok_or("s not found")?.as_i64().ok_or("s not i64")?; 677 | if s == 5 { 678 | log::warn!("正在重连KOOK"); 679 | break; 680 | } 681 | else if s == 1 { 682 | log::warn!("连接KOOK成功"); 683 | } 684 | else if s == 0 { 685 | log::info!("收到KOOK事件:{}",js.to_string()); 686 | let d = js.get("d").ok_or("d not found")?; 687 | let sn = js.get("sn").ok_or("sn not found")?.as_i64().ok_or("sn not i64")?; 688 | self.sn.store(sn, std::sync::atomic::Ordering::Relaxed); 689 | let rst = self.deal_kook_event(d.clone()).await; 690 | if rst.is_err() { 691 | log::error!("处理KOOK事件出错:{}",rst.err().unwrap()); 692 | } 693 | }else if s == 3 { 694 | log::info!("收到KOOK心跳响应包"); 695 | } else { 696 | log::error!("收到未知的KOOK数据:{}",js.to_string()); 697 | } 698 | } 699 | } 700 | Ok(()) 701 | } 702 | pub async fn get_lifecycle_event(&self) -> Result> { 703 | let self_id = self.self_id; 704 | let tm = SystemTime::now().duration_since(std::time::UNIX_EPOCH)?.as_secs().to_string(); 705 | let ret = format!("{{\"meta_event_type\":\"lifecycle\",\"post_type\":\"meta_event\",\"self_id\":{self_id},\"sub_type\":\"connect\",\"time\":{tm}}}"); 706 | Ok(ret) 707 | } 708 | pub async fn get_heartbeat_event(&self) -> Result> { 709 | let self_id = self.self_id; 710 | let tm = SystemTime::now().duration_since(std::time::UNIX_EPOCH)?.as_secs().to_string(); 711 | let js = serde_json::json!({ 712 | "time":tm, 713 | "self_id":self_id, 714 | "post_type":"meta_event", 715 | "meta_event_type":"heartbeat", 716 | "interval":5000, 717 | "status":{ 718 | "online":true, 719 | "good":true 720 | } 721 | }); 722 | Ok(js.to_string()) 723 | } 724 | 725 | async fn deal_group_file_upload_event(&self,data:&serde_json::Value,user_id:u64) -> Result> { 726 | 727 | let group_id_str = data.get("target_id").ok_or("target_id not found")?.as_str().ok_or("target_id not str")?; 728 | let group_id = group_id_str.parse::()?; 729 | let message = data.get("content").ok_or("content not found")?.as_str().ok_or("content not str")?.to_owned(); 730 | 731 | #[derive(Serialize)] 732 | struct FileInfo { 733 | url:String, 734 | name:String, 735 | size:i64, 736 | busid:i64 737 | } 738 | fn get_file(message:&str) -> Result, Box> { 739 | let err = "get file err"; 740 | let js_arr:serde_json::Value = serde_json::from_str(&message)?; 741 | let card_arr = js_arr.as_array().ok_or(err)?; 742 | if card_arr.len() != 1 { 743 | return Ok(None); 744 | } 745 | let md_arr = card_arr.get(0).unwrap().get("modules").ok_or(err)?.as_array().ok_or(err)?; 746 | if md_arr.len() != 1 { 747 | return Ok(None); 748 | } 749 | let obj = md_arr.get(0).unwrap(); 750 | let tp = obj.get("type").ok_or(err)?.as_str().ok_or(err)?; 751 | if tp != "file" { 752 | return Ok(None); 753 | } 754 | let url = obj.get("src").ok_or(err)?.as_str().ok_or(err)?.to_owned(); 755 | if !url.starts_with("https://img.kookapp.cn/") { 756 | return Ok(None); 757 | } 758 | let name = obj.get("title").ok_or(err)?.as_str().ok_or(err)?.to_owned(); 759 | let size = obj.get("size").ok_or(err)?.as_i64().ok_or(err)?.to_owned(); 760 | return Ok(Some(FileInfo{ 761 | url, 762 | name, 763 | size, 764 | busid:0 765 | })); 766 | } 767 | 768 | // 处理文件 769 | if let Ok(file) = get_file(&message) { 770 | if let Some(f) = file { 771 | let event_json = serde_json::json!({ 772 | "time":SystemTime::now().duration_since(std::time::UNIX_EPOCH)?.as_secs(), 773 | "self_id":self.self_id, 774 | "post_type":"notice", 775 | "notice_type":"group_upload", 776 | "group_id":group_id, 777 | "user_id":user_id, 778 | "file":f 779 | }); 780 | self.send_to_onebot_client(&event_json).await; 781 | return Ok(true); 782 | } 783 | } 784 | return Ok(false); 785 | } 786 | 787 | 788 | async fn deal_audio_msg(&self,data:&serde_json::Value,msg:&mut String) -> Result> { 789 | 790 | let message = data.get("content").ok_or("content not found")?.as_str().ok_or("content not str")?.to_owned(); 791 | let err = "get file err"; 792 | let js_arr:serde_json::Value = serde_json::from_str(&message)?; 793 | let card_arr = js_arr.as_array().ok_or(err)?; 794 | if card_arr.len() != 1 { 795 | return Ok(false); 796 | } 797 | 798 | let md_arr = card_arr.get(0).unwrap().get("modules").ok_or(err)?.as_array().ok_or(err)?; 799 | if md_arr.len() != 1 { 800 | return Ok(false); 801 | } 802 | let obj = md_arr.get(0).unwrap(); 803 | let tp = obj.get("type").ok_or(err)?.as_str().ok_or(err)?; 804 | if tp != "audio" { 805 | return Ok(false); 806 | } 807 | if get_json_str(obj, "title") != "" && get_json_str(obj, "cover") != "" { 808 | // 说明是音乐分享,不是语音 809 | return Ok(true); 810 | } 811 | let url = obj.get("src").ok_or(err)?.as_str().ok_or(err)?; 812 | let url_t = crate::cqtool::cq_params_encode(url); 813 | msg.push_str(&format!("[CQ:record,file={},url={}]",url_t,url_t)); 814 | return Ok(true); 815 | 816 | } 817 | 818 | async fn deal_group_message_event(&self,data:&serde_json::Value,user_id:u64) -> Result<(), Box> { 819 | let group_id_str = data.get("target_id").ok_or("target_id not found")?.as_str().ok_or("target_id not str")?; 820 | let group_id = group_id_str.parse::()?; 821 | let message = data.get("content").ok_or("content not found")?.as_str().ok_or("content not str")?.to_owned(); 822 | let extra = data.get("extra").ok_or("extra not found")?; 823 | // 获取发送者 824 | let sender: GroupMemberInfo = self.get_group_member_info(&group_id.to_string(),&user_id.to_string(),true).await?; 825 | 826 | // 获取消息类型 827 | let msg_type = data.get("type").ok_or("type not found")?.as_i64().ok_or("type not i64")?; 828 | 829 | let mut msg = String::new(); 830 | 831 | // 处理卡牌消息 832 | if msg_type == 10 { // 卡牌消息 833 | // 处理群文件上传事件 834 | if self.deal_group_file_upload_event(data,user_id).await? { 835 | return Ok(()); 836 | } 837 | 838 | if self.deal_audio_msg(data,&mut msg).await? { 839 | // do nothing 840 | } 841 | else { 842 | // 未知的card 843 | msg.push_str("卡片消息"); 844 | } 845 | } else { 846 | // 处理回复 847 | if let Some(quote) = extra.get("quote") { 848 | let rong_id = get_json_str(quote, "rong_id"); 849 | let cq_id = crate::msgid_tool::get_cq_msg_id(&rong_id).0; 850 | msg.push_str(&format!("[CQ:reply,id={cq_id}]")); 851 | } 852 | 853 | // 转为CQ格式 854 | msg.push_str(&kook_msg_to_cq(msg_type,&message)?); 855 | } 856 | 857 | if msg == "" { 858 | return Ok(()); 859 | } 860 | 861 | // 存msg_id 862 | let raw_msg_id = data.get("msg_id").ok_or("msg_id not found")?.as_str().ok_or("msg_id not str")?; 863 | let msg_id = crate::msgid_tool::add_msg_id(QMessageStruct {raw_ids:vec![raw_msg_id.to_owned()], user_id }); 864 | 865 | let event_json = serde_json::json!({ 866 | "time":SystemTime::now().duration_since(std::time::UNIX_EPOCH)?.as_secs(), 867 | "self_id":self.self_id, 868 | "post_type":"message", 869 | "message_type":"group", 870 | "sub_type":"normal", 871 | "message_id":msg_id, 872 | "group_id":group_id, 873 | "user_id":user_id, 874 | "message":msg, 875 | "raw_message":msg, 876 | "font":0, 877 | "sender":sender 878 | }); 879 | self.send_to_onebot_client(&event_json).await; 880 | Ok(()) 881 | } 882 | 883 | async fn deal_private_message_event(&self,data:&serde_json::Value,user_id:u64) -> Result<(), Box> { 884 | let message = data.get("content").ok_or("content not found")?.as_str().ok_or("content not str")?.to_owned(); 885 | 886 | let extra = data.get("extra").ok_or("extra not found")?; 887 | let author = extra.get("author").ok_or("author not found")?; 888 | 889 | let username = author.get("username").ok_or("username not found")?.as_str().ok_or("username not str")?; 890 | 891 | let avatar = author.get("avatar").ok_or("avatar not found")?.as_str().ok_or("avatar not str")?; 892 | 893 | let sender: FriendInfo = FriendInfo { 894 | user_id, 895 | nickname: username.to_owned(), 896 | remark: username.to_owned(), 897 | avatar: avatar.to_owned() 898 | }; 899 | 900 | let msg_type = data.get("type").ok_or("type not found")?.as_i64().ok_or("type not i64")?; 901 | 902 | let mut msg = String::new(); 903 | 904 | // 处理卡牌消息 905 | if msg_type == 10 { // 卡牌消息 906 | 907 | if self.deal_audio_msg(data,&mut msg).await? { 908 | // do nothing 909 | } 910 | else { 911 | // 未知的card 912 | msg.push_str("卡片消息"); 913 | } 914 | }else { 915 | // 处理回复 916 | if let Some(quote) = extra.get("quote") { 917 | let rong_id = get_json_str(quote, "rong_id"); 918 | let cq_id = crate::msgid_tool::get_cq_msg_id(&rong_id).0; 919 | msg.push_str(&format!("[CQ:reply,id={cq_id}]")); 920 | } 921 | 922 | // 转为CQ格式 923 | msg.push_str(&kook_msg_to_cq(msg_type,&message)?); 924 | } 925 | 926 | if msg == "" { 927 | return Ok(()); 928 | } 929 | 930 | let raw_msg_id = data.get("msg_id").ok_or("msg_id not found")?.as_str().ok_or("msg_id not str")?; 931 | let msg_id = crate::msgid_tool::add_msg_id(QMessageStruct {raw_ids:vec![raw_msg_id.to_owned()], user_id }); 932 | 933 | let event_json = serde_json::json!({ 934 | "time":SystemTime::now().duration_since(std::time::UNIX_EPOCH)?.as_secs(), 935 | "self_id":self.self_id, 936 | "post_type":"message", 937 | "message_type":"private", 938 | "sub_type":"friend", 939 | "message_id":msg_id, 940 | "user_id":user_id, 941 | "message":msg, 942 | "raw_message":msg, 943 | "font":0, 944 | "sender":sender 945 | }); 946 | self.send_to_onebot_client(&event_json).await; 947 | Ok(()) 948 | } 949 | 950 | 951 | async fn deal_group_decrease_event(&self,data:&serde_json::Value) -> Result<(), Box> { 952 | let guild_id_str = data.get("target_id").ok_or("target_id not found")?.as_str().ok_or("target_id not str")?; 953 | let group_list = self.get_channel_list(guild_id_str).await?; 954 | let user_id_str = data.get("extra").ok_or("extra not found")? 955 | .get("body").ok_or("body not found")? 956 | .get("user_id").ok_or("user_id not found")? 957 | .as_str().ok_or("user_id not str")?; 958 | let user_id = user_id_str.parse::()?; 959 | for it in &group_list { 960 | 961 | let event_json = serde_json::json!({ 962 | "time":SystemTime::now().duration_since(std::time::UNIX_EPOCH)?.as_secs(), 963 | "self_id":self.self_id, 964 | "post_type":"notice", 965 | "notice_type":"group_decrease", 966 | "sub_type":"leave", 967 | "group_id":it.group_id, 968 | "operator_id":user_id, 969 | "user_id":user_id, 970 | }); 971 | self.send_to_onebot_client(&event_json).await; 972 | } 973 | Ok(()) 974 | } 975 | 976 | async fn deal_group_recall(&self,data:&serde_json::Value) -> Result<(), Box> { 977 | let msg_id_str = data.get("extra").ok_or("extra not found")? 978 | .get("body").ok_or("body not found")? 979 | .get("msg_id").ok_or("msg_id not found")? 980 | .as_str().ok_or("msg_id not str")?; 981 | let group_id_str = data.get("extra").ok_or("extra not found")? 982 | .get("body").ok_or("body not found")? 983 | .get("channel_id").ok_or("channel_id not found")? 984 | .as_str().ok_or("channel_id not str")?; 985 | let group_id = group_id_str.parse::()?; 986 | let (cq_id,user_id) = crate::msgid_tool::get_cq_msg_id(msg_id_str); 987 | // self.get_msg(msg_id_str).await?; 988 | let event_json = serde_json::json!({ 989 | "time":SystemTime::now().duration_since(std::time::UNIX_EPOCH)?.as_secs(), 990 | "self_id":self.self_id, 991 | "post_type":"notice", 992 | "notice_type":"group_recall", 993 | "group_id":group_id, 994 | "user_id": user_id, 995 | "operator_id":1, 996 | "message_id": cq_id 997 | }); 998 | self.send_to_onebot_client(&event_json).await; 999 | Ok(()) 1000 | } 1001 | 1002 | 1003 | async fn deal_private_recall(&self,data:&serde_json::Value) -> Result<(), Box> { 1004 | let msg_id_str = data.get("extra").ok_or("extra not found")? 1005 | .get("body").ok_or("body not found")? 1006 | .get("msg_id").ok_or("msg_id not found")? 1007 | .as_str().ok_or("msg_id not str")?; 1008 | let user_id_str = data.get("extra").ok_or("extra not found")? 1009 | .get("body").ok_or("body not found")? 1010 | .get("author_id").ok_or("author_id not found")? 1011 | .as_str().ok_or("author_id not str")?; 1012 | let user_id = user_id_str.parse::()?; 1013 | let (cq_id,_user_id) = crate::msgid_tool::get_cq_msg_id(msg_id_str); 1014 | // self.get_msg(msg_id_str).await?; 1015 | let event_json = serde_json::json!({ 1016 | "time":SystemTime::now().duration_since(std::time::UNIX_EPOCH)?.as_secs(), 1017 | "self_id":self.self_id, 1018 | "post_type":"notice", 1019 | "notice_type":"friend_recall", 1020 | "user_id": user_id, 1021 | "message_id": cq_id 1022 | }); 1023 | self.send_to_onebot_client(&event_json).await; 1024 | Ok(()) 1025 | } 1026 | 1027 | 1028 | 1029 | async fn deal_group_increase_event(&self,data:&serde_json::Value) -> Result<(), Box> { 1030 | let guild_id_str = data.get("target_id").ok_or("target_id not found")?.as_str().ok_or("target_id not str")?; 1031 | let group_list = self.get_channel_list(guild_id_str).await?; 1032 | let user_id_str = data.get("extra").ok_or("extra not found")? 1033 | .get("body").ok_or("body not found")? 1034 | .get("user_id").ok_or("user_id not found")? 1035 | .as_str().ok_or("user_id not str")?; 1036 | let user_id = user_id_str.parse::()?; 1037 | for it in &group_list { 1038 | 1039 | let event_json = serde_json::json!({ 1040 | "time":SystemTime::now().duration_since(std::time::UNIX_EPOCH)?.as_secs(), 1041 | "self_id":self.self_id, 1042 | "post_type":"notice", 1043 | "notice_type":"group_increase", 1044 | "sub_type":"approve", 1045 | "message_id":0, 1046 | "group_id":it.group_id, 1047 | "operator_id":user_id, 1048 | "user_id":user_id, 1049 | }); 1050 | self.send_to_onebot_client(&event_json).await; 1051 | } 1052 | Ok(()) 1053 | } 1054 | async fn deal_group_event(&self,data:&serde_json::Value) -> Result<(), Box> { 1055 | let user_id_str = data.get("author_id").ok_or("author_id not found")?.as_str().ok_or("author_id not str")?; 1056 | let user_id = user_id_str.parse::()?; 1057 | if user_id == 1 { // 系统消息 1058 | let tp = data.get("type").ok_or("type not found")?.as_i64().ok_or("type not i64")?; 1059 | if tp != 255 { 1060 | return Ok(()); // 不是系统消息,直接返回 1061 | } 1062 | let sub_type = data.get("extra").ok_or("extra not found")?.get("type").ok_or("type not found")?.as_str().ok_or("type not str")?; 1063 | if sub_type == "exited_guild" { 1064 | self.deal_group_decrease_event(data).await?; 1065 | } else if sub_type == "joined_guild" { 1066 | self.deal_group_increase_event(data).await?; 1067 | } else if sub_type == "deleted_message" { 1068 | self.deal_group_recall(data).await?; 1069 | } 1070 | } else { 1071 | let self_id = self.self_id; 1072 | if user_id != self_id { 1073 | self.deal_group_message_event(data,user_id).await?; 1074 | } 1075 | 1076 | } 1077 | Ok(()) 1078 | } 1079 | 1080 | async fn deal_person_event(&self,data:&serde_json::Value) -> Result<(), Box> { 1081 | let user_id_str = data.get("author_id").ok_or("author_id not found")?.as_str().ok_or("author_id not str")?; 1082 | let user_id = user_id_str.parse::()?; 1083 | if user_id == 1 { // 系统消息 1084 | let tp = data.get("type").ok_or("type not found")?.as_i64().ok_or("type not i64")?; 1085 | if tp != 255 { 1086 | return Ok(()); // 不是系统消息,直接返回 1087 | } 1088 | let sub_type = data.get("extra").ok_or("extra not found")?.get("type").ok_or("type not found")?.as_str().ok_or("type not str")?; 1089 | if sub_type == "self_exited_guild" { 1090 | // self.deal_group_kick_me_event(data).await?; 1091 | } else if sub_type == "deleted_private_message" { 1092 | self.deal_private_recall(data).await?; 1093 | } 1094 | } else { 1095 | let self_id = self.self_id; 1096 | if user_id != self_id { 1097 | self.deal_private_message_event(data,user_id).await?; 1098 | } 1099 | } 1100 | Ok(()) 1101 | } 1102 | async fn deal_kook_event(&self,data:serde_json::Value)-> Result<(), Box> { 1103 | let tp = data.get("channel_type").ok_or("channel_type not found")?.as_str().ok_or("channel_type not str")?; 1104 | if tp == "GROUP" { 1105 | self.deal_group_event(&data).await?; 1106 | }else if tp == "PERSON" { 1107 | self.deal_person_event(&data).await?; 1108 | } 1109 | Ok(()) 1110 | } 1111 | 1112 | async fn make_kook_msg(&self,message_arr:&serde_json::Value,is_group:bool) -> Result<(Vec<(i32, String)>,String), Box> { 1113 | let mut to_send_data: Vec<(i32, String)> = vec![]; 1114 | let mut quote = String::new(); 1115 | let mut last_type = 1; 1116 | for it in message_arr.as_array().ok_or("message not arr")? { 1117 | let tp = it.get("type").ok_or("type not found")?; 1118 | if tp == "text"{ 1119 | let t = it.get("data").ok_or("data not found")?.get("text").ok_or("text not found")?.as_str().ok_or("text not str")?.to_owned(); 1120 | let s = make_kook_text(&t); 1121 | if last_type == 1 && to_send_data.len() != 0 { 1122 | let l = to_send_data.len(); 1123 | to_send_data.get_mut(l - 1).unwrap().1.push_str(&s); 1124 | } else { 1125 | to_send_data.push((1,s)); 1126 | last_type = 1; 1127 | } 1128 | } else if tp == "image"{ 1129 | let file = it.get("data").ok_or("data not found")?.get("file").ok_or("file not found")?.as_str().ok_or("file not str")?; 1130 | let file_url = self.upload_asset(file).await?; 1131 | to_send_data.push((2,file_url)); 1132 | last_type = 2; 1133 | } 1134 | else if tp == "at"{ 1135 | if !is_group { 1136 | continue; 1137 | } 1138 | let qq = to_json_str(it.get("data").ok_or("data not found")?.get("qq").ok_or("qq not found")?); 1139 | let at_str = &format!("(met){}(met)",qq); 1140 | if last_type == 1 && to_send_data.len() != 0 { 1141 | let l = to_send_data.len(); 1142 | to_send_data.get_mut(l - 1).unwrap().1.push_str(at_str); 1143 | } else { 1144 | to_send_data.push((1,at_str.to_owned())); 1145 | last_type = 1; 1146 | } 1147 | } else if tp == "reply"{ 1148 | if quote != "" { 1149 | continue; 1150 | } 1151 | let cq_id = to_json_str(it.get("data").ok_or("data not found")?.get("id").ok_or("reply not found")?); 1152 | let kook_id = crate::msgid_tool::get_msg_id(cq_id.parse::()?); 1153 | quote = kook_id.raw_ids.get(0).unwrap_or(&String::new()).to_owned(); 1154 | } 1155 | else if tp == "music"{ 1156 | let music_type = it.get("data").ok_or("data not found")?.get("type").ok_or("type not found")?.as_str().ok_or("type not str")?; 1157 | if music_type == "custom" { 1158 | let data = it.get("data").ok_or("data not found")?; 1159 | let mut audio = get_json_str(data, "audio"); 1160 | if audio == "" { 1161 | audio = get_json_str(data, "voice"); 1162 | } 1163 | let title = get_json_str(data, "title"); 1164 | let image = get_json_str(data, "image"); 1165 | let js = serde_json::json!([{ 1166 | "type": "card", 1167 | "theme": "secondary", 1168 | "size": "lg", 1169 | "modules": [ 1170 | { 1171 | "type": "audio", 1172 | "title": title, 1173 | "src": audio, 1174 | "cover": image 1175 | }] 1176 | }]); 1177 | to_send_data.push((10,js.to_string())); 1178 | last_type = 10; 1179 | }else if music_type == "163" { 1180 | let data = it.get("data").ok_or("data not found")?; 1181 | let id = get_json_str(data, "id"); 1182 | let url = format!("https://api.gumengya.com/Api/Netease?format=json&id={id}"); 1183 | let mut header: HashMap = HashMap::new(); 1184 | header.insert("User-Agent".to_owned(), "https://github.com/super1207/KookOneBot".to_owned()); 1185 | let ret = Self::http_post(&url,vec![],&header,false).await?; 1186 | let ret_json:serde_json::Value = serde_json::from_str(&String::from_utf8(ret)?)?; 1187 | let music_data = ret_json.get("data").ok_or("data not found")?; 1188 | let audio = get_json_str(music_data, "url"); 1189 | let title = get_json_str(music_data, "title"); 1190 | let image = get_json_str(music_data, "pic"); 1191 | let js = serde_json::json!([{ 1192 | "type": "card", 1193 | "theme": "secondary", 1194 | "size": "lg", 1195 | "modules": [ 1196 | { 1197 | "type": "audio", 1198 | "title": title, 1199 | "src": audio, 1200 | "cover": image 1201 | }] 1202 | }]); 1203 | to_send_data.push((10,js.to_string())); 1204 | last_type = 10; 1205 | }else if music_type == "qq" { 1206 | let data = it.get("data").ok_or("data not found")?; 1207 | let id = get_json_str(data, "id"); 1208 | let url = format!("https://api.gumengya.com/Api/Tencent?format=json&id={id}"); 1209 | let mut header: HashMap = HashMap::new(); 1210 | header.insert("User-Agent".to_owned(), "https://github.com/super1207/KookOneBot".to_owned()); 1211 | let ret = Self::http_post(&url,vec![],&header,false).await?; 1212 | let ret_json:serde_json::Value = serde_json::from_str(&String::from_utf8(ret)?)?; 1213 | let music_data = ret_json.get("data").ok_or("data not found")?; 1214 | let mut audio = get_json_str(music_data, "url"); 1215 | lazy_static! { 1216 | static ref AT_REGEX : Regex = Regex::new( 1217 | r"://(.+)/amobile" 1218 | ).unwrap(); 1219 | } 1220 | audio = AT_REGEX.replace_all(&audio, "://aqqmusic.tc.qq.com/amobile").to_string(); 1221 | let title = get_json_str(music_data, "title"); 1222 | let image = get_json_str(music_data, "pic"); 1223 | let js = serde_json::json!([{ 1224 | "type": "card", 1225 | "theme": "secondary", 1226 | "size": "lg", 1227 | "modules": [ 1228 | { 1229 | "type": "audio", 1230 | "title": title, 1231 | "src": audio, 1232 | "cover": image 1233 | }] 1234 | }]); 1235 | to_send_data.push((10,js.to_string())); 1236 | last_type = 10; 1237 | } 1238 | } 1239 | else if tp == "record" { 1240 | let data = it.get("data").ok_or("data not found")?; 1241 | let file = get_json_str(data, "file"); 1242 | let url = self.upload_asset(&file).await?; 1243 | let js = serde_json::json!([{ 1244 | "type": "card", 1245 | "theme": "secondary", 1246 | "size": "lg", 1247 | "modules": [ 1248 | { 1249 | "type": "audio", 1250 | "src": url, 1251 | }] 1252 | }]); 1253 | to_send_data.push((10,js.to_string())); 1254 | last_type = 10; 1255 | } 1256 | else { 1257 | let j = serde_json::json!([it]); 1258 | let s = arr_to_cq_str(&j)?; 1259 | let s2 = make_kook_text(&s); 1260 | if last_type == 1 && to_send_data.len() != 0 { 1261 | let l = to_send_data.len(); 1262 | to_send_data.get_mut(l - 1).unwrap().1.push_str(&s2); 1263 | } else { 1264 | to_send_data.push((1,s2)); 1265 | last_type = 1; 1266 | } 1267 | } 1268 | } 1269 | Ok((to_send_data,quote)) 1270 | } 1271 | 1272 | fn get_auto_escape_from_params(&self,params:&serde_json::Value) -> bool { 1273 | let is_auto_escape = get_json_bool(params, "auto_escape"); 1274 | return is_auto_escape; 1275 | } 1276 | 1277 | async fn deal_ob_send_group_msg(&self,params:&serde_json::Value,_js:&serde_json::Value,echo:&serde_json::Value) -> Result> { 1278 | let group_id = get_json_str(params,"group_id"); 1279 | let message_arr:serde_json::Value; 1280 | let message_rst = params.get("message").ok_or("message not found")?; 1281 | 1282 | if message_rst.is_string() { 1283 | if self.get_auto_escape_from_params(¶ms) { 1284 | message_arr = serde_json::json!( 1285 | [{"type":"text","data":{ 1286 | "text": message_rst.as_str() 1287 | }}] 1288 | ); 1289 | } else { 1290 | message_arr = str_msg_to_arr(message_rst)?; 1291 | } 1292 | }else { 1293 | message_arr = params.get("message").ok_or("message not found")?.to_owned(); 1294 | } 1295 | 1296 | let (to_send_data, mut quote) = self.make_kook_msg(&message_arr,true).await?; 1297 | 1298 | let mut msg_ids = vec![]; 1299 | for (tp,msg) in & to_send_data.clone() { 1300 | let msg_id = self.send_group_msg(*tp,&group_id,msg,"e).await?; 1301 | msg_ids.push(msg_id); 1302 | quote = "".to_owned(); 1303 | } 1304 | let msg_id = crate::msgid_tool::add_msg_id(QMessageStruct{ raw_ids: msg_ids, user_id: self.self_id }); 1305 | let send_json = serde_json::json!({ 1306 | "status":"ok", 1307 | "retcode":0, 1308 | "data": { 1309 | "message_id":msg_id 1310 | }, 1311 | "echo":echo 1312 | }); 1313 | Ok(send_json) 1314 | } 1315 | 1316 | 1317 | async fn deal_ob_send_private_msg(&self,params:&serde_json::Value,_js:&serde_json::Value,echo:&serde_json::Value) -> Result> { 1318 | let user_id = get_json_str(params,"user_id"); 1319 | let message_arr:serde_json::Value; 1320 | let message_rst = params.get("message").ok_or("message not found")?; 1321 | 1322 | if message_rst.is_string() { 1323 | if self.get_auto_escape_from_params(¶ms) { 1324 | message_arr = serde_json::json!( 1325 | [{"type":"text","data":{ 1326 | "text": message_rst.as_str() 1327 | }}] 1328 | ); 1329 | } else { 1330 | message_arr = str_msg_to_arr(message_rst)?; 1331 | } 1332 | }else { 1333 | message_arr = params.get("message").ok_or("message not found")?.to_owned(); 1334 | } 1335 | 1336 | let (to_send_data, mut quote) = self.make_kook_msg(&message_arr,true).await?; 1337 | 1338 | let mut msg_ids = vec![]; 1339 | for (tp,msg) in & to_send_data.clone() { 1340 | let msg_id = self.send_private_msg(*tp,&user_id,msg,"e).await?; 1341 | msg_ids.push(msg_id); 1342 | quote = "".to_owned(); 1343 | } 1344 | let msg_id = crate::msgid_tool::add_msg_id(QMessageStruct{ raw_ids: msg_ids, user_id: self.self_id }); 1345 | let send_json = serde_json::json!({ 1346 | "status":"ok", 1347 | "retcode":0, 1348 | "data": { 1349 | "message_id":msg_id 1350 | }, 1351 | "echo":echo 1352 | }); 1353 | Ok(send_json) 1354 | } 1355 | 1356 | async fn deal_ob_get_login_info(&self,_params:&serde_json::Value,_js:&serde_json::Value,echo:&serde_json::Value) -> Result> { 1357 | let info: LoginInfo = self.get_login_info().await?; 1358 | let send_json = serde_json::json!({ 1359 | "status":"ok", 1360 | "retcode":0, 1361 | "data": info, 1362 | "echo":echo 1363 | }); 1364 | Ok(send_json) 1365 | } 1366 | 1367 | async fn deal_ob_get_stranger_info(&self,params:&serde_json::Value,_js:&serde_json::Value,echo:&serde_json::Value) -> Result> { 1368 | let user_id = get_json_str(params,"user_id"); 1369 | let use_cache = !get_json_bool(params,"no_cache"); 1370 | let info = self.get_stranger_info(&user_id,use_cache).await?; 1371 | let send_json = serde_json::json!({ 1372 | "status":"ok", 1373 | "retcode":0, 1374 | "data": info, 1375 | "echo":echo 1376 | }); 1377 | Ok(send_json) 1378 | } 1379 | 1380 | async fn deal_ob_get_group_info(&self,params:&serde_json::Value,_js:&serde_json::Value,echo:&serde_json::Value) -> Result> { 1381 | let group_id = get_json_str(params,"group_id"); 1382 | let use_cache = !get_json_bool(params,"no_cache"); 1383 | let info = self.get_group_info(&group_id,use_cache).await?; 1384 | let send_json = serde_json::json!({ 1385 | "status":"ok", 1386 | "retcode":0, 1387 | "data": info, 1388 | "echo":echo 1389 | }); 1390 | Ok(send_json) 1391 | } 1392 | 1393 | async fn deal_ob_get_group_list(&self,_params:&serde_json::Value,_js:&serde_json::Value,echo:&serde_json::Value) -> Result> { 1394 | let info = self.get_group_list().await?; 1395 | let send_json = serde_json::json!({ 1396 | "status":"ok", 1397 | "retcode":0, 1398 | "data": info, 1399 | "echo":echo 1400 | }); 1401 | Ok(send_json) 1402 | } 1403 | 1404 | 1405 | async fn deal_ob_get_group_member_info(&self,params:&serde_json::Value,_js:&serde_json::Value,echo:&serde_json::Value) -> Result> { 1406 | let group_id = get_json_str(params,"group_id"); 1407 | let user_id = get_json_str(params,"user_id"); 1408 | let use_cache = !get_json_bool(params,"no_cache"); 1409 | let info = self.get_group_member_info(&group_id, &user_id,use_cache).await?; 1410 | let send_json = serde_json::json!({ 1411 | "status":"ok", 1412 | "retcode":0, 1413 | "data": info, 1414 | "echo":echo 1415 | }); 1416 | Ok(send_json) 1417 | } 1418 | 1419 | async fn deal_ob_set_group_kick(&self,params:&serde_json::Value,_js:&serde_json::Value,echo:&serde_json::Value) -> Result> { 1420 | let group_id = get_json_str(params,"group_id"); 1421 | let user_id = get_json_str(params,"user_id"); 1422 | self.set_group_kick(&group_id, &user_id).await?; 1423 | let send_json = serde_json::json!({ 1424 | "status":"ok", 1425 | "retcode":0, 1426 | "data": {}, 1427 | "echo":echo 1428 | }); 1429 | Ok(send_json) 1430 | } 1431 | 1432 | async fn deal_ob_delete_msg(&self,params:&serde_json::Value,_js:&serde_json::Value,echo:&serde_json::Value) -> Result> { 1433 | let msg_id = get_json_str(params,"message_id").parse::()?; 1434 | let msg_ids = crate::msgid_tool::get_msg_id(msg_id); 1435 | for it in msg_ids.raw_ids { 1436 | self.delete_msg(&it).await?; 1437 | } 1438 | let send_json = serde_json::json!({ 1439 | "status":"ok", 1440 | "retcode":0, 1441 | "data": {}, 1442 | "echo":echo 1443 | }); 1444 | Ok(send_json) 1445 | } 1446 | 1447 | async fn deal_ob_set_group_leave(&self,params:&serde_json::Value,_js:&serde_json::Value,echo:&serde_json::Value) -> Result> { 1448 | let group_id = get_json_str(params,"group_id"); 1449 | self.set_group_leave(&group_id).await?; 1450 | let send_json = serde_json::json!({ 1451 | "status":"ok", 1452 | "retcode":0, 1453 | "data": {}, 1454 | "echo":echo 1455 | }); 1456 | Ok(send_json) 1457 | } 1458 | 1459 | async fn deal_ob_set_group_name(&self,params:&serde_json::Value,_js:&serde_json::Value,echo:&serde_json::Value) -> Result> { 1460 | let group_id = get_json_str(params,"group_id"); 1461 | let group_name = get_json_str(params,"group_name"); 1462 | self.set_group_name(&group_id,&group_name).await?; 1463 | let send_json = serde_json::json!({ 1464 | "status":"ok", 1465 | "retcode":0, 1466 | "data": {}, 1467 | "echo":echo 1468 | }); 1469 | Ok(send_json) 1470 | } 1471 | 1472 | async fn deal_ob_set_group_card(&self,params:&serde_json::Value,_js:&serde_json::Value,echo:&serde_json::Value) -> Result> { 1473 | let group_id = get_json_str(params,"group_id"); 1474 | let user_id = get_json_str(params,"user_id"); 1475 | let card = get_json_str(params,"card"); 1476 | self.set_group_card(&group_id,&user_id,&card).await?; 1477 | let send_json = serde_json::json!({ 1478 | "status":"ok", 1479 | "retcode":0, 1480 | "data": {}, 1481 | "echo":echo 1482 | }); 1483 | Ok(send_json) 1484 | } 1485 | 1486 | async fn deal_ob_get_friend_list(&self,_params:&serde_json::Value,_js:&serde_json::Value,echo:&serde_json::Value) -> Result> { 1487 | let info = self.get_friend_list().await?; 1488 | let send_json = serde_json::json!({ 1489 | "status":"ok", 1490 | "retcode":0, 1491 | "data": info, 1492 | "echo":echo 1493 | }); 1494 | Ok(send_json) 1495 | } 1496 | 1497 | async fn deal_ob_get_group_member_list(&self,params:&serde_json::Value,_js:&serde_json::Value,echo:&serde_json::Value) -> Result> { 1498 | let group_id = get_json_str(params,"group_id"); 1499 | let info = self.get_group_member_list(&group_id).await?; 1500 | let send_json = serde_json::json!({ 1501 | "status":"ok", 1502 | "retcode":0, 1503 | "data": info, 1504 | "echo":echo 1505 | }); 1506 | Ok(send_json) 1507 | } 1508 | 1509 | async fn deal_ob_get_cookies(&self,params:&serde_json::Value,_js:&serde_json::Value,echo:&serde_json::Value) -> Result> { 1510 | let domain = get_json_str(params,"domain"); 1511 | if domain == "token" { 1512 | let send_json = serde_json::json!({ 1513 | "status":"ok", 1514 | "retcode":0, 1515 | "data": { 1516 | "cookies":self.token 1517 | }, 1518 | "echo":echo 1519 | }); 1520 | return Ok(send_json); 1521 | } 1522 | return None.ok_or(format!("`{domain}` not support"))?; 1523 | } 1524 | 1525 | async fn deal_onebot_sub(&self,text:&str,js:&serde_json::Value,echo:&serde_json::Value) -> Result> { 1526 | let action = js.get("action").ok_or("action not found")?.as_str().ok_or("action not str")?; 1527 | let def = serde_json::json!({}); 1528 | let params = js.get("params").unwrap_or(&def); 1529 | let send_json; 1530 | log::info!("收到来自onebot的动作:{text}"); 1531 | send_json = match action { 1532 | "send_group_msg" => { 1533 | self.deal_ob_send_group_msg(¶ms,&js,&echo).await? 1534 | }, 1535 | "send_private_msg" => { 1536 | self.deal_ob_send_private_msg(¶ms,&js,&echo).await? 1537 | }, 1538 | "send_msg" => { 1539 | let group_id = get_json_str(params, "group_id"); 1540 | if group_id != "" { 1541 | self.deal_ob_send_group_msg(¶ms,&js,&echo).await? 1542 | }else { 1543 | self.deal_ob_send_private_msg(¶ms,&js,&echo).await? 1544 | } 1545 | }, 1546 | "get_login_info" => { 1547 | self.deal_ob_get_login_info(¶ms,&js,&echo).await? 1548 | }, 1549 | "get_stranger_info" => { 1550 | self.deal_ob_get_stranger_info(¶ms,&js,&echo).await? 1551 | }, 1552 | "get_group_info" => { 1553 | self.deal_ob_get_group_info(¶ms,&js,&echo).await? 1554 | }, 1555 | "get_group_list" => { 1556 | self.deal_ob_get_group_list(¶ms,&js,&echo).await? 1557 | }, 1558 | "get_group_member_info" => { 1559 | self.deal_ob_get_group_member_info(¶ms,&js,&echo).await? 1560 | }, 1561 | "set_group_kick" => { 1562 | self.deal_ob_set_group_kick(¶ms,&js,&echo).await? 1563 | }, 1564 | "delete_msg" => { 1565 | self.deal_ob_delete_msg(¶ms,&js,&echo).await? 1566 | }, 1567 | "set_group_leave" => { 1568 | self.deal_ob_set_group_leave(¶ms,&js,&echo).await? 1569 | } 1570 | "set_group_name" => { 1571 | self.deal_ob_set_group_name(¶ms,&js,&echo).await? 1572 | }, 1573 | "set_group_card" => { 1574 | self.deal_ob_set_group_card(¶ms,&js,&echo).await? 1575 | } 1576 | "get_friend_list" => { 1577 | self.deal_ob_get_friend_list(¶ms,&js,&echo).await? 1578 | }, 1579 | "get_group_member_list" => { 1580 | self.deal_ob_get_group_member_list(¶ms,&js,&echo).await? 1581 | }, 1582 | "get_cookies" => { 1583 | self.deal_ob_get_cookies(¶ms,&js,&echo).await? 1584 | }, 1585 | "can_send_image" => { 1586 | serde_json::json!({ 1587 | "status":"ok", 1588 | "retcode":0, 1589 | "data": {"yes":true}, 1590 | "echo":echo 1591 | }) 1592 | }, 1593 | "can_send_record" => { 1594 | serde_json::json!({ 1595 | "status":"ok", 1596 | "retcode":0, 1597 | "data": {"yes":false}, 1598 | "echo":echo 1599 | }) 1600 | }, 1601 | "get_status" => { 1602 | serde_json::json!({ 1603 | "status":"ok", 1604 | "retcode":0, 1605 | "data": { 1606 | "online":true, 1607 | "good":true 1608 | }, 1609 | "echo":echo 1610 | }) 1611 | }, 1612 | "get_version_info" => { 1613 | serde_json::json!({ 1614 | "status":"ok", 1615 | "retcode":0, 1616 | "data": { 1617 | "app_name":"kook-onebot", 1618 | "app_version":"0.1.0", 1619 | "protocol_version":"v11" 1620 | }, 1621 | "echo":echo 1622 | }) 1623 | }, 1624 | _ => { 1625 | serde_json::json!({ 1626 | "status":"failed", 1627 | "retcode":1404, 1628 | "echo":echo 1629 | }) 1630 | } 1631 | }; 1632 | Ok(send_json) 1633 | } 1634 | // 这个函数处理onebot的api调用 1635 | pub async fn deal_onebot(&self,text:&str) -> (i64,String) { 1636 | let js_ret; 1637 | let http_code:i64; 1638 | let js_rst: Result = serde_json::from_str(&text); 1639 | if let Ok(js) = js_rst { 1640 | let def_str = serde_json::json!(""); 1641 | let echo = js.get("echo").unwrap_or(&def_str); 1642 | let rst = self.deal_onebot_sub(text,&js,echo).await; 1643 | match rst { 1644 | Ok(ret_json) => { 1645 | let code = ret_json.get("retcode").unwrap().as_i64().unwrap(); 1646 | if code == 0 { 1647 | http_code = 200; 1648 | } else { // 1404 API NOT FOUND 1649 | http_code = 404; 1650 | log::error!("ONEBOT动作调用出错:`API NOT FOUND`"); 1651 | } 1652 | js_ret = ret_json; 1653 | }, 1654 | Err(err) => { 1655 | http_code = 200; 1656 | js_ret = serde_json::json!({ 1657 | "status":"failed", 1658 | "retcode":-1, 1659 | "echo":echo 1660 | }); 1661 | log::error!("ONEBOT动作调用出错:{err:?}"); 1662 | }, 1663 | } 1664 | } else { 1665 | // 如果 POST 请求的正文格式不正确,状态码为 400 1666 | http_code = 400; 1667 | js_ret = serde_json::json!({ 1668 | "status":"failed", 1669 | "retcode":1400, 1670 | }); 1671 | log::error!("ONEBOT动作调用出错:`INVALID JSON`"); 1672 | } 1673 | let json_str = js_ret.to_string(); 1674 | log::info!("ONEBOT动作返回:{json_str}"); 1675 | return (http_code,json_str); 1676 | } 1677 | } 1678 | 1679 | 1680 | fn get_json_str(js:&serde_json::Value,key:&str) -> String { 1681 | let key_val = js.get(key); 1682 | if key_val.is_none() { 1683 | return "".to_owned(); 1684 | } 1685 | let val = key_val.unwrap(); 1686 | if val.is_i64() { 1687 | return val.as_i64().unwrap().to_string(); 1688 | } 1689 | if val.is_u64() { 1690 | return val.as_u64().unwrap().to_string(); 1691 | } 1692 | if val.is_string() { 1693 | return val.as_str().unwrap().to_string(); 1694 | } 1695 | return "".to_owned(); 1696 | } 1697 | 1698 | fn get_json_bool(js:&serde_json::Value,key:&str) -> bool { 1699 | if let Some(j) = js.get(key) { 1700 | if j.is_boolean() { 1701 | return j.as_bool().unwrap(); 1702 | } else if j.is_string(){ 1703 | if j.as_str().unwrap() == "true" { 1704 | return true; 1705 | } else { 1706 | return false; 1707 | } 1708 | } 1709 | else { 1710 | return false; 1711 | } 1712 | } else { 1713 | return false; 1714 | } 1715 | } 1716 | 1717 | 1718 | 1719 | #[derive(Serialize, Deserialize, Debug)] 1720 | struct GroupInfo { 1721 | group_id:u64, 1722 | group_name:String, 1723 | member_count:i32, 1724 | max_member_count:i32 1725 | } 1726 | 1727 | 1728 | #[derive(Serialize, Deserialize, Debug)] 1729 | pub struct LoginInfo { 1730 | pub user_id:u64, 1731 | pub nickname:String, 1732 | avatar:String 1733 | } 1734 | 1735 | 1736 | #[derive(Serialize, Deserialize, Debug)] 1737 | struct StrangerInfo { 1738 | user_id:u64, 1739 | nickname:String, 1740 | sex:String, 1741 | age:i32, 1742 | avatar:String 1743 | } 1744 | 1745 | 1746 | #[derive(Serialize, Deserialize, Debug)] 1747 | pub struct FriendInfo { 1748 | user_id:u64, 1749 | nickname:String, 1750 | remark:String, 1751 | avatar:String 1752 | } 1753 | 1754 | 1755 | #[derive(Serialize, Deserialize, Debug,Clone)] 1756 | struct GroupMemberInfo { 1757 | group_id:u64, 1758 | user_id:u64, 1759 | nickname:String, 1760 | card:String, 1761 | sex:String, 1762 | age:i32, 1763 | area:String, 1764 | join_time:i32, 1765 | last_sent_time:i32, 1766 | level:String, 1767 | role:String, 1768 | unfriendly:bool, 1769 | title:String, 1770 | title_expire_time:i32, 1771 | card_changeable:bool, 1772 | avatar:String 1773 | } 1774 | --------------------------------------------------------------------------------