kovi_plugin_siliconflow/
lib.rs

1use std::{collections::HashMap, error::Error, sync::Arc};
2use kovi::{async_move, serde_json::{from_str, json}, Message, MsgEvent, PluginBuilder as plugin, RuntimeBot};
3use reqwest::Client;
4use config::Config;
5use response::{GeneralCompletions, ReasonChatCompletion, RequestResponse, UserProFile};
6use kovi_plugin_expand_napcat::NapCatVec;
7mod config;
8mod response;
9
10const HELP: &str = ".sc help: 帮助
11.sc info config: 列出当前配置
12.sc info user: 获取用户信息
13.sc api_key set <api_key>: 更新api_key
14.sc hint set <提示词>: 更新提示词
15.sc forward set <true|false>: 开启/关闭消息转发
16.sc prefix set <prefix> <model>: 设置触发器和对应模型
17.sc prefix del <prefix>: 删除触发器";
18
19#[kovi::plugin]
20async fn main() {
21    let bot = plugin::get_runtime_bot();
22    plugin::on_msg(async_move!(e; bot;{
23        ask_question_main(e, bot).await;
24    }));
25    plugin::on_admin_msg(async_move!(e;bot;{
26        manager_plugin(e, bot).await
27    }));
28}
29
30// 收发 Ai 回答内容
31async fn ask_question_main(e: Arc<MsgEvent>, bot: Arc<RuntimeBot>){
32  let config_path = bot.get_data_path().join("config.json");
33  let cfg = Config::load(&config_path).map_err(|err|{
34    let msg = format!("加载配置文件出错了, 原因: {}", err.to_string());
35    e.reply_and_quote(msg);
36    return ;
37  }).unwrap();
38  let text = e.borrow_text();
39  match text {
40      Some(pre_match_str) => {
41        let match_prefix = cfg.prefix.keys().filter(|&x|{
42          let x_len = x.chars().count();
43          x == &pre_match_str.chars().take(x_len).collect::<String>()
44        });
45        if match_prefix.clone().count() < 1{
46          return ;
47        }
48        let mut match_prefix_vec: Vec<&String> = match_prefix.clone().collect();
49        match_prefix_vec.sort_by(|&a, &b| b.len().cmp(&a.len()));
50        let prefix = match_prefix_vec[0];
51        let question = e.raw_message.trim_start_matches(prefix);
52        let model = cfg.prefix.get(prefix).unwrap();
53        let uid = &e.self_id.to_string();
54        let nickname = &e.get_sender_nickname();
55        send_poke(&bot, &e).await;
56        let ans = get_ans_from_api(&e, cfg.api_key, model.to_string(), cfg.hint, question.to_string())
57        .await;
58        match ans {
59            Ok(req_result) => {
60              if cfg.forward{
61                let mut nodes = Vec::new();
62                nodes.push_fake_node_from_content(uid, nickname, Message::from(&req_result.answer["message"]));
63                if req_result.reason {
64                  nodes.push_fake_node_from_content(uid, nickname, Message::from(&req_result.answer["reason_message"]));
65                }
66                e.reply(nodes);
67              } else {
68                // 没开启消息转发的话,直接发送回答内容
69                e.reply(Message::from(&req_result.answer["message"]));
70              }
71            },
72            Err(err) => {
73              let msg = format!("Api请求出错了, {}", err.to_string());
74              e.reply_and_quote(msg);
75            }
76        }
77      },
78      None => {}
79  }
80
81}
82
83// 管理配置文件
84async fn manager_plugin(e: Arc<MsgEvent>, bot: Arc<RuntimeBot>){
85    if !e.raw_message.starts_with(".sc"){
86        return ;
87    }
88    let raw_msg: Vec<&str> = e.raw_message.as_str().split_whitespace().collect();
89    let config_path = bot.get_data_path().join("config.json");
90    let cfg = Config::load(&config_path).map_err(|x|{
91      let msg = format!("加载配置文件出错了, 原因: {}", x.to_string());
92      e.reply_and_quote(msg);
93    }).unwrap();
94
95    match raw_msg.as_slice() {
96        [_, "info", "config"] => {
97            e.reply(Message::from(cfg.to_string()));
98        }, 
99        [_, "info", "user"] => {
100            let api_key = &cfg.api_key;
101            let user_data = get_user_profile(api_key).await;
102            match user_data {
103                Ok(data) => e.reply(reply_user_profile(data)),
104                Err(err) => {
105                    e.reply_and_quote(err.to_string());
106                }
107            }
108            
109        },
110        [_, "api_key", "set", new_api_key] => {
111            let result = cfg.set_api_key(new_api_key.to_string(), &config_path);
112            match result {
113                Ok(res) => {
114                  e.reply_and_quote(res);
115                },
116                Err(err) => {
117                  e.reply_and_quote(err.to_string());
118                }
119            }
120        },
121        [_, "hint", "set", new_hint@ ..] => {
122          let new_hint = new_hint.join(" ");
123            let result = cfg.set_api_hint(new_hint, &config_path);
124            match result {
125              Ok(res) => {
126                e.reply_and_quote(res);
127              },
128              Err(err) => {
129                e.reply_and_quote(err.to_string());
130              }
131          }
132        },
133        [_, "forward", "set", action] => {
134          if let Ok(action) = action.parse::<bool>() {
135              let result = cfg.set_forward(action, &config_path);
136              match result{
137                Ok(result) => {
138                  e.reply_and_quote(result);
139                },
140                Err(err) => {
141                  e.reply_and_quote(err.to_string());
142                }
143              }
144          } else {
145            let msg = format!("消息转发开关只有true和false, 你输入了意外的字符, 请重新设置");
146            e.reply_and_quote(msg);
147          }
148        },
149        [_, "prefix", "set", prefix, model] =>{
150          let result = cfg.set_prefix(prefix.to_string(), model.to_string(), &config_path);
151          match result {
152              Ok(result) => e.reply_and_quote(result),
153              Err(err) => e.reply_and_quote(err),
154          }
155        },
156        [_, "prefix", "del", prefix] =>{
157          let result = cfg.del_prefix(prefix.to_string(), &config_path);
158          match result {
159            Ok(result) => e.reply_and_quote(result),
160            Err(err) => e.reply_and_quote(err.to_string()),
161          }
162
163        }
164        _ => {
165          if cfg.api_key.trim().is_empty(){
166            e.reply_and_quote("喵发现你的 api_key 是空的哟, 你可以使用以下命令更新你的 api_key, 否则功能受限哟喵~\n.sc api_key set <api_key>");
167        }
168        e.reply(HELP);
169    }
170  }
171}
172
173async fn get_user_profile(api_key: &str) -> Result<UserProFile, reqwest::Error> {
174    let client = Client::new();
175    let user_profile_url = "https://api.siliconflow.cn/v1/user/info";
176    let response: UserProFile = client.get(user_profile_url)
177    .header("Authorization", format!("Bearer {}", api_key))
178    .send()
179    .await?
180    .json()
181    .await?;
182    Ok(response)
183}
184fn reply_user_profile(user_data: UserProFile) -> Message{
185    let mut msg = Message::new();
186    let user_data = user_data.data;
187    let text = format!("用户名: {}\n邮箱: {}\n赠送余额: {}\n状态: {}\n总余额: {}",
188    &user_data.name,&user_data.email, &user_data.balance, &user_data.status, &user_data.totalBalance);
189    msg.push_image(&user_data.image);
190    msg.push_text(text);
191    msg
192}
193
194async fn send_poke(bot: &Arc<RuntimeBot>, e: &Arc<MsgEvent>) {
195    let params = if e.is_group() {
196        json!({ "group_id": e.group_id, "user_id": e.user_id })
197    } else {
198        json!({ "user_id": e.user_id })
199    };
200    bot.send_api("send_poke", params);
201}
202
203// 从 Api 获取处理过的消息文本
204async fn get_ans_from_api(
205    e: &Arc<MsgEvent>,
206    api_key: String,
207    model: String,
208    hint: String,
209    qestion: String
210) -> Result<RequestResponse, Box<dyn Error>>{
211  let client = Client::new();
212  let api_url = "https://api.siliconflow.cn/v1/chat/completions";
213  let plugin_name= "kovi-plugin-siliconflow";
214  let reasonable = || {
215    let v = &["Qwen/QwQ-32B"];
216    if let Some(_) = &model.find("DeepSeek-R1"){
217      return true;
218    }
219    if v.iter().filter(|&&x| x == &model).count() > 0{
220      return true;
221    }
222    return false;
223  };
224  let messages = if hint.trim().is_empty() {
225      vec![json!({"role": "user", "content": &qestion})]
226  } else {
227      vec![
228          json!({"role": "system", "content": hint}),
229          json!({"role": "user", "content": &qestion})
230      ]
231  };
232
233  let payload = json!({
234      "messages": messages,
235      "model": model,
236      "stream": false
237  });
238
239  let response = client.post(api_url)
240      .header("Authorization", format!("Bearer {}", api_key))
241      .json(&payload)
242      .send()
243      .await?
244      .text().await?;
245  
246  let result;
247  if reasonable() {
248    let response_json: ReasonChatCompletion = from_str(&response).map_err(|err| {
249      let msg = format!("[{}] 响应体解析错误, {}", plugin_name, err.to_string());
250      e.reply_and_quote(msg);
251    }).unwrap();
252    let mut msg_result = HashMap::new();
253    let msg = &response_json.choices.get(0).unwrap().message;
254    msg_result.entry(String::from("message")).or_insert(msg.content.clone());
255    msg_result.entry(String::from("reason_message")).or_insert(msg.reasoning_content.clone());
256    result = RequestResponse::new(msg_result, true);
257  } else {
258    let response_json: GeneralCompletions = from_str(&response).map_err(|err| {
259      let msg = format!("[{}] 响应体解析错误, {}", plugin_name, err.to_string());
260      e.reply_and_quote(msg);
261    }).unwrap();
262    let mut msg_result = HashMap::new();
263    let msg = &response_json.choices.get(0).unwrap().message;
264    msg_result.entry(String::from("message")).or_insert(msg.content.clone());
265    result = RequestResponse::new(msg_result, false);
266  }
267  Ok(result)
268
269}
270