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