kovi_plugin_siliconflow/
lib.rs

1use std::{error::Error, sync::Arc};
2use kovi::{async_move, serde_json::{from_value, json, Value}, Message, MsgEvent, PluginBuilder as plugin, RuntimeBot};
3use reqwest::Client;
4use config::Config;
5use response::{ChatCompletion, R1ChatCompletion, UserProFile, V3ChatCompletion};
6
7mod config;
8mod response;
9
10const API_URL: &str = "https://api.siliconflow.cn/v1/chat/completions";
11const USER_PROFILE_URL: &str = "https://api.siliconflow.cn/v1/user/info";
12const PLUGIN_NAME: &str = "kovi-plugin-siliconflow";
13const HELP: &str = ".sc help 帮助\n.sc info config 列出当前配置\n.sc info user 获取用户信息\n.sc update <api_key> 更新api_key\n.sc hint <提示词> 更新提示词";
14
15#[kovi::plugin]
16async fn main() {
17    let bot = plugin::get_runtime_bot();
18    plugin::on_msg(async_move!(e; bot;{
19        if !e.raw_message.starts_with('%') {
20            return;
21        }
22        ask_question_main(e, bot).await;
23    }));
24    plugin::on_admin_msg(async_move!(e;bot;{
25        // 填 Api、查余额
26        let prefix = ".sc";
27        if !e.raw_message.starts_with(prefix){
28            return ;
29        }
30        manager_plugin(e, bot).await
31    }));
32}
33
34async fn ask_question_main(e: Arc<MsgEvent>, bot: Arc<RuntimeBot>){
35    let config_path = bot.get_data_path().join("config.json");
36    let cfg = Arc::new(Config::new(&config_path).expect("Failed to load config"));
37    let raw_msg = e.get_text();
38    match parse_prefix(raw_msg.as_str()) {
39        Ok((prefix, msg)) => {
40            if msg.is_empty(){
41                return ;
42            }
43            if cfg.api_key_is_null() {
44                e.reply("api_key 是空的, 请使用命令: .sc update <api_key> 或手动填写 api_key");
45            }
46            send_poke(&bot, &e).await;
47            handle_message(e, cfg, prefix, msg.as_str()).await;
48        },
49        Err(_) => reply_error(e, "无法解析消息前缀"),
50    }
51}
52
53async fn manager_plugin(e: Arc<MsgEvent>, bot: Arc<RuntimeBot>){
54    let raw_msg: Vec<&str> = e.raw_message.as_str().split_whitespace().collect();
55    let config_path = bot.get_data_path().join("config.json");
56    let cfg = Arc::new(Config::new(&config_path).expect("Failed to load config"));
57    if cfg.api_key_is_null(){
58        e.reply("喵发现你的 api_key 是空的哟, 你可以使用以下命令更新你的 api_key, 否则功能受限哟喵~\n.sc update <api_key>");
59    }
60
61    match raw_msg.as_slice() {
62        [_, "info", "config"] => {
63            e.reply(Message::from(cfg.to_string()));
64        }, 
65        [_, "info", "user"] => {
66            if cfg.api_key_is_null() {
67                reply_error(e, "api_key 为空呢喵~");
68                return ;
69            }
70            let api_key = &cfg.api_key;
71            let user_data = get_user_profile(api_key).await;
72            match user_data {
73                Ok(data) => e.reply(reply_user_profile(data)),
74                Err(err) => {
75                    reply_error(e, err.to_string().as_str())
76                }
77            }
78            
79        },
80        [_, "update", new_api_key] => {
81            let result = cfg.set_api_key(new_api_key.to_string(), &config_path);
82            if let Err(err) = result {
83                reply_error(e, err.to_string().as_str());
84            }
85        },
86        [_, "hint", new_hint] => {
87            let result = cfg.set_api_hint(new_hint.to_string(), &config_path);
88            if let Err(err) = result {
89                reply_error(e, err.to_string().as_str());
90            }
91        }
92        _ => e.reply(HELP),
93    }
94}
95
96async fn get_user_profile(api_key: &str) -> Result<UserProFile, reqwest::Error> {
97    let client = Client::new();
98    let response: UserProFile = client.get(USER_PROFILE_URL)
99    .header("Authorization", format!("Bearer {}", api_key))
100    .send()
101    .await?
102    .json()
103    .await?;
104    println!("{response:#?}");
105    Ok(response)
106}
107fn reply_user_profile(user_data: UserProFile) -> Message{
108    let mut msg = Message::new();
109    let user_data = user_data.data;
110    let text = format!("用户名: {}\n邮箱: {}\n赠送余额: {}\n状态: {}\n总余额: {}",
111    &user_data.name,&user_data.email, &user_data.balance, &user_data.status, &user_data.totalBalance);
112    msg.push_image(&user_data.image);
113    msg.push_text(text);
114    msg
115}
116
117async fn send_poke(bot: &Arc<RuntimeBot>, e: &Arc<MsgEvent>) {
118    let params = if e.is_group() {
119        json!({ "group_id": e.group_id, "user_id": e.user_id })
120    } else {
121        json!({ "user_id": e.user_id })
122    };
123    bot.send_api("send_poke", params);
124}
125
126async fn handle_message(
127    e: Arc<MsgEvent>,
128    cfg: Arc<config::Config>,
129    prefix: u8,
130    msg: &str
131) {
132    let model = match prefix {
133        1 => "deepseek-ai/DeepSeek-R1",
134        0 => "deepseek-ai/DeepSeek-V3",
135        _ => {
136            reply_error(e, "未知的模型");
137            panic!("未知的模型")
138        }
139    };
140    if let Err(err) = process_answer_data(&e, model, msg, cfg).await {
141        reply_error(e, err.to_string().as_str());
142    }
143}
144
145async fn process_answer_data(e: &Arc<MsgEvent> , model: &str, msg: &str, cfg: Arc<config::Config>) -> Result<(), Box<dyn Error>>{
146    let response = send_chat_request(msg, model, cfg.api_key.as_str(), cfg.hint.as_str()).await?;
147    match  model {
148        "deepseek-ai/DeepSeek-R1" => {
149            let r1_json: R1ChatCompletion = from_value(response)?;
150            reply_success(e, r1_json.get_plain_msg()).await;
151            Ok(())
152        },
153        "deepseek-ai/DeepSeek-V3" => {
154            let v3_json: V3ChatCompletion = from_value(response)?;
155            reply_success(e, v3_json.get_plain_msg()).await;
156            Ok(())
157        },
158        _ => Ok(())
159    }
160}
161
162async fn reply_success(e: &Arc<MsgEvent>, msg: &str) {
163    // let msg = format!("{}\n—{}", msg, model);
164    e.reply(Message::from(msg));
165}
166
167fn reply_error(e: Arc<MsgEvent>, msg: &str) {
168    let formatted = format!("[-]{}: {}", PLUGIN_NAME, msg);
169    e.reply(Message::from(formatted));
170}
171
172fn parse_prefix(raw: &str) -> Result<(u8, String), ()> {
173    raw.strip_prefix("%%")
174        .map(|msg| (1, msg.to_string()))
175        .or_else(|| raw.strip_prefix('%').map(|msg| (0, msg.to_string())))
176        .ok_or(())
177}
178
179async fn send_chat_request(
180    message: &str,
181    model: &str,
182    api_key: &str,
183    hint: &str,
184) -> Result<Value, reqwest::Error> {
185    let client = Client::new();
186    let messages = if hint.is_empty() {
187        vec![json!({"role": "user", "content": message})]
188    } else {
189        vec![
190            json!({"role": "system", "content": hint}),
191            json!({"role": "user", "content": message})
192        ]
193    };
194
195    let payload = json!({
196        "messages": messages,
197        "model": model,
198        "stream": false
199    });
200
201    let response = client.post(API_URL)
202        .header("Authorization", format!("Bearer {}", api_key))
203        .json(&payload)
204        .send()
205        .await?
206        .json().await;
207    response
208}