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 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 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
83async 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
203async 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