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
30async 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 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
77async 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
197async 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