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 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 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}