├── .github
└── workflows
│ └── build.yml
├── .gitignore
├── .idea
├── .gitignore
├── coolq-sdk-rust.iml
├── misc.xml
├── modules.xml
├── vcs.xml
└── workspace.xml
├── .rustfmt.toml
├── Cargo.toml
├── LICENSE
├── README.md
├── cqrs_builder
├── Cargo.toml
└── src
│ ├── gen_app_json.rs
│ └── lib.rs
├── cqrs_macro
├── Cargo.toml
└── src
│ └── lib.rs
├── src
├── api.rs
├── events
│ ├── add_friend_request.rs
│ ├── add_group_request.rs
│ ├── discuss_message.rs
│ ├── friend_add.rs
│ ├── group_admin.rs
│ ├── group_ban.rs
│ ├── group_member_decrease.rs
│ ├── group_member_increase.rs
│ ├── group_message.rs
│ ├── group_upload.rs
│ ├── mod.rs
│ └── private_message.rs
├── iconv.rs
├── lib.rs
└── targets
│ ├── cqcode.rs
│ ├── group.rs
│ ├── message.rs
│ ├── mod.rs
│ └── user.rs
└── tests
└── test_bench.rs
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | name: build
2 |
3 | on: [push]
4 |
5 | jobs:
6 | build:
7 |
8 | runs-on: ubuntu-latest
9 |
10 | steps:
11 | - uses: actions/checkout@v2
12 | - name: Build
13 | run: cargo build --verbose
14 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Generated by Cargo
2 | # will have compiled files and executables
3 | /target/
4 | /.idea/
5 |
6 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
7 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
8 | Cargo.lock
9 |
10 | # These are backup files generated by rustfmt
11 | **/*.rs.bk
12 |
--------------------------------------------------------------------------------
/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /workspace.xml
--------------------------------------------------------------------------------
/.idea/coolq-sdk-rust.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/workspace.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
43 |
44 |
45 |
46 |
47 |
52 |
53 |
54 |
55 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 | 1574750656275
204 |
205 |
206 | 1574750656275
207 |
208 |
209 |
210 |
211 |
212 |
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
224 |
225 |
226 |
227 |
228 |
229 |
230 |
231 |
232 |
233 |
234 |
235 |
236 |
237 |
238 |
239 |
240 |
241 |
242 |
243 |
244 |
245 |
246 |
247 |
248 |
249 |
250 |
251 |
252 |
253 |
254 |
255 |
256 |
257 |
258 |
259 |
260 |
261 |
262 |
263 |
264 |
265 |
266 |
267 |
268 |
269 |
270 |
271 |
272 |
273 |
274 |
275 |
276 |
277 |
278 |
279 |
280 |
281 |
282 |
283 |
284 |
285 |
286 |
287 | 1581857403250
288 |
289 |
290 |
291 | 1581857403250
292 |
293 |
294 | 1582009375178
295 |
296 |
297 |
298 | 1582009375178
299 |
300 |
301 | 1582009651691
302 |
303 |
304 |
305 | 1582009651691
306 |
307 |
308 | 1582016620786
309 |
310 |
311 |
312 | 1582016620786
313 |
314 |
315 | 1582016700366
316 |
317 |
318 |
319 | 1582016700366
320 |
321 |
322 | 1582016796977
323 |
324 |
325 |
326 | 1582016796977
327 |
328 |
329 | 1582016838397
330 |
331 |
332 |
333 | 1582016838397
334 |
335 |
336 | 1582016860748
337 |
338 |
339 |
340 | 1582016860749
341 |
342 |
343 | 1582017643576
344 |
345 |
346 |
347 | 1582017643576
348 |
349 |
350 | 1582019333487
351 |
352 |
353 |
354 | 1582019333487
355 |
356 |
357 | 1582020441390
358 |
359 |
360 |
361 | 1582020441390
362 |
363 |
364 | 1582022002478
365 |
366 |
367 |
368 | 1582022002478
369 |
370 |
371 | 1582092946918
372 |
373 |
374 |
375 | 1582092946918
376 |
377 |
378 | 1582093313038
379 |
380 |
381 |
382 | 1582093313038
383 |
384 |
385 | 1582093322142
386 |
387 |
388 |
389 | 1582093322142
390 |
391 |
392 | 1582093858510
393 |
394 |
395 |
396 | 1582093858510
397 |
398 |
399 | 1582094109844
400 |
401 |
402 |
403 | 1582094109844
404 |
405 |
406 | 1582094186252
407 |
408 |
409 |
410 | 1582094186252
411 |
412 |
413 | 1582095696485
414 |
415 |
416 |
417 | 1582095696485
418 |
419 |
420 | 1582095758900
421 |
422 |
423 |
424 | 1582095758900
425 |
426 |
427 | 1582446307330
428 |
429 |
430 |
431 | 1582446307330
432 |
433 |
434 | 1582446393309
435 |
436 |
437 |
438 | 1582446393309
439 |
440 |
441 | 1582525434916
442 |
443 |
444 |
445 | 1582525434916
446 |
447 |
448 | 1582525458764
449 |
450 |
451 |
452 | 1582525458764
453 |
454 |
455 | 1582528310161
456 |
457 |
458 |
459 | 1582528310161
460 |
461 |
462 | 1582876570408
463 |
464 |
465 |
466 | 1582876570408
467 |
468 |
469 | 1582970725250
470 |
471 |
472 |
473 | 1582970725250
474 |
475 |
476 | 1582970815376
477 |
478 |
479 |
480 | 1582970815376
481 |
482 |
483 | 1582970927345
484 |
485 |
486 |
487 | 1582970927345
488 |
489 |
490 | 1582984840181
491 |
492 |
493 |
494 | 1582984840181
495 |
496 |
497 | 1583138632067
498 |
499 |
500 |
501 | 1583138632067
502 |
503 |
504 | 1583138750044
505 |
506 |
507 |
508 | 1583138750044
509 |
510 |
511 | 1583146143302
512 |
513 |
514 |
515 | 1583146143302
516 |
517 |
518 | 1583146186577
519 |
520 |
521 |
522 | 1583146186578
523 |
524 |
525 | 1583237024003
526 |
527 |
528 |
529 | 1583237024003
530 |
531 |
532 | 1583547634824
533 |
534 |
535 |
536 | 1583547634824
537 |
538 |
539 | 1583547643297
540 |
541 |
542 |
543 | 1583547643297
544 |
545 |
546 | 1583547704781
547 |
548 |
549 |
550 | 1583547704781
551 |
552 |
553 | 1583548058259
554 |
555 |
556 |
557 | 1583548058259
558 |
559 |
560 | 1583549393913
561 |
562 |
563 |
564 | 1583549393913
565 |
566 |
567 | 1583550176992
568 |
569 |
570 |
571 | 1583550176992
572 |
573 |
574 | 1583550744301
575 |
576 |
577 |
578 | 1583550744301
579 |
580 |
581 | 1583555271808
582 |
583 |
584 |
585 | 1583555271808
586 |
587 |
588 | 1583559014944
589 |
590 |
591 |
592 | 1583559014944
593 |
594 |
595 | 1583566326712
596 |
597 |
598 |
599 | 1583566326712
600 |
601 |
602 | 1583577736538
603 |
604 |
605 |
606 | 1583577736538
607 |
608 |
609 | 1583579374036
610 |
611 |
612 |
613 | 1583579374036
614 |
615 |
616 | 1583581038501
617 |
618 |
619 |
620 | 1583581038501
621 |
622 |
623 | 1583648309987
624 |
625 |
626 |
627 | 1583648309987
628 |
629 |
630 |
631 |
632 |
633 |
634 |
635 |
636 |
637 |
669 |
670 |
671 |
672 |
673 |
674 |
675 |
676 |
677 |
678 |
679 |
680 |
681 |
682 |
683 |
684 |
685 |
686 |
687 |
688 |
689 |
690 |
691 |
692 |
693 |
694 |
695 |
696 |
697 |
698 |
699 |
700 |
701 |
702 |
703 |
704 |
705 |
706 |
707 |
708 |
709 |
710 |
711 |
712 |
713 |
714 |
715 |
716 |
717 |
718 |
719 |
720 |
721 |
722 |
723 |
724 |
725 |
726 |
727 |
728 |
729 |
730 |
731 |
732 |
733 |
734 |
735 |
736 |
737 |
738 |
739 |
740 |
741 |
742 |
743 |
744 |
745 |
746 |
747 |
748 |
749 |
750 |
751 |
752 |
753 |
754 |
755 |
756 |
757 |
758 |
759 |
760 |
761 |
762 |
763 |
764 |
765 |
766 |
767 |
768 |
769 |
770 |
771 |
772 |
773 |
774 |
775 |
776 |
777 |
778 |
779 |
780 |
781 |
782 |
783 |
784 |
785 |
786 |
787 |
788 |
789 |
790 |
791 |
792 |
793 |
794 |
795 |
796 |
797 |
798 |
799 |
800 |
801 |
802 |
803 |
804 |
805 |
806 |
807 |
808 |
809 |
810 |
811 |
812 |
813 |
814 |
815 |
816 |
817 |
818 |
819 |
820 |
821 |
822 |
823 |
824 |
825 |
826 |
827 |
828 |
829 |
830 |
831 |
832 |
833 |
834 |
835 |
836 |
837 |
--------------------------------------------------------------------------------
/.rustfmt.toml:
--------------------------------------------------------------------------------
1 | brace_style = "PreferSameLine"
2 | control_brace_style = "AlwaysSameLine"
3 | fn_args_layout = "Compressed"
4 | format_code_in_doc_comments = true
5 | format_macro_matchers = true
6 | match_block_trailing_comma = true
7 | merge_imports = true
--------------------------------------------------------------------------------
/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "coolq-sdk-rust"
3 | version = "0.1.21"
4 | authors = ["soeur "]
5 | edition = "2018"
6 | license = "MIT"
7 | description = "A sdk for coolq"
8 | documentation = "https://docs.rs/coolq-sdk-rust/"
9 | repository = "https://github.com/juzi5201314/coolq-sdk-rust"
10 | keywords = ["coolq", "qq", "sdk", "cqp"]
11 | readme = "README.md"
12 |
13 | [package.metadata.docs.rs]
14 | all-features = true
15 | rustdoc-args = ["--cfg", "docsrs"]
16 |
17 | [dependencies]
18 | lazy_static = "1.2.0"
19 | base64 = "0.11.0"
20 | byteorder = "1.3.2"
21 | regex = "1.3.1"
22 | tokio = { version = "0.2.13", default-features = false, features = ["rt-core", "fs"], optional = true }
23 | libloading = "0.5"
24 | once_cell = "1.3.1"
25 | md-5 = { version = "0.8.0", optional = true }
26 | hex = { version = "0.4.1", optional = true }
27 | cqrs_macro = { version = "0.1", path = "cqrs_macro" }
28 | libc = "0.2.67"
29 | futures = { version = "0.3.4", optional = true }
30 |
31 | [features]
32 | default = []
33 | enhanced-cqcode = ["tokio", "hex", "md-5"]
34 | async-listener = ["cqrs_macro/async-listener", "tokio", "futures"]
35 | tokio-threaded = ["async-listener", "tokio/rt-threaded"]
36 |
37 | [workspace]
38 | members = ["cqrs_macro", "cqrs_builder"]
39 |
40 | [profile.release]
41 | opt-level = 3
42 | lto = true
43 | debug = false
44 | codegen-units = 1
45 | panic = "abort"
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 orange soeur
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # coolq-sdk-rust
2 |
3 | 
4 | 
5 | 
6 | 
7 | 
8 | 
9 | 
10 |
11 | # 酷q已经停止更新
12 |
13 | [酷q](http://cqp.cc)的一个sdk。
14 |
15 | # Build
16 | 工具链: `i686-pc-windows-msvc`
17 | ```bash
18 | cargo build
19 | ```
20 |
21 | # Test
22 | ```bash
23 | cargo test
24 | ```
25 |
26 | # Documentation
27 | ### online
28 | [Documentation](https://docs.rs/coolq-sdk-rust/) by docs.rs
29 | ### local
30 | ```bash
31 | cargo doc --no-deps
32 | ```
33 |
34 | # Book
35 | [gitbook.com](https://book-cqrs.gugugu.dev/)
36 |
--------------------------------------------------------------------------------
/cqrs_builder/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "cqrs_builder"
3 | version = "0.1.1"
4 | authors = ["soeur "]
5 | edition = "2018"
6 | license = "MIT"
7 | description = "Crate for coolq-rust-sdk"
8 |
9 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
10 |
11 | [dependencies]
12 | serde = { version = "1.0.104", features = ["derive"] }
13 | serde_json = "1.0.48"
14 |
15 | [features]
16 | full-priority = []
--------------------------------------------------------------------------------
/cqrs_builder/src/gen_app_json.rs:
--------------------------------------------------------------------------------
1 | //! 在编译时生成app.json
2 | //!
3 | //! 默认情况下,事件回调函数全部为中优先级
4 | //!
5 | //! 若要启用全部优先级,请打开 full-priority feature:
6 | //!
7 | //! Cargo.toml
8 | //! ```toml
9 | //! [build-dependencies]
10 | //! cqrs_builder = { version = "0.1", features = ["full-priority"] } # 在dependencies中打开feature,才会生成对应的函数
11 | //! ```
12 | //!
13 | //! # Examples
14 | //! ```should_panic
15 | //! // build.rs
16 | //! fn main() {
17 | //! cqrs_builder::AppJson::new("dev.gugugu.example")
18 | //! .name("rust-sdk-example".to_owned())
19 | //! .version("0.0.1".to_owned())
20 | //! .version_id(1)
21 | //! .author("soeur ".to_owned())
22 | //! .description("rust sdk example.".to_owned())
23 | //! .finish()
24 | //! }
25 | //! ```
26 | //!
27 | //! ## 不使用sdk的事件处理,自定义处理函数。
28 | //! ```should_panic
29 | //! // build.rs
30 | //! fn main() {
31 | //! cqrs_builder::AppJson::new("dev.gugugu.example")
32 | //! // .name .version...
33 | //! .no_default_event()
34 | //! .add_event(1003, "插件启用", 30000, "cq_on_plugin_enable")
35 | //! .remove_event(1003, 30000)
36 | //! .finish()
37 | //! }
38 | //! ```
39 | //!
40 | //! ## 不使用sdk默认生成的全部auth,根据需要自己生成
41 | //! ```should_panic
42 | //! // build.rs
43 | //! fn main() {
44 | //! cqrs_builder::AppJson::new("dev.gugugu.example")
45 | //! // .name .version...
46 | //! .no_default_auth()
47 | //! .add_auth(20)
48 | //! .add_auth(30)
49 | //! .remove_auth(20)
50 | //! .finish()
51 | //! }
52 | //! ```
53 |
54 | use std::{
55 | env,
56 | fs::File,
57 | io::Write,
58 | path::Path,
59 | sync::atomic::{AtomicUsize, Ordering},
60 | };
61 |
62 | use serde::Serialize;
63 | use serde_json::{json, Value};
64 |
65 | static EVENT_ID: AtomicUsize = AtomicUsize::new(1);
66 |
67 | macro_rules! gen_setters {
68 | ($struct: ident, $($name: ident: $type: ty),*) => {
69 | impl $struct {
70 | $(
71 | pub fn $name(&mut self, $name: $type) -> &mut Self {
72 | self.$name = $name;
73 | self
74 | }
75 | )*
76 | }
77 | };
78 | }
79 |
80 | macro_rules! gen_event_json {
81 | ($type: expr, $name: expr, $func_name: expr, $priority: expr, $priority_name: expr) => {
82 | json!({
83 | "id": EVENT_ID.fetch_add(1, Ordering::SeqCst),
84 | "type": $type,
85 | "name": format!("{}{}", $name, $priority_name).to_string(),
86 | "priority": $priority,
87 | "function": format!("{}{}", $func_name, $priority_name).to_string()
88 | })
89 | };
90 | }
91 |
92 | macro_rules! default_events {
93 | ($({type: $type: expr, name: $name: expr, function: $func_name: expr}),*) => {
94 | vec![
95 | // 特殊处理这4个事件,因为我认为这4个事件没必要分优先级,而且也不应该进行拦截
96 | gen_event_json!(1001, "酷Q启动", "on_start", 10000, ""),
97 | gen_event_json!(1002, "酷Q退出", "on_exit", 10000, ""),
98 | gen_event_json!(1003, "插件启用", "on_enable", 10000, ""),
99 | gen_event_json!(1004, "插件停用", "on_disable", 10000, ""),
100 | $(
101 | #[cfg(feature = "full-priority")]
102 | gen_event_json!($type, $name, $func_name, 10000, "_highest"),
103 | #[cfg(feature = "full-priority")]
104 | gen_event_json!($type, $name, $func_name, 20000, "_high"),
105 | #[cfg(feature = "full-priority")]
106 | gen_event_json!($type, $name, $func_name, 30000, "_medium"),
107 | #[cfg(feature = "full-priority")]
108 | gen_event_json!($type, $name, $func_name, 40000, "_low"),
109 | #[cfg(not(feature = "full-priority"))]
110 | gen_event_json!($type, $name, $func_name, 30000, "_medium")
111 | ),*
112 | ]
113 | };
114 | }
115 |
116 | #[derive(Serialize)]
117 | pub struct AppJson {
118 | appid: String,
119 | ret: usize,
120 | apiver: usize,
121 | name: String,
122 | version: String,
123 | version_id: usize,
124 | author: String,
125 | description: String,
126 | auth: Vec,
127 | event: Vec,
128 | menu: Vec
129 | }
130 |
131 | impl AppJson {
132 | pub fn new(appid: &str) -> AppJson {
133 | let mut aj = AppJson::default();
134 | aj.appid = appid.to_owned();
135 | aj
136 | }
137 |
138 | pub fn remove_auth(&mut self, auth: usize) -> &mut Self {
139 | self.auth.remove(
140 | self.auth
141 | .iter()
142 | .position(|auth| auth == auth)
143 | .expect(format!("auth.{} not found", auth).as_ref()),
144 | );
145 | self
146 | }
147 |
148 | pub fn no_default_auth(&mut self) -> &mut Self {
149 | self.auth.clear();
150 | self
151 | }
152 |
153 | pub fn add_auth(&mut self, auth: usize) -> &mut Self {
154 | self.auth.push(auth);
155 | self
156 | }
157 |
158 | pub fn add_menu(&mut self, name: &str, func_name: &str) -> &mut Self {
159 | self.menu.push(json! ({
160 | "name": name,
161 | "function": func_name
162 | }));
163 | self
164 | }
165 |
166 | /// 事件类型,名字,优先度,函数名字。具体查看[酷q文档](https://docs.cqp.im/dev/v9/app.json/event/)
167 | pub fn add_event(
168 | &mut self, _type: usize, name: &str, priority: usize, func_name: &str,
169 | ) -> &mut Self {
170 | self.event.push(json!({
171 | "id": EVENT_ID.fetch_add(1, Ordering::SeqCst),
172 | "type": _type,
173 | "name": name.to_string(),
174 | "priority": priority,
175 | "function": func_name.to_string()
176 | }));
177 | self
178 | }
179 |
180 | pub fn no_default_event(&mut self) -> &mut Self {
181 | self.event.clear();
182 | self
183 | }
184 |
185 | /// 删除指定类型,优先度的事件。
186 | /// 注意: 若删除事件,sdk里对应的事件回调将不会被执行。
187 | pub fn remove_event(&mut self, _type: usize, priority: usize) -> &mut Self {
188 | self.event.remove(
189 | self.event
190 | .iter()
191 | .position(|e| {
192 | if let Value::Object(v) = e {
193 | v.get("type").unwrap() == _type && v.get("priority").unwrap() == priority
194 | } else {
195 | false
196 | }
197 | })
198 | .unwrap(),
199 | );
200 | self
201 | }
202 |
203 | pub fn finish(&mut self) {
204 | let out_dir = env::var("OUT_DIR").unwrap();
205 | let app_json = Path::new(&out_dir)
206 | .parent()
207 | .unwrap()
208 | .parent()
209 | .unwrap()
210 | .parent()
211 | .unwrap()
212 | .join("app.json");
213 | File::create(app_json)
214 | .unwrap()
215 | .write_all(serde_json::to_vec_pretty(self).unwrap().as_slice())
216 | .unwrap();
217 | File::create(Path::new(&out_dir).join("appid"))
218 | .unwrap()
219 | .write_all(self.appid.as_bytes())
220 | .unwrap();
221 | }
222 | }
223 |
224 | gen_setters!(
225 | AppJson,
226 | ret: usize,
227 | apiver: usize,
228 | name: String,
229 | version: String,
230 | version_id: usize,
231 | author: String,
232 | description: String
233 | );
234 |
235 | impl Default for AppJson {
236 | fn default() -> Self {
237 | AppJson {
238 | appid: "".to_owned(),
239 | ret: 1,
240 | apiver: 9,
241 | name: String::from("example app"),
242 | version: String::from("0.0.1"),
243 | version_id: 1,
244 | author: String::from("hao are you?"),
245 | description: String::from("rust sdk example"),
246 | event: default_events![
247 | {
248 | type: 21,
249 | name: "私聊消息",
250 | function: "on_private_msg"
251 | },
252 | {
253 | type: 2,
254 | name: "群消息",
255 | function: "on_group_msg"
256 | },
257 | {
258 | type: 4,
259 | name: "讨论组消息",
260 | function: "on_discuss_msg"
261 |
262 | },
263 | {
264 | type: 11,
265 | name: "群文件上传",
266 | function: "on_group_upload"
267 | },
268 | {
269 | type: 101,
270 | name: "群管理员变动",
271 | function: "on_group_admin"
272 | },
273 | {
274 | type: 102,
275 | name: "群成员减少",
276 | function: "on_group_member_decrease"
277 | },
278 | {
279 | type: 103,
280 | name: "群成员增加",
281 | function: "on_group_member_increase"
282 | },
283 | {
284 | type: 104,
285 | name: "群禁言",
286 | function: "on_group_ban"
287 | },
288 | {
289 | type: 201,
290 | name: "好友添加",
291 | function: "on_friend_add"
292 | },
293 | {
294 | type: 301,
295 | name: "加好友请求",
296 | function: "on_add_friend_request"
297 | },
298 | {
299 | type: 302,
300 | name: "加群请求/邀请",
301 | function: "on_add_group_request"
302 | }
303 | ],
304 | auth: vec![
305 | 20, 30, 101, 103, 106, 110, 120, 121, 122, 123, 124, 125, 126, 127, 128, 130, 131,
306 | 132, 140, 150, 151, 160, 161, 162, 180,
307 | ],
308 | menu: Vec::new()
309 | }
310 | }
311 | }
312 |
--------------------------------------------------------------------------------
/cqrs_builder/src/lib.rs:
--------------------------------------------------------------------------------
1 | pub use gen_app_json::AppJson;
2 |
3 | mod gen_app_json;
--------------------------------------------------------------------------------
/cqrs_macro/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "cqrs_macro"
3 | version = "0.1.1"
4 | authors = ["juzi5201314 <1034236490@qq.com>"]
5 | edition = "2018"
6 | license = "MIT"
7 | description = "Crate for coolq-rust-sdk"
8 |
9 | [lib]
10 | proc-macro = true
11 |
12 | [dependencies]
13 | quote = "1.0.2"
14 | syn = { version = "1.0.14", features = ["full"] }
15 | darling = "0.10.2"
16 | proc-macro2 = "1.0.8"
17 |
18 | [features]
19 | async-listener = []
--------------------------------------------------------------------------------
/cqrs_macro/src/lib.rs:
--------------------------------------------------------------------------------
1 | #![allow(unused_variables)]
2 |
3 | extern crate proc_macro;
4 |
5 | use darling::FromMeta;
6 | use proc_macro2::TokenStream;
7 | use syn::{FnArg, ReturnType};
8 |
9 | use quote::quote;
10 | use std::borrow::Borrow;
11 |
12 | macro_rules! error {
13 | ($tokens: expr, $message: expr) => {
14 | return syn::Error::new_spanned($tokens, $message)
15 | .to_compile_error()
16 | .into();
17 | };
18 | }
19 |
20 | #[cfg(not(test))]
21 | #[proc_macro_attribute]
22 | pub fn main(_: proc_macro::TokenStream, item: proc_macro::TokenStream) -> proc_macro::TokenStream {
23 | let func = syn::parse_macro_input!(item as syn::ItemFn);
24 | let func_name = &func.sig.ident;
25 | let attrs = &func.attrs;
26 |
27 | if let ReturnType::Type(_, _) = func.sig.output {
28 | error!(func.sig.output, "should return '()'.")
29 | }
30 |
31 | let call = if func.sig.asyncness.is_some() {
32 | if cfg!(not(feature = "async-listener")) {
33 | error!(&func.sig.asyncness, "No 'async-listener' feature support.")
34 | }
35 | quote! {
36 | coolq_sdk_rust::ASYNC_RUNTIME.spawn(#func_name());
37 | }
38 | } else {
39 | quote! {
40 | #func_name();
41 | }
42 | };
43 |
44 | (quote! {
45 | #[export_name = "AppInfo"]
46 | pub extern "stdcall" fn app_info() -> *const ::std::os::raw::c_char {
47 | coolq_sdk_rust::api::Convert::from(format!("{},{}", coolq_sdk_rust::APIVER, include_str!(concat!(env!("OUT_DIR"), "/appid")))).into()
48 | }
49 |
50 | #[no_mangle]
51 | pub extern "stdcall" fn on_enable() -> i32 {
52 | #(#attrs)*
53 | #[inline]
54 | #func
55 | #call
56 | 0
57 | }
58 | }).into()
59 | }
60 |
61 | #[proc_macro_attribute]
62 | pub fn block_on(
63 | _: proc_macro::TokenStream, item: proc_macro::TokenStream,
64 | ) -> proc_macro::TokenStream {
65 | item
66 | }
67 |
68 | #[derive(Debug, FromMeta)]
69 | struct MacroArgs {
70 | //event: String,
71 | #[darling(default)]
72 | priority: Option,
73 | }
74 |
75 | #[proc_macro_attribute]
76 | pub fn listener(
77 | attr: proc_macro::TokenStream, item: proc_macro::TokenStream,
78 | ) -> proc_macro::TokenStream {
79 | let args = attr.clone();
80 | let args = MacroArgs::from_list(&syn::parse_macro_input!(args as syn::AttributeArgs)).unwrap();
81 | let func = syn::parse_macro_input!(item as syn::ItemFn);
82 | let func_name = &func.sig.ident;
83 | let attrs = &func.attrs;
84 |
85 | let find_event_name = || -> Option {
86 | if let FnArg::Typed(t) = &func.sig.inputs.first()? {
87 | if let syn::Type::Path(tr) = t.ty.borrow() {
88 | return Some((&tr.path.segments.first()?.ident).to_string());
89 | }
90 | }
91 | None
92 | };
93 | let event_name = match find_event_name() {
94 | Some(some) => some,
95 | None => {
96 | error!(
97 | &func.sig.inputs.first(),
98 | r#"The first parameter of the function must be "event: [AnyEvent]"."#
99 | )
100 | }
101 | };
102 |
103 | if let Some(extern_func_info) = get_event_func(event_name.as_ref()) {
104 | let event = event_name.parse::().unwrap();
105 | let extern_func_name = if let Some(priority) = args.priority {
106 | let prioritys = vec!["highest", "high", "medium", "low"];
107 | if !prioritys.contains(&priority.as_ref()) {
108 | error!(
109 | TokenStream::from(attr),
110 | format!("Priority can only be {}.", prioritys.join(","))
111 | )
112 | }
113 | format!("{}_{}", extern_func_info.0, priority)
114 | } else {
115 | format!("{}_medium", extern_func_info.0)
116 | }
117 | .parse::()
118 | .unwrap();
119 | let args_name_t = extern_func_info.1.parse::().unwrap();
120 | let args_name = extern_func_info.2.parse::().unwrap();
121 | let result_type = extern_func_info.3.parse::().unwrap();
122 |
123 | let call = if func.sig.asyncness.is_some() {
124 | if cfg!(not(feature = "async-listener")) {
125 | error!(&func.sig.asyncness, "No 'async-listener' feature support.")
126 | }
127 | if attrs
128 | .iter()
129 | .find(|attr| {
130 | attr.path
131 | .segments
132 | .iter()
133 | .find(|ps| ps.ident.to_string() == "block_on")
134 | .is_some()
135 | })
136 | .is_some()
137 | {
138 | quote! {
139 | coolq_sdk_rust::api::Convert::from(coolq_sdk_rust::block_on(#func_name(coolq_sdk_rust::events::#event::new(#args_name)))).into()
140 | }
141 | } else {
142 | quote! {
143 | coolq_sdk_rust::ASYNC_RUNTIME.spawn(#func_name(coolq_sdk_rust::events::#event::new(#args_name)));
144 | 0
145 | }
146 | }
147 | } else {
148 | quote! {
149 | coolq_sdk_rust::api::Convert::from(#func_name(coolq_sdk_rust::events::#event::new(#args_name))).into()
150 | }
151 | };
152 |
153 | (quote! {
154 | #[no_mangle]
155 | pub extern "stdcall" fn #extern_func_name(#args_name_t) -> #result_type {
156 | #(#attrs)*
157 | #[inline]
158 | #func
159 | #call
160 | }
161 | })
162 | .into()
163 | } else {
164 | error!(&func.sig.inputs.first(), "Cannot find this event.")
165 | }
166 | }
167 |
168 | macro_rules! gen_get_event_func {
169 | ($(($event: ident, $func_name: ident; $($arg: ident: $t: ty),* => $result_t: ty)),*) => {
170 | fn get_event_func(event: &str) -> Option<(String, String, String, String)> {
171 | match event {
172 | $(stringify!($event) => Some((
173 | stringify!($func_name).to_string(),
174 | stringify!($($arg: $t),*).to_string(),
175 | stringify!($($arg),*).to_string(),
176 | stringify!($result_t).to_string()
177 | ))),*,
178 | _ => None,
179 | }
180 | }
181 | }
182 | }
183 |
184 | gen_get_event_func!(
185 | (StartEvent, on_start;
186 | => i32),
187 | (ExitEvent, on_exit;
188 | => i32),
189 | (DisableEvent, on_disable;
190 | => i32),
191 | (PrivateMessageEvent, on_private_msg;
192 | sub_type: i32,
193 | msg_id: i32,
194 | user_id: i64,
195 | msg: *const ::std::os::raw::c_char,
196 | font: i32
197 | => i32),
198 | (GroupMessageEvent, on_group_msg;
199 | sub_type: i32,
200 | msg_id: i32,
201 | group_id: i64,
202 | user_id: i64,
203 | anonymous_flag: *const ::std::os::raw::c_char,
204 | msg: *const ::std::os::raw::c_char,
205 | font: i32
206 | => i32),
207 | (DiscussMessageEvent, on_discuss_msg;
208 | sub_type: i32,
209 | msg_id: i32,
210 | discuss_id: i64,
211 | user_id: i64,
212 | msg: *const ::std::os::raw::c_char,
213 | font: i32
214 | => i32),
215 | (GroupUploadEvent, on_group_upload;
216 | sub_type: i32,
217 | send_time: i32,
218 | group_id: i64,
219 | user_id: i64,
220 | file: *const ::std::os::raw::c_char
221 | => i32),
222 | (GroupAdminEvent, on_group_admin;
223 | sub_type: i32,
224 | send_time: i32,
225 | group_id: i64,
226 | user_id: i64
227 | => i32),
228 | (GroupMemberDecreaseEvent, on_group_member_decrease;
229 | sub_type: i32,
230 | send_time: i32,
231 | group_id: i64,
232 | operate_user_id: i64,
233 | being_operate_user_id: i64
234 | => i32),
235 | (GroupMemberIncreaseEvent, on_group_member_increase;
236 | sub_type: i32,
237 | send_time: i32,
238 | group_id: i64,
239 | operate_user_id: i64,
240 | being_operate_user_id: i64
241 | => i32),
242 | (GroupBanEvent, on_group_ban;
243 | sub_type: i32,
244 | send_time: i32,
245 | group_id: i64,
246 | operate_user_id: i64,
247 | being_operate_user_id: i64,
248 | time: i64
249 | => i32),
250 | (FriendAddEvent, on_friend_add;
251 | sub_type: i32,
252 | send_time: i32,
253 | user_id: i64
254 | => i32),
255 | (AddFriendRequestEvent, on_add_friend_request;
256 | sub_type: i32,
257 | send_time: i32,
258 | user_id: i64,
259 | msg: *const ::std::os::raw::c_char,
260 | flag: *const ::std::os::raw::c_char
261 | => i32),
262 | (AddGroupRequestEvent, on_add_group_request;
263 | sub_type: i32,
264 | send_time: i32,
265 | group_id: i64,
266 | user_id: i64,
267 | msg: *const ::std::os::raw::c_char,
268 | flag: *const ::std::os::raw::c_char
269 | => i32)
270 | );
271 |
--------------------------------------------------------------------------------
/src/api.rs:
--------------------------------------------------------------------------------
1 | //! # 酷q相关api
2 | //! 在运行时调用CQP.dll
3 |
4 | use std::{
5 | convert::{TryFrom, TryInto},
6 | io::Error as IoError,
7 | os::raw::c_char,
8 | ptr::null,
9 | };
10 | use std::fmt::Formatter;
11 |
12 | use once_cell::sync::OnceCell;
13 |
14 | use crate::targets::{
15 | File,
16 | group::{Group, GroupMember},
17 | message::MessageSegment,
18 | read_multi_object,
19 | user::{FriendInfo, User}
20 | };
21 |
22 | static AUTH_CODE: OnceCell = OnceCell::new();
23 |
24 | macro_rules! gb18030 {
25 | ($str:expr) => {{
26 | use crate::iconv::IconvEncodable;
27 | ::std::ffi::CString::new($str.as_bytes().encode_with_encoding("GB18030").unwrap())
28 | .unwrap()
29 | .into_raw()
30 | }};
31 | }
32 |
33 | #[doc(hidden)]
34 | #[macro_export]
35 | macro_rules! utf8 {
36 | ($c_char:expr) => {
37 | unsafe {
38 | use crate::iconv::IconvDecodable;
39 | ::std::ffi::CStr::from_ptr($c_char)
40 | .to_bytes()
41 | .decode_with_encoding("GB18030")
42 | .unwrap()
43 | }
44 | };
45 | }
46 |
47 | macro_rules! try_convert_to {
48 | ($from:ty, $to:ty, $err:ty, $convert:expr) => {
49 | impl TryFrom> for $to {
50 | type Error = $err;
51 |
52 | fn try_from(value: Convert<$from>) -> std::result::Result {
53 | $convert(value)
54 | }
55 | }
56 | };
57 | }
58 |
59 | macro_rules! convert_to {
60 | ($from:ty, $to:ty, $convert:expr) => {
61 | impl From> for $to {
62 | fn from(value: Convert<$from>) -> Self {
63 | $convert(value.0)
64 | }
65 | }
66 | };
67 |
68 | ($t:ty) => {
69 | convert_to!($t, $t, |value| value);
70 | };
71 | }
72 |
73 | macro_rules! convert_from {
74 | ($from:ty, $to:ty, $convert:expr) => {
75 | impl From<$from> for Convert<$to> {
76 | fn from(value: $from) -> Self {
77 | Convert($convert(value))
78 | }
79 | }
80 | };
81 |
82 | ($t:ty) => {
83 | convert_from!($t, $t, |value| value);
84 | };
85 | }
86 |
87 | macro_rules! gen_api_func {
88 | ($($(#[$doc: meta])* ($cq_func: ident, $func: ident; $($arg: ident: $t: ty),* => $result_t: ty)),*) => {
89 | $(gen_api_func!($(#[$doc])* $cq_func, $func; $($arg: $t),* => $result_t);)*
90 | fn init_api_funcs(lib: libloading::Library) {
91 | #[allow(unused_must_use)] unsafe {
92 | $($cq_func.set(*lib.get(stringify!($cq_func).as_bytes()).unwrap());)*
93 | }
94 | }
95 | };
96 |
97 | ($(#[$doc: meta])* $cq_func: ident, $func: ident; $($arg: ident: $t: ty),* => $result_t: ty) => {
98 | static $cq_func: OnceCell $result_t> = OnceCell::new();
99 |
100 | $(#[$doc])*
101 | pub fn $func($($arg: impl Into>),*) -> Result> {
102 | unsafe {
103 | let lib = libloading::Library::new("CQP.dll").unwrap();
104 | Result::r_from(lib.get:: $result_t>(stringify!($cq_func).as_bytes()).unwrap()(AUTH_CODE.get().expect("auth code not found.").clone(), $($arg.into().into()),*))
105 | }
106 | //Result::r_from(($cq_func.get().expect("CQP.dll not init."))(AUTH_CODE.get().expect("auth code not found.").clone(), $($arg.into().into()),*).into())
107 | }
108 | };
109 | }
110 |
111 | gen_api_func!(
112 | /// 发送私聊消息
113 | ///
114 | /// # Examples
115 | /// ```should_panic
116 | /// use coolq_sdk_rust::api::send_private_msg;
117 | ///
118 | /// let msg_id: i32 = send_private_msg(12345, "hello world!").expect("发送失败").into();
119 | /// ```
120 | ///
121 | /// # Result
122 | ///
123 | /// 消息id
124 | (CQ_sendPrivateMsg, send_private_msg; user_id: i64, msg: *const c_char => i32),
125 | /// 发送群聊消息
126 | ///
127 | /// # Examples
128 | /// ```should_panic
129 | /// use coolq_sdk_rust::api::send_group_msg;
130 | ///
131 | /// let msg_id: i32 = send_group_msg(123456, "hello world").expect("发送失败").into();
132 | /// ```
133 | (CQ_sendGroupMsg, send_group_msg; group_id: i64, msg: *const c_char => i32),
134 | /// 发送讨论组消息
135 | ///
136 | /// # Examples
137 | /// ```should_panic
138 | /// use coolq_sdk_rust::api::send_discuss_msg;
139 | ///
140 | /// let msg_id: i32 = send_discuss_msg(123456, "hello world").expect("发送失败").into();
141 | /// ```
142 | (CQ_sendDiscussMsg, send_discuss_msg; discuss_id: i64, msg: *const c_char => i32),
143 | /// 撤回消息
144 | ///
145 | /// 消息id可在消息上报事件中获得
146 | ///
147 | /// 自己发的消息只能在2分钟之内撤回。管理员/群主可不限时撤回群员消息
148 | ///
149 | /// # Examples
150 | /// ```should_panic
151 | /// use coolq_sdk_rust::api::delete_msg;
152 | ///
153 | /// delete_msg(1).expect("撤回失败");
154 | /// ```
155 | (CQ_deleteMsg, delete_msg; message_id: i32 => i32),
156 | /// 发送赞
157 | ///
158 | /// `@times`: 赞次数
159 | ///
160 | /// 每天只能发送10次
161 | ///
162 | /// # Examples
163 | /// ```should_panic
164 | /// use coolq_sdk_rust::api::send_like_v2;
165 | ///
166 | /// send_like_v2(12345, 10).expect("赞失败");
167 | /// ```
168 | (CQ_sendLikeV2, send_like_v2; user_id: i64, times: i32 => i32),
169 | /// 踢出群成员
170 | ///
171 | /// `@refuse_rejoin`: 拒绝再次加入
172 | ///
173 | /// # Examples
174 | /// ```should_panic
175 | /// use coolq_sdk_rust::api::set_group_kick;
176 | ///
177 | /// set_group_kick(123456, 12345, false).expect("权限不足");
178 | /// ```
179 | (CQ_setGroupKick, set_group_kick; group_id: i64, user_id: i64, refuse_rejoin: i32 => i32),
180 | /// 禁言群成员
181 | ///
182 | /// 时间为0则解除禁言
183 | ///
184 | /// # Examples
185 | /// ```should_panic
186 | /// use coolq_sdk_rust::api::set_group_ban;
187 | ///
188 | /// set_group_ban(123456, 12345, 60).expect("权限不足");
189 | /// ```
190 | (CQ_setGroupBan, set_group_ban; group_id: i64, user_id: i64, time: i64 => i32),
191 | /// 设置群管理
192 | ///
193 | /// # Examples
194 | /// ```should_panic
195 | /// use coolq_sdk_rust::api::set_group_admin;
196 | ///
197 | /// set_group_admin(123456, 12345, true).expect("权限不足");
198 | /// ```
199 | (CQ_setGroupAdmin, set_group_admin; group_id: i64, user_id: i64, set_admin: i32 => i32),
200 | /// 设置群头衔
201 | ///
202 | /// 头衔为空则删除头衔
203 | ///
204 | /// 时间参数似乎没有效果
205 | ///
206 | /// # Examples
207 | /// ```should_panic
208 | /// use coolq_sdk_rust::api::set_group_special_title;
209 | ///
210 | /// set_group_special_title(123456, 12345, "头衔", -1).expect("设置失败");
211 | /// ```
212 | (CQ_setGroupSpecialTitle, set_group_special_title; group_id: i64, user_id: i64, title: *const c_char, time: i64 => i32),
213 | /// 设置全群禁言
214 | ///
215 | /// # Examples
216 | /// ```should_panic
217 | /// use coolq_sdk_rust::api::set_group_whole_ban;
218 | ///
219 | /// set_group_whole_ban(123456, true).expect("权限不足");
220 | /// ```
221 | (CQ_setGroupWholeBan, set_group_whole_ban; group_id: i64, enable: i32 => i32),
222 | /// 禁言匿名成员
223 | ///
224 | /// # Examples
225 | /// ```should_panic
226 | ///
227 | /// use coolq_sdk_rust::api::set_group_anonymous_ban;
228 | ///
229 | /// set_group_anonymous_ban(123456, "Flag", 60).expect("禁言失败");
230 | /// ```
231 | (CQ_setGroupAnonymousBan, set_group_anonymous_ban; group_id: i64, anonymous_flag: *const c_char, time: i64 => i32),
232 | /// 设置开启群匿名
233 | ///
234 | /// # Examples
235 | /// ```should_panic
236 | /// use coolq_sdk_rust::api::set_group_anonymous;
237 | ///
238 | /// set_group_anonymous(123456, false).expect("权限不足");
239 | /// ```
240 | (CQ_setGroupAnonymous, set_group_anonymous; group_id: i64, enable: i32 => i32),
241 | /// 设置群名片
242 | ///
243 | /// 名片为空则删除名片
244 | ///
245 | /// # Examples
246 | /// ```should_panic
247 | /// use coolq_sdk_rust::api::set_group_card;
248 | ///
249 | /// set_group_card(123456, 12345, "群员a").expect("权限不足");
250 | /// ```
251 | (CQ_setGroupCard, set_group_card; group_id: i64, user_id: i64, card: *const c_char => i32),
252 | /// 退出群组
253 | ///
254 | /// 机器人为群主才能解散
255 | ///
256 | /// # Examples
257 | /// ```should_panic
258 | /// use coolq_sdk_rust::api::set_group_leave;
259 | ///
260 | /// set_group_leave(123456, false).expect("退出失败");
261 | /// ```
262 | (CQ_setGroupLeave, set_group_leave; group_id: i64, is_dismiss: i32 => i32),
263 | /// 退出讨论组
264 | ///
265 | /// # Examples
266 | /// ```should_panic
267 | /// use coolq_sdk_rust::api::set_discuss_leave;
268 | ///
269 | /// set_discuss_leave(123456).expect("退出失败");
270 | /// ```
271 | (CQ_setDiscussLeave, set_discuss_leave; discuss_id: i64 => i32),
272 | /// 处理好友添加请求
273 | ///
274 | /// Flag可在上报的好友添加事件中找到
275 | ///
276 | /// # Examples
277 | /// ```should_panic
278 | /// use coolq_sdk_rust::api::{set_friend_add_request, Flag};
279 | ///
280 | /// set_friend_add_request("Flag", true, "好友a").expect("处理失败");
281 | /// ```
282 | (CQ_setFriendAddRequest, set_friend_add_request; flag: *const c_char, response: i32, comment: *const c_char => i32),
283 | /// 处理加群请求/邀请
284 | ///
285 | /// Flag可在加群事件获得
286 | ///
287 | /// `@request`: 1入群申请/2入群邀请,实际可在加群事件获得
288 | ///
289 | /// # Examples
290 | /// ```should_panic
291 | /// use coolq_sdk_rust::api::{set_group_add_request_v2, Flag};
292 | ///
293 | /// set_group_add_request_v2("Flag", 1, false, "禁止入群").expect("处理失败");
294 | /// ```
295 | (CQ_setGroupAddRequestV2, set_group_add_request_v2; flag: *const c_char, request: i32, response: i32, reason: *const c_char => i32),
296 | /// 获取群员信息
297 | ///
298 | /// 频繁调用建议使用缓存
299 | ///
300 | /// # Examples
301 | /// ```should_panic
302 | /// use coolq_sdk_rust::api::get_group_member_info_v2;
303 | /// use coolq_sdk_rust::targets::group::GroupMember;
304 | /// use std::convert::TryInto;
305 | ///
306 | /// let member_info: GroupMember = get_group_member_info_v2(123456, 12345, false).expect("获取失败").try_into().expect("解析失败");
307 | /// ```
308 | (CQ_getGroupMemberInfoV2, get_group_member_info_v2; group_id: i64, user_id: i64, no_cache: i32 => *const c_char),
309 | /// 获取群成员列表
310 | ///
311 | /// 此处获取到的群成员信息与[`get_group_member_info_v2`]相似,但是部分信息缺失
312 | ///
313 | /// # Examples
314 | /// ```should_panic
315 | /// use coolq_sdk_rust::api::get_group_member_list;
316 | /// use coolq_sdk_rust::targets::group::GroupMember;
317 | /// use std::convert::TryInto;
318 | ///
319 | /// let member_list: Vec = get_group_member_list(123456).expect("获取失败").try_into().expect("解析失败");
320 | /// ```
321 | (CQ_getGroupMemberList, get_group_member_list; group_id: i64 => *const c_char),
322 | /// 获取群列表
323 | ///
324 | /// 群列表里只有少量群信息,更多群信息请使用[`get_group_info`]
325 | ///
326 | /// # Examples
327 | /// ```should_panic
328 | /// use coolq_sdk_rust::api::get_group_list;
329 | /// use coolq_sdk_rust::targets::group::Group;
330 | /// use std::convert::TryInto;
331 | ///
332 | /// let groups: Vec = get_group_list().expect("获取失败").try_into().expect("解析失败");
333 | /// ```
334 | (CQ_getGroupList, get_group_list; => *const c_char),
335 | /// 获取好友列表
336 | ///
337 | /// # Examples
338 | (CQ_getFriendList, get_friend_list; no_cache: i32 => *const c_char),
339 | (CQ_getStrangerInfo, get_stranger_info; user_id: i64, no_cache: i32 => *const c_char),
340 | (CQ_addLog, add_log; priority: i32, tag: *const c_char, msg: *const c_char => i32),
341 | (CQ_getCookies, get_cookies; => *const c_char),
342 | (CQ_getCookiesV2, get_cookies_v2; => *const c_char),
343 | (CQ_getCsrfToken, get_csrf_token; => *const c_char),
344 | (CQ_getLoginQQ, get_login_qq; => i64),
345 | (CQ_getLoginNick, get_login_nick; => *const c_char),
346 | (CQ_getAppDirectory, get_app_directory; => *const c_char),
347 | (CQ_setFatal, set_fatal; error_message: *const c_char => *const c_char),
348 | (CQ_getRecordV2, get_record_v2; file_name: *const c_char, outformat: *const c_char => *const c_char),
349 | (CQ_canSendImage, can_send_image; => bool),
350 | (CQ_canSendRecord, can_send_record; => bool),
351 | (CQ_getImage, get_image; file_name: *const c_char => *const c_char),
352 | (CQ_getGroupInfo, get_group_info; group_id: i64, no_cache: i32 => *const c_char)
353 | );
354 |
355 | convert_from!(i64);
356 | convert_from!(i32);
357 | convert_from!(bool);
358 | convert_from!(String);
359 | convert_from!(*const c_char, String, |c| utf8!(c));
360 | convert_from!(&str, *const c_char, |str: &str| gb18030!(str));
361 | convert_from!(String, *const c_char, |str: String| gb18030!(str.as_str()));
362 | convert_from!(CQLogLevel, i32, |level| match level {
363 | CQLogLevel::DEBUG => CQLOG_DEBUG,
364 | CQLogLevel::INFO => CQLOG_INFO,
365 | CQLogLevel::INFOSUCCESS => CQLOG_INFOSUCCESS,
366 | CQLogLevel::INFORECV => CQLOG_INFORECV,
367 | CQLogLevel::INFOSEND => CQLOG_INFOSEND,
368 | CQLogLevel::WARNING => CQLOG_WARNING,
369 | CQLogLevel::ERROR => CQLOG_ERROR,
370 | CQLogLevel::FATAL => CQLOG_FATAL,
371 | });
372 | convert_from!(bool, i32, |b| b as i32);
373 | convert_from!(*const c_char);
374 | convert_from!((), i32, |_| 0); // 为了支持listener可以返回空()
375 | convert_from!(&MessageSegment, *const c_char, |ms: &MessageSegment| gb18030!(ms.to_string()));
376 |
377 | convert_to!(i64);
378 | convert_to!(i32);
379 | convert_to!(*const c_char);
380 | convert_to!(*const c_char, String, |c| utf8!(c));
381 | convert_to!(i32, bool, |i| i != 0);
382 | try_convert_to!(
383 | *const c_char,
384 | GroupMember,
385 | IoError,
386 | |c| GroupMember::decode(String::from(c).as_bytes())
387 | );
388 | try_convert_to!(*const c_char, Group, IoError, |c| Group::decode(
389 | String::from(c).as_bytes()
390 | ));
391 | try_convert_to!(*const c_char, Vec, IoError, |c| read_multi_object(
392 | String::from(c).as_bytes()
393 | )
394 | .and_then(|objs| objs.iter().map(|b| Group::decode_small(&b)).collect()));
395 | try_convert_to!(*const c_char, Vec, IoError, |c| {
396 | read_multi_object(String::from(c).as_bytes())
397 | .and_then(|objs| objs.iter().map(|b| GroupMember::decode(&b)).collect())
398 | });
399 | try_convert_to!(*const c_char, User, IoError, |c| User::decode(
400 | String::from(c).as_bytes()
401 | ));
402 | try_convert_to!(*const c_char, File, IoError, |c| File::decode(
403 | String::from(c).as_bytes()
404 | ));
405 |
406 | try_convert_to!(*const c_char, Vec, IoError, |c| {
407 | read_multi_object(String::from(c).as_bytes())
408 | .and_then(|objs| objs.iter().map(|b| FriendInfo::decode(&b)).collect())
409 | });
410 |
411 | impl ToString for Convert {
412 | fn to_string(&self) -> String {
413 | self.0.to_string()
414 | }
415 | }
416 |
417 | impl Convert {
418 | /// ```
419 | /// use coolq_sdk_rust::api::Convert;
420 | ///
421 | /// let ok = Convert::from(1).to::();
422 | /// ```
423 | pub fn to>>(self) -> T {
424 | self.into()
425 | }
426 |
427 | pub fn try_to>>(self) -> std::result::Result {
428 | self.try_into()
429 | }
430 | }
431 |
432 | /// 用于转换类型
433 | #[derive(Debug)]
434 | pub struct Convert(T);
435 |
436 | #[derive(Debug)]
437 | pub struct Error(pub i32);
438 |
439 | impl std::fmt::Display for Error {
440 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
441 | write!(f, "COOLQ API ERROR({}).", self.0)
442 | }
443 | }
444 |
445 | impl std::error::Error for Error {}
446 |
447 | /// 返回api是否调用成功。
448 | ///
449 | /// 如发送消息失败,获取群信息失败,不能发送图片等等,返回Error。
450 | pub type Result = std::result::Result;
451 |
452 | /// AFrom是为了跳过'孤儿规则',为Result实现From。
453 | trait AFrom {
454 | fn r_from(_: T) -> Self;
455 | }
456 |
457 | impl AFrom for Result> {
458 | fn r_from(i: i32) -> Self {
459 | if i >= 0 {
460 | Ok(Convert::from(i))
461 | } else {
462 | Err(Error(i))
463 | }
464 | }
465 | }
466 |
467 | impl AFrom for Result> {
468 | fn r_from(i: i64) -> Self {
469 | if i != 0 {
470 | Ok(Convert::from(i))
471 | } else {
472 | Err(Error(0))
473 | }
474 | }
475 | }
476 |
477 | impl AFrom for Result> {
478 | fn r_from(i: bool) -> Self {
479 | if i {
480 | Ok(Convert::from(i))
481 | } else {
482 | Err(Error(0))
483 | }
484 | }
485 | }
486 |
487 | impl AFrom<*const c_char> for Result> {
488 | fn r_from(c: *const c_char) -> Self {
489 | if c != null() {
490 | Ok(Convert::from(c))
491 | } else {
492 | Err(Error(0))
493 | }
494 | }
495 | }
496 |
497 | /// 调试 灰色
498 | static CQLOG_DEBUG: i32 = 0;
499 | /// 信息 黑色
500 | static CQLOG_INFO: i32 = 10;
501 | /// 信息(成功) 紫色
502 | static CQLOG_INFOSUCCESS: i32 = 11;
503 | /// 信息(接收) 蓝色
504 | static CQLOG_INFORECV: i32 = 12;
505 | /// 信息(发送) 绿色
506 | static CQLOG_INFOSEND: i32 = 13;
507 | /// 警告 橙色
508 | static CQLOG_WARNING: i32 = 20;
509 | /// 错误 红色
510 | static CQLOG_ERROR: i32 = 30;
511 | /// 致命错误 深红
512 | static CQLOG_FATAL: i32 = 40;
513 |
514 | /// 日志等级
515 | pub enum CQLogLevel {
516 | DEBUG,
517 | INFO,
518 | INFOSUCCESS,
519 | INFORECV,
520 | INFOSEND,
521 | WARNING,
522 | ERROR,
523 | FATAL,
524 | }
525 |
526 | /// 处理请求的'标识'
527 | pub type Flag = String;
528 |
529 | pub(crate) unsafe fn init(auth_code: i32) {
530 | AUTH_CODE.set(auth_code).unwrap();
531 | init_api_funcs(libloading::Library::new("cqp.dll").unwrap());
532 | }
533 |
--------------------------------------------------------------------------------
/src/events/add_friend_request.rs:
--------------------------------------------------------------------------------
1 | use std::os::raw::c_char;
2 |
3 | use crate::{
4 | api::{set_friend_add_request, Convert, Flag},
5 | targets::user::User,
6 | };
7 |
8 | #[derive(Debug, Clone)]
9 | pub struct AddFriendRequestEvent {
10 | pub sub_type: i32,
11 | pub send_time: i32,
12 | pub msg: String,
13 | pub flag: Flag,
14 | pub user: User,
15 | }
16 |
17 | impl AddFriendRequestEvent {
18 | pub fn new(
19 | sub_type: i32, send_time: i32, user_id: i64, msg: *const c_char, flag: *const c_char,
20 | ) -> Self {
21 | AddFriendRequestEvent {
22 | sub_type,
23 | send_time,
24 | msg: Convert::from(msg).into(),
25 | flag: Convert::from(flag).into(),
26 | user: User::new(user_id),
27 | }
28 | }
29 |
30 | /// `comment`: 备注
31 | pub fn handle(&self, approve: bool, comment: &str) -> crate::api::Result> {
32 | set_friend_add_request(self.flag.clone(), approve, comment)
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/events/add_group_request.rs:
--------------------------------------------------------------------------------
1 | use std::os::raw::c_char;
2 |
3 | use crate::{
4 | api::{set_group_add_request_v2, Convert, Flag},
5 | targets::{group::Group, user::User},
6 | };
7 |
8 | #[derive(Debug, Clone)]
9 | pub struct AddGroupRequestEvent {
10 | pub sub_type: i32,
11 | pub send_time: i32,
12 | pub msg: String,
13 | pub flag: Flag,
14 | pub group: Group,
15 | pub user: User,
16 | }
17 |
18 | impl AddGroupRequestEvent {
19 | pub fn new(
20 | sub_type: i32, send_time: i32, group_id: i64, user_id: i64, msg: *const c_char,
21 | flag: *const c_char,
22 | ) -> Self {
23 | AddGroupRequestEvent {
24 | sub_type,
25 | send_time,
26 | msg: Convert::from(msg).into(),
27 | flag: Convert::from(flag).into(),
28 | group: Group::new(group_id),
29 | user: User::new(user_id),
30 | }
31 | }
32 |
33 | /// 收到入群邀请
34 | pub fn is_invite(&self) -> bool {
35 | self.sub_type == 2
36 | }
37 |
38 | /// 用户申请入群
39 | pub fn is_application(&self) -> bool {
40 | self.sub_type == 1
41 | }
42 |
43 | /// `reason`: 拒绝理由
44 | pub fn handle(&self, approve: bool, reason: &str) -> crate::api::Result> {
45 | set_group_add_request_v2(self.flag.clone(), self.sub_type, approve, reason)
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/events/discuss_message.rs:
--------------------------------------------------------------------------------
1 | //! qq已经放弃讨论组,所以讨论组方面的东西就不写了。
2 |
3 | use std::os::raw::c_char;
4 |
5 | #[derive(Debug, Clone)]
6 | pub struct DiscussMessageEvent {
7 | pub sub_type: i32,
8 | pub msg_id: i32,
9 | pub discuss_id: i64,
10 | pub user_id: i64,
11 | pub msg: String,
12 | pub font: i32,
13 | }
14 |
15 | impl DiscussMessageEvent {
16 | pub fn new(
17 | sub_type: i32, msg_id: i32, discuss_id: i64, user_id: i64, msg: *const c_char, font: i32,
18 | ) -> Self {
19 | DiscussMessageEvent {
20 | sub_type,
21 | msg_id,
22 | discuss_id,
23 | user_id,
24 | msg: "".to_string(),
25 | font,
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/events/friend_add.rs:
--------------------------------------------------------------------------------
1 | use crate::targets::user::User;
2 |
3 | #[derive(Debug, Clone)]
4 | pub struct FriendAddEvent {
5 | pub sub_type: i32,
6 | pub send_time: i32,
7 | pub user: User,
8 | }
9 |
10 | impl FriendAddEvent {
11 | pub fn new(sub_type: i32, send_time: i32, user_id: i64) -> Self {
12 | FriendAddEvent {
13 | sub_type,
14 | send_time,
15 | user: User::new(user_id),
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/events/group_admin.rs:
--------------------------------------------------------------------------------
1 | use crate::targets::{group::Group, user::User};
2 |
3 | #[derive(Debug, Clone)]
4 | pub struct GroupAdminEvent {
5 | pub sub_type: i32,
6 | pub send_time: i32,
7 | pub group: Group,
8 | pub user: User,
9 | }
10 |
11 | impl GroupAdminEvent {
12 | pub fn new(sub_type: i32, send_time: i32, group_id: i64, user_id: i64) -> Self {
13 | GroupAdminEvent {
14 | sub_type,
15 | send_time,
16 | group: Group::new(group_id),
17 | user: User::new(user_id),
18 | }
19 | }
20 |
21 | pub fn is_add(&self) -> bool {
22 | self.sub_type == 2
23 | }
24 |
25 | pub fn is_remove(&self) -> bool {
26 | self.sub_type == 1
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/events/group_ban.rs:
--------------------------------------------------------------------------------
1 | use crate::{
2 | api,
3 | api::Error,
4 | targets::{group::Group, user::User},
5 | };
6 |
7 | #[derive(Debug, Clone)]
8 | pub struct GroupBanEvent {
9 | pub sub_type: i32,
10 | pub send_time: i32,
11 | pub operate_user: User,
12 | pub being_operate_user: User,
13 | pub time: i64,
14 | pub group: Group,
15 | }
16 |
17 | impl GroupBanEvent {
18 | pub fn new(
19 | sub_type: i32, send_time: i32, group_id: i64, operate_user_id: i64,
20 | being_operate_user_id: i64, time: i64,
21 | ) -> Self {
22 | GroupBanEvent {
23 | sub_type,
24 | send_time,
25 | operate_user: User::new(operate_user_id),
26 | being_operate_user: User::new(being_operate_user_id),
27 | time,
28 | group: Group::new(group_id),
29 | }
30 | }
31 |
32 | pub fn is_whole_ban(&self) -> bool {
33 | self.being_operate_user.user_id == 0
34 | }
35 |
36 | /// 撤销禁言
37 | ///
38 | /// 仅在身份为管理员和或群主的时候有效
39 | pub fn revoke(&self) -> crate::api::Result> {
40 | if self.is_ban() {
41 | if self.is_whole_ban() {
42 | self.group.set_whole_ban(false)
43 | } else {
44 | self.group.set_ban(self.being_operate_user.user_id, 0)
45 | }
46 | } else {
47 | Err(Error(0))
48 | }
49 | }
50 |
51 | pub fn is_unban(&self) -> bool {
52 | self.sub_type == 1
53 | }
54 |
55 | pub fn is_ban(&self) -> bool {
56 | self.sub_type == 2
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/src/events/group_member_decrease.rs:
--------------------------------------------------------------------------------
1 | use crate::targets::{group::Group, user::User};
2 |
3 | #[derive(Debug, Clone)]
4 | pub struct GroupMemberDecreaseEvent {
5 | pub sub_type: i32,
6 | pub send_time: i32,
7 | pub operate_user: User,
8 | pub being_operate_user: User,
9 | pub group: Group,
10 | }
11 |
12 | impl GroupMemberDecreaseEvent {
13 | pub fn new(
14 | sub_type: i32, send_time: i32, group_id: i64, operate_user_id: i64,
15 | being_operate_user_id: i64,
16 | ) -> Self {
17 | GroupMemberDecreaseEvent {
18 | sub_type,
19 | send_time,
20 | operate_user: User::new(operate_user_id),
21 | being_operate_user: User::new(being_operate_user_id),
22 | group: Group::new(group_id),
23 | }
24 | }
25 |
26 | /// 主动退出
27 | pub fn is_quit(&self) -> bool {
28 | self.sub_type == 1
29 | }
30 |
31 | /// 被踢出
32 | pub fn is_kick(&self) -> bool {
33 | self.sub_type == 2 || self.sub_type == 3
34 | }
35 |
36 | pub fn is_kick_me(&self) -> bool {
37 | self.sub_type == 3
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/events/group_member_increase.rs:
--------------------------------------------------------------------------------
1 | use crate::targets::{group::Group, user::User};
2 |
3 | #[derive(Debug, Clone)]
4 | pub struct GroupMemberIncreaseEvent {
5 | pub sub_type: i32,
6 | pub send_time: i32,
7 | pub operate_user: User,
8 | pub being_operate_user: User,
9 | pub group: Group,
10 | }
11 |
12 | impl GroupMemberIncreaseEvent {
13 | pub fn new(
14 | sub_type: i32, send_time: i32, group_id: i64, operate_user_id: i64,
15 | being_operate_user_id: i64,
16 | ) -> Self {
17 | GroupMemberIncreaseEvent {
18 | sub_type,
19 | send_time,
20 | operate_user: User::new(operate_user_id),
21 | being_operate_user: User::new(being_operate_user_id),
22 | group: Group::new(group_id),
23 | }
24 | }
25 |
26 | /// 被邀请入群
27 | pub fn is_invite(&self) -> bool {
28 | self.sub_type == 2
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/events/group_message.rs:
--------------------------------------------------------------------------------
1 | use std::os::raw::c_char;
2 |
3 | use crate::{
4 | api::{Convert, Flag},
5 | targets::{
6 | Anonymous,
7 | group::Group,
8 | message::{Message, SendMessage},
9 | user::User,
10 | },
11 | };
12 | use crate::api::get_group_member_info_v2;
13 | use crate::targets::group::GroupMember;
14 |
15 | #[derive(Debug, Clone)]
16 | pub struct GroupMessageEvent {
17 | pub sub_type: i32,
18 | pub anonymous_flag: Flag,
19 | pub msg: Message,
20 | pub font: i32,
21 | pub group: Group,
22 | pub user: User,
23 | }
24 |
25 | impl GroupMessageEvent {
26 | pub fn new(
27 | sub_type: i32, msg_id: i32, group_id: i64, user_id: i64, anonymous_flag: *const c_char,
28 | msg: *const c_char, font: i32,
29 | ) -> Self {
30 | GroupMessageEvent {
31 | sub_type,
32 | anonymous_flag: Convert::from(anonymous_flag).into(),
33 | msg: Message::new(msg, msg_id),
34 | font,
35 | group: Group::new(group_id),
36 | user: {
37 | let mut user = User::new(user_id);
38 | if let Ok(gm) = get_group_member_info_v2(group_id, user_id, false) {
39 | user.set_authority(gm.try_to::().unwrap().authority);
40 | }
41 | user
42 | },
43 | }
44 | }
45 |
46 | pub fn get_message(&self) -> &Message {
47 | &self.msg
48 | }
49 |
50 | pub fn is_anonymous(&self) -> bool {
51 | !self.anonymous_flag.is_empty()
52 | }
53 |
54 | pub fn get_anonymous(&self) -> std::io::Result {
55 | if self.is_anonymous() {
56 | Anonymous::decode(self.anonymous_flag.as_bytes(), self.group.group_id)
57 | } else {
58 | Ok(Anonymous::default())
59 | }
60 | }
61 |
62 | pub fn reply(&self, msg: impl ToString) -> crate::api::Result {
63 | self.group.send_message(msg)
64 | }
65 |
66 | pub fn reply_at(&self, msg: impl ToString) -> crate::api::Result {
67 | self.group.at(self.user.user_id, msg)
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/src/events/group_upload.rs:
--------------------------------------------------------------------------------
1 | use crate::{api::Convert, targets::File};
2 | use std::{convert::TryInto, os::raw::c_char};
3 |
4 | #[derive(Debug, Clone)]
5 | pub struct GroupUploadEvent {
6 | pub sub_type: i32,
7 | pub send_time: i32,
8 | pub group_id: i64,
9 | pub user_id: i64,
10 | pub file: File,
11 | }
12 |
13 | impl GroupUploadEvent {
14 | pub fn new(
15 | sub_type: i32, send_time: i32, group_id: i64, user_id: i64, file: *const c_char,
16 | ) -> Self {
17 | GroupUploadEvent {
18 | sub_type,
19 | send_time,
20 | group_id,
21 | user_id,
22 | file: Convert::from(file).try_into().unwrap(),
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/events/mod.rs:
--------------------------------------------------------------------------------
1 | mod add_friend_request;
2 | mod add_group_request;
3 | mod discuss_message;
4 | mod friend_add;
5 | mod group_admin;
6 | mod group_ban;
7 | mod group_member_decrease;
8 | mod group_member_increase;
9 | mod group_message;
10 | mod group_upload;
11 | mod private_message;
12 |
13 | pub use add_friend_request::*;
14 | pub use add_group_request::*;
15 | pub use discuss_message::*;
16 | pub use friend_add::*;
17 | pub use group_admin::*;
18 | pub use group_ban::*;
19 | pub use group_member_decrease::*;
20 | pub use group_member_increase::*;
21 | pub use group_message::*;
22 | pub use group_upload::*;
23 | pub use private_message::*;
24 |
25 | macro_rules! impl_new {
26 | ($($name:ident),*) => {
27 | $(
28 | impl $name {
29 | pub fn new() -> Self {
30 | $name
31 | }
32 | }
33 | )*
34 | };
35 | }
36 |
37 | #[allow(dead_code)]
38 | pub struct StartEvent;
39 | #[allow(dead_code)]
40 | pub struct ExitEvent;
41 | #[allow(dead_code)]
42 | pub struct DisableEvent;
43 |
44 | impl_new!(StartEvent, ExitEvent, DisableEvent);
45 |
--------------------------------------------------------------------------------
/src/events/private_message.rs:
--------------------------------------------------------------------------------
1 | use std::os::raw::c_char;
2 |
3 | use crate::targets::{
4 | message::{Message, SendMessage},
5 | user::User,
6 | };
7 |
8 | #[derive(Debug, Clone)]
9 | pub enum PrivateMessageType {
10 | Friend,
11 | Group,
12 | Discuss,
13 | Other,
14 | }
15 |
16 | impl From for PrivateMessageType {
17 | fn from(i: i32) -> Self {
18 | match i {
19 | 11 => PrivateMessageType::Friend,
20 | 2 => PrivateMessageType::Group,
21 | 3 => PrivateMessageType::Discuss,
22 | _ => PrivateMessageType::Other,
23 | }
24 | }
25 | }
26 |
27 | #[derive(Debug)]
28 | pub struct PrivateMessageEvent {
29 | pub sub_type: i32,
30 | pub msg: Message,
31 | pub font: i32,
32 | pub user: User,
33 | }
34 |
35 | impl PrivateMessageEvent {
36 | pub fn new(sub_type: i32, msg_id: i32, user_id: i64, msg: *const c_char, font: i32) -> Self {
37 | PrivateMessageEvent {
38 | sub_type,
39 | msg: Message::new(msg, msg_id),
40 | font,
41 | user: User::new(user_id),
42 | }
43 | }
44 |
45 | pub fn get_message(&self) -> &Message {
46 | &self.msg
47 | }
48 |
49 | pub fn reply(&self, msg: impl ToString) -> crate::api::Result {
50 | self.user.send_message(msg)
51 | }
52 |
53 | pub fn get_sub_type(&self) -> PrivateMessageType {
54 | PrivateMessageType::from(self.sub_type)
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/iconv.rs:
--------------------------------------------------------------------------------
1 | //! 修改自 https://github.com/andelf/rust-iconv/blob/master/src/lib.rs
2 |
3 | extern crate libc;
4 |
5 | use libc::{E2BIG, EILSEQ, EINVAL};
6 | use std::{
7 | ffi::CString,
8 | io,
9 | io::{Read, Result, Write},
10 | iter, mem, ptr, str,
11 | };
12 |
13 | use libc::{c_char, c_int, c_void, size_t};
14 |
15 | #[allow(dead_code)]
16 | const DEFAULT_BUF_SIZE: usize = 64 * 1024;
17 |
18 | #[allow(non_camel_case_types)]
19 | type iconv_t = *mut c_void;
20 |
21 | lazy_static! {
22 | static ref LIBICONV: libloading::Library = libloading::Library::new("libiconv.dll").unwrap();
23 | }
24 |
25 | /// The representation of a iconv converter
26 | pub(crate) struct Converter {
27 | cd: iconv_t,
28 | }
29 |
30 | impl Converter {
31 | /// Creates a new Converter from ``from`` encoding and ``to`` encoding.
32 | pub fn new(from: &str, to: &str) -> Converter {
33 | lazy_static! {
34 | static ref ICONV_OPEN: extern "C" fn(__tocode: *const c_char, __fromcode: *const c_char) -> iconv_t =
35 | unsafe { *LIBICONV.get("libiconv_open".as_bytes(),).unwrap() };
36 | };
37 |
38 | let from_encoding = CString::new(from).unwrap();
39 | let to_encoding = CString::new(to).unwrap();
40 |
41 | let handle = ICONV_OPEN(to_encoding.as_ptr(), from_encoding.as_ptr());
42 | if handle as isize == -1 {
43 | panic!(
44 | "Error creating conversion descriptor from {:} to {:}",
45 | from, to
46 | );
47 | }
48 | Converter { cd: handle }
49 | }
50 |
51 | /// Convert from input into output.
52 | /// Returns (bytes_read, bytes_written, errno).
53 | pub fn convert(&self, input: &[u8], output: &mut [u8]) -> (usize, usize, c_int) {
54 | lazy_static! {
55 | static ref ICONV: extern "C" fn(
56 | __cd: iconv_t,
57 | __inbuf: *mut *mut c_char,
58 | __inbytesleft: *mut size_t,
59 | __outbuf: *mut *mut c_char,
60 | __outbytesleft: *mut size_t,
61 | ) -> size_t = unsafe { *LIBICONV.get("libiconv".as_bytes()).unwrap() };
62 | };
63 |
64 | let input_left = input.len() as size_t;
65 | let output_left = output.len() as size_t;
66 | if input_left > 0 && output_left > 0 {
67 | let input_ptr = input.as_ptr();
68 | let output_ptr = output.as_ptr();
69 |
70 | let ret = unsafe {
71 | ICONV(
72 | self.cd,
73 | mem::transmute(&input_ptr),
74 | mem::transmute(&input_left),
75 | mem::transmute(&output_ptr),
76 | mem::transmute(&output_left),
77 | )
78 | };
79 | let bytes_read = input.len() - input_left as usize;
80 | let bytes_written = output.len() - output_left as usize;
81 |
82 | return (
83 | bytes_read,
84 | bytes_written,
85 | if ret as isize == -1 {
86 | io::Error::last_os_error().raw_os_error().unwrap() as c_int
87 | } else {
88 | 0
89 | },
90 | );
91 | } else if input_left == 0 && output_left > 0 {
92 | let output_ptr = output.as_ptr();
93 |
94 | let ret = unsafe {
95 | ICONV(
96 | self.cd,
97 | ptr::null_mut::<*mut c_char>(),
98 | mem::transmute(&input_left),
99 | mem::transmute(&output_ptr),
100 | mem::transmute(&output_left),
101 | )
102 | };
103 |
104 | let bytes_written = output.len() - output_left as usize;
105 |
106 | return (
107 | 0,
108 | bytes_written,
109 | if ret as isize == -1 {
110 | io::Error::last_os_error().raw_os_error().unwrap() as c_int
111 | } else {
112 | 0
113 | },
114 | );
115 | } else {
116 | let ret = unsafe {
117 | ICONV(
118 | self.cd,
119 | ptr::null_mut::<*mut c_char>(),
120 | mem::transmute(&input_left),
121 | ptr::null_mut::<*mut c_char>(),
122 | mem::transmute(&output_left),
123 | )
124 | };
125 |
126 | return (0, 0, ret as i32);
127 | }
128 | }
129 | }
130 |
131 | impl Drop for Converter {
132 | fn drop(&mut self) {
133 | lazy_static! {
134 | static ref ICONV_CLOSE: extern "C" fn(__cd: iconv_t) -> c_int =
135 | unsafe { *LIBICONV.get("libiconv_close".as_bytes()).unwrap() };
136 | };
137 | ICONV_CLOSE(self.cd);
138 | }
139 | }
140 |
141 | /// A ``Reader`` which does iconv convert from another Reader.
142 | pub struct IconvReader {
143 | inner: R,
144 | conv: Converter,
145 | buf: Vec,
146 | read_pos: usize,
147 | write_pos: usize,
148 | err: Option,
149 | tempbuf: Vec, // used when outbut is too small and can't make a single convertion
150 | }
151 |
152 | impl IconvReader {
153 | #[allow(dead_code)]
154 | pub fn new(r: R, from: &str, to: &str) -> IconvReader {
155 | let conv = Converter::new(from, to);
156 | let mut buf = Vec::with_capacity(DEFAULT_BUF_SIZE);
157 | buf.extend(iter::repeat(0).take(DEFAULT_BUF_SIZE));
158 | IconvReader {
159 | inner: r,
160 | conv: conv,
161 | buf: buf,
162 | read_pos: 0,
163 | write_pos: 0,
164 | err: None,
165 | tempbuf: Vec::new(), // small buf allocate dynamicly
166 | }
167 | }
168 |
169 | fn fill_buf(&mut self) {
170 | if self.read_pos > 0 {
171 | unsafe {
172 | ptr::copy::(
173 | self.buf.as_mut_ptr(),
174 | mem::transmute(&self.buf[self.read_pos]),
175 | self.write_pos - self.read_pos,
176 | );
177 | }
178 |
179 | self.write_pos -= self.read_pos;
180 | self.read_pos = 0;
181 | }
182 | match self.inner.read(&mut self.buf[self.write_pos..]) {
183 | Ok(nread) => {
184 | self.write_pos += nread;
185 | },
186 | Err(e) => {
187 | self.err = Some(e);
188 | },
189 | }
190 | }
191 | }
192 |
193 | impl Read for IconvReader {
194 | fn read(&mut self, buf: &mut [u8]) -> Result {
195 | if self.tempbuf.len() != 0 {
196 | let mut nwrite = 0;
197 | for slot in self.tempbuf.iter() {
198 | buf[nwrite] = *slot;
199 | nwrite += 1;
200 | }
201 | //let nwrite = buf.clone_from_slice(self.tempbuf.as_ref());
202 | if nwrite < self.tempbuf.len() {
203 | self.tempbuf = self.tempbuf[nwrite..].to_vec();
204 | } else {
205 | self.tempbuf = Vec::new();
206 | }
207 | return Ok(nwrite);
208 | }
209 |
210 | while self.write_pos == 0 || self.read_pos == self.write_pos {
211 | match self.err {
212 | Some(ref e) => {
213 | return Err(io::Error::new(e.kind(), ""));
214 | },
215 | None => self.fill_buf(),
216 | }
217 | }
218 |
219 | let (nread, nwrite, err) = self
220 | .conv
221 | .convert(&self.buf[self.read_pos..self.write_pos], buf);
222 |
223 | self.read_pos += nread;
224 |
225 | match err {
226 | EILSEQ => {
227 | //debug!("An invalid multibyte sequence has been encountered in the input.");
228 | return Err(io::Error::new(io::ErrorKind::InvalidInput, ""));
229 | },
230 | EINVAL => {
231 | //debug!("An incomplete multibyte sequence has been encountered in the input.");
232 | // FIXME fill_buf() here is ugly
233 | self.fill_buf();
234 | return Ok(nwrite);
235 | },
236 | E2BIG => {
237 | //debug!("There is not sufficient room at *outbuf.");
238 | // FIXED: if outbuf buffer has size 1? Can't hold a
239 | if nread == 0 && nwrite == 0 && buf.len() > 0 {
240 | // outbuf too small and can't conv 1 rune
241 | let mut tempbuf = Vec::with_capacity(8);
242 | tempbuf.extend(iter::repeat(0u8).take(8));
243 | assert!(self.tempbuf.is_empty());
244 | let (nread, temp_nwrite, err) = self
245 | .conv
246 | .convert(&self.buf[self.read_pos..self.write_pos], &mut tempbuf[..]);
247 | self.read_pos += nread;
248 | // here we will write 1 or 2 bytes as most.
249 | // try avoiding return Ok(0)
250 | let mut nwrite = 0;
251 | for slot in tempbuf.iter() {
252 | buf[nwrite] = *slot;
253 | nwrite += 1
254 | }
255 | //buf.clone_from_slice(tempbuf.as_slice());
256 | self.tempbuf = tempbuf[nwrite..temp_nwrite].to_vec();
257 | match err {
258 | EILSEQ => return Err(io::Error::new(io::ErrorKind::InvalidInput, "")),
259 | _ => return Ok(nwrite),
260 | }
261 | }
262 | return Ok(nwrite);
263 | },
264 | 0 => {
265 | return Ok(nwrite);
266 | },
267 | _ => unreachable!(),
268 | }
269 | }
270 | }
271 |
272 | /// A ``Writer`` which does iconv convert into another Writer. not implemented yet.
273 | #[allow(dead_code)]
274 | pub struct IconvWriter {
275 | inner: W,
276 | conv: Converter,
277 | buf: Vec,
278 | read_pos: usize,
279 | write_pos: usize,
280 | err: Option,
281 | }
282 |
283 | impl IconvWriter {
284 | #[allow(dead_code)]
285 | pub fn new(r: W, from: &str, to: &str) -> IconvWriter {
286 | let conv = Converter::new(from, to);
287 | let mut buf = Vec::with_capacity(DEFAULT_BUF_SIZE);
288 | buf.extend(iter::repeat(0u8).take(DEFAULT_BUF_SIZE));
289 | IconvWriter {
290 | inner: r,
291 | conv: conv,
292 | buf: buf,
293 | read_pos: 0,
294 | write_pos: 0,
295 | err: None,
296 | }
297 | }
298 | }
299 |
300 | impl Write for IconvWriter {
301 | fn write(&mut self, _buf: &[u8]) -> Result {
302 | unimplemented!()
303 | }
304 |
305 | fn flush(&mut self) -> Result<()> {
306 | unimplemented!()
307 | }
308 | }
309 |
310 | // TODO: use Result<> instead of Option<> to indicate Error
311 | pub fn convert_bytes(inbuf: &[u8], from: &str, to: &str) -> Option> {
312 | let converter = Converter::new(from, to);
313 | let mut outbuf_size = inbuf.len() * 2;
314 | let mut total_nread = 0;
315 | let mut total_nwrite = 0;
316 |
317 | let mut outbuf = Vec::with_capacity(outbuf_size);
318 | unsafe { outbuf.set_len(outbuf_size) };
319 |
320 | while total_nread < inbuf.len() {
321 | let (nread, nwrite, err) =
322 | converter.convert(&inbuf[total_nread..], &mut outbuf[total_nwrite..]);
323 |
324 | total_nread += nread;
325 | total_nwrite += nwrite;
326 |
327 | match err {
328 | EINVAL | EILSEQ => return None,
329 | E2BIG => {
330 | outbuf_size += inbuf.len();
331 | outbuf.reserve(outbuf_size);
332 | unsafe { outbuf.set_len(outbuf_size) };
333 | },
334 | _ => (),
335 | }
336 | }
337 |
338 | unsafe { outbuf.set_len(total_nwrite) };
339 | outbuf.shrink_to_fit();
340 |
341 | return Some(outbuf);
342 | }
343 |
344 | /// Can be encoded to bytes via iconv
345 | pub trait IconvEncodable {
346 | /// Encode to bytes with encoding
347 | fn encode_with_encoding(&self, encoding: &str) -> Option>;
348 | }
349 |
350 | impl<'a> IconvEncodable for &'a [u8] {
351 | fn encode_with_encoding(&self, encoding: &str) -> Option> {
352 | convert_bytes(*self, "UTF-8", encoding)
353 | }
354 | }
355 |
356 | impl<'a> IconvEncodable for Vec {
357 | fn encode_with_encoding(&self, encoding: &str) -> Option> {
358 | convert_bytes(&self[..], "UTF-8", encoding)
359 | }
360 | }
361 |
362 | impl<'a> IconvEncodable for &'a str {
363 | fn encode_with_encoding(&self, encoding: &str) -> Option> {
364 | return self.as_bytes().encode_with_encoding(encoding);
365 | }
366 | }
367 |
368 | impl<'a> IconvEncodable for String {
369 | fn encode_with_encoding(&self, encoding: &str) -> Option> {
370 | return self.as_bytes().encode_with_encoding(encoding);
371 | }
372 | }
373 |
374 | /// Can be decoded to str via iconv
375 | pub trait IconvDecodable {
376 | /// Decode to str with encoding
377 | fn decode_with_encoding(&self, encoding: &str) -> Option;
378 | }
379 |
380 | impl<'a> IconvDecodable for &'a [u8] {
381 | fn decode_with_encoding(&self, encoding: &str) -> Option {
382 | convert_bytes(*self, encoding, "UTF-8")
383 | .and_then(|bs| str::from_utf8(&bs[..]).map(|s| s.to_string()).ok())
384 | }
385 | }
386 |
387 | impl<'a> IconvDecodable for Vec {
388 | fn decode_with_encoding(&self, encoding: &str) -> Option {
389 | convert_bytes(&self[..], encoding, "UTF-8")
390 | .and_then(|bs| str::from_utf8(&bs[..]).map(|s| s.to_string()).ok())
391 | }
392 | }
393 |
--------------------------------------------------------------------------------
/src/lib.rs:
--------------------------------------------------------------------------------
1 | #![allow(non_upper_case_globals)]
2 | #![allow(unused_variables)]
3 |
4 | #![doc(html_root_url = "https://docs.rs/coolq-sdk-rust/latest")]
5 | #![cfg_attr(docsrs, feature(doc_cfg))]
6 |
7 | //! 使用Rust编写的酷q sdk。
8 | //!
9 | //! ## Get started
10 | //! ```toml
11 | //! coolq-sdk-rust = "0.1"
12 | //! ```
13 | //!
14 | //!
15 | //! ## Features
16 | //!
17 | //! * `enhanced-cqcode`: 开启 [增强cq码(图片)][enhanced-cqcode]
18 | //! * `async-listener`: 开启async事件回调函数
19 | //! * `tokio-threaded`: 开启tokio的rt-threaded feature。
20 | //!
21 | //! [enhanced-cqcode]: crate::targets::cqcode::CQImage
22 | //!
23 | //!
24 | //! ## Examples
25 | //!
26 | //! `Cargo.toml`:
27 | //! ```toml
28 | //! [dependencies]
29 | //! coolq-sdk-rust = "0.1"
30 | //!
31 | //! [build-dependencies]
32 | //! cqrs_builder = { version = "0.1", features = ["full-priority"] }
33 | //!
34 | //! [lib]
35 | //! crate-type = ["cdylib"]
36 | //! ```
37 | //!
38 | //!
39 | //! `build.rs`:
40 | //! ```no_run
41 | //! // 在编译时生成适用于`coolq-sdk-rust`的app.json,json可在target目录同生成的二进制文件一起找到>
42 | //! use cqrs_builder::AppJson;
43 | //!
44 | //! fn main() {
45 | //! AppJson::new("dev.gugugu.example") // appid
46 | //! .name("rust-sdk-example".to_owned())
47 | //! .version("0.0.1".to_owned())
48 | //! .version_id(1)
49 | //! .author("soeur ".to_owned())
50 | //! .description("rust sdk example.".to_owned())
51 | //! .finish();
52 | //! }
53 | //! ```
54 | //! 目前还支持一个`feature`:
55 | //!
56 | //! * `full-priority` :
57 | //!
58 | //! > 启用该功能之后,[cqrs_builder](https://docs.rs/cqrs_builder)会生成支持全部 [**优先级**](https://docs.cqp.im/dev/v9/app.json/event/#priority) 的app.json
59 | //! >
60 | //! > 更多信息可以在[AppJson](https://docs.rs/cqrs_builder/latest/cqrs_builder/struct.AppJson.html)找到。
61 | //!
62 | //!
63 | //! `lib.rs`:
64 | //! ```no_run
65 | //! use coolq_sdk_rust::prelude::*;
66 | //! use coolq_sdk_rust::targets::message::MessageSegment;
67 | //!
68 | //! // 必须有一个`coolq_sdk_rust::main`函数。
69 | //! #[coolq_sdk_rust::main]
70 | //! fn main() {
71 | //! api::add_log(CQLogLevel::INFOSUCCESS, "info", "插件enable").expect("日志发送失败");
72 | //! }
73 | //!
74 | //! // `priority`可选填,默认中优先级。
75 | //! // 开启`full-priority`功能之后,`priority`才会生效。否则除medium外的回调函数将不会被酷q调用
76 | //! #[listener(priority = "high")]
77 | //! fn this_is_private_msg(event: PrivateMessageEvent) {
78 | //! event.reply("hello");
79 | //! }
80 | //!
81 | //! // async函数
82 | //! // 异步函数将放入sdk共用的tokio runtime中处理
83 | //! // 异步函数无法拦截事件
84 | //! #[listener]
85 | //! async fn this_is_also_private_msg(event: PrivateMessageEvent) {
86 | //! xxx.await;
87 | //! }
88 | //!
89 | //! // block_on宏
90 | //! // 添加了block_on宏的异步函数 将会生成一个新的tokio runtime来***阻塞***运行
91 | //! // 该类函数可拦截事件
92 | //! #[listener]
93 | //! #[block_on]
94 | //! async fn oh(_: ExitEvent) {
95 | //! say_bye.await
96 | //! }
97 | //!
98 | //! // 这是一个检测群聊消息中含有什么cq码的例子
99 | //! #[listener]
100 | //! fn group_msg(event: GroupMessageEvent) {
101 | //! if event.get_message().has_cqcode() {
102 | //! let mut msg = MessageSegment::new();
103 | //! event.get_message().cqcodes.iter().for_each(|cqcode| {
104 | //! msg.add(cqcode).add("\n");
105 | //! });
106 | //! event.reply_at(format!("信息含有以下cq码: {:?}", msg).no_cq_code());
107 | //! }
108 | //! }
109 | //! ```
110 |
111 | #[macro_use]
112 | extern crate lazy_static;
113 |
114 | use std::panic::set_hook;
115 |
116 | #[doc(hidden)]
117 | pub use cqrs_macro::main;
118 |
119 | use crate::api::set_fatal;
120 |
121 | mod iconv;
122 |
123 | pub mod api;
124 | pub mod events;
125 | pub mod targets;
126 |
127 | pub mod prelude {
128 | pub use crate::{
129 | api::{self, Convert, CQLogLevel},
130 | events::*,
131 | targets::{cqcode::*, group::Group, message::*, user::User, Anonymous, File},
132 | };
133 | pub use cqrs_macro::listener;
134 | pub use cqrs_macro::block_on;
135 | }
136 |
137 | #[cfg(feature = "async-listener")]
138 | use {
139 | tokio::runtime::Runtime,
140 | once_cell::sync::Lazy,
141 | futures::Future
142 | };
143 |
144 | #[doc(hidden)]
145 | #[cfg(feature = "async-listener")]
146 | pub static ASYNC_RUNTIME: Lazy = Lazy::new(|| Runtime::new().unwrap());
147 |
148 | #[doc(hidden)]
149 | #[cfg(feature = "async-listener")]
150 | pub fn block_on(f: F) -> F::Output {
151 | Runtime::new().unwrap().block_on(f)
152 | }
153 |
154 | pub const APIVER: usize = 9;
155 |
156 | #[doc(hidden)]
157 | #[export_name = "Initialize"]
158 | pub unsafe extern "stdcall" fn initialize(auth_code: i32) -> i32 {
159 | set_hook(Box::new(|info| {
160 | // 在 mirai-native 上会返回 0 而被当成错误
161 | let _ = set_fatal(info.to_string());
162 | }));
163 | api::init(auth_code);
164 | 0
165 | }
166 |
--------------------------------------------------------------------------------
/src/targets/cqcode.rs:
--------------------------------------------------------------------------------
1 | use std::collections::HashMap;
2 | use std::fmt::{Debug, Display, Formatter};
3 |
4 | use regex::Regex;
5 |
6 | #[cfg(feature = "enhanced-cqcode")]
7 | use {
8 | crate::api::get_app_directory,
9 | hex::ToHex,
10 | md5::Digest,
11 | std::io::{Error, ErrorKind},
12 | std::path::Path,
13 | tokio::fs::{copy, File},
14 | tokio::prelude::io::AsyncWriteExt
15 | };
16 |
17 | lazy_static! {
18 | static ref tag_regex: Regex = Regex::new(r"\[CQ:([A-Za-z]*)(?:(,[^\[\]]+))?]").unwrap();
19 | static ref args_regex: Regex = Regex::new(r",([A-Za-z]+)=([^,\[\]]+)").unwrap();
20 | }
21 |
22 | #[derive(Debug, Clone)]
23 | pub enum CQCode {
24 | Face(i32),
25 | Emoji(i32),
26 | Bface(i32),
27 | Sface(i32),
28 | Image(String),
29 | Record(String, bool),
30 | At(i64),
31 | AtAll(),
32 | Rps(i32),
33 | Dice(i32),
34 | Shake(),
35 | Anonymous(bool),
36 | Sign(String, String, String),
37 | Location(f32, f32, String, String),
38 | Music(String, i32, i32),
39 | MusicCustom(String, String, String, String, String),
40 | Share(String, String, String, String),
41 | Contact(i64, String), // 推荐名片,id为群号/qq号,type为group/qq。
42 | Unknown(String),
43 | }
44 |
45 | impl Display for CQCode {
46 | fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
47 | let s = match self {
48 | CQCode::Face(id) => format!("[CQ:face,id={}]", id),
49 | CQCode::Emoji(id) => format!("[CQ:emoji,id={}]", id),
50 | CQCode::Bface(id) => format!("[CQ:bface,id={}]", id),
51 | CQCode::Sface(id) => format!("[CQ:sface,id={}]", id),
52 | CQCode::Image(img) => format!("[CQ:image,file={}]", img),
53 | CQCode::Record(file, magic) => {
54 | format!("[CQ:record,file={},magic={}]", file, magic.to_string())
55 | },
56 | CQCode::At(qq) => format!("[CQ:at,qq={}]", qq),
57 | CQCode::AtAll() => "[CQ:at,qq=all]".to_owned(),
58 | CQCode::Rps(t) => format!("[CQ:rps,type={}]", t),
59 | CQCode::Dice(t) => format!("[CQ:dice,type={}]", t),
60 | CQCode::Shake() => "[CQ:shake]".to_owned(),
61 | CQCode::Anonymous(ignore) => format!("[CQ:anonymous,ignore={}]", ignore.to_string()),
62 | CQCode::Location(latitude, longitude, title, content) => format!(
63 | "[CQ:location,lat={},lon={},title={},content={}]",
64 | latitude, longitude, title, content
65 | ),
66 | CQCode::Music(t, id, style) => {
67 | format!("[CQ:music,type={},id={},style={}]", t, id, style)
68 | },
69 | CQCode::MusicCustom(url, audio, title, content, image) => format!(
70 | "[CQ:music,type=custom,url={},audio={},title={},content={},image={}]",
71 | url, audio, title, content, image
72 | ),
73 | CQCode::Share(url, title, content, image) => format!(
74 | "[CQ:share,url={},title={},content={},image={}]",
75 | url, title, content, image
76 | ),
77 | _ => String::from("Unsupported cqcode."),
78 | };
79 | write!(f, "{}", s)
80 | }
81 | }
82 |
83 | #[cfg(feature = "enhanced-cqcode")]
84 | #[derive(Debug, Clone)]
85 | pub enum CQImage {
86 | /// 默认发送data\image\{image}。"xx.jpg"
87 | Default(String),
88 | /// 发送指定目录下的{image}。该目录必须可读。"/home/me/xx.jpg"
89 | File(String),
90 | /// 发送base64编码的图片。"JXU2MThCJXU4QkY0JXU4QkREJXVGRjBDJXU1NDNCJXU2MjEx"
91 | Base64(String),
92 | /// 发送二进制图片。"很明显,这个没办法演示给你看"
93 | Binary(Vec),
94 | }
95 |
96 | #[cfg(feature = "enhanced-cqcode")]
97 | #[cfg_attr(docsrs, doc(cfg(feature = "enhanced-cqcode")))]
98 | impl CQImage {
99 | /// 有阻塞版本[`to_file_name_blocking`]
100 | ///
101 | /// [`to_file_name_blocking`]: CQImage::to_file_name_blocking
102 | pub async fn to_file_name(&self) -> std::io::Result {
103 | // 插件数据目录在data\app\appid,借此来获取data目录。
104 | let image_dir = Path::new(&get_app_directory().unwrap().to::())
105 | .parent()
106 | .unwrap()
107 | .parent()
108 | .unwrap()
109 | .join("image");
110 |
111 | Ok(match self {
112 | CQImage::Default(img) => img.clone(),
113 |
114 | CQImage::File(path) => {
115 | let path = Path::new(path);
116 | if !path.exists() && !path.is_file() {
117 | return Err(Error::new(
118 | ErrorKind::NotFound,
119 | "image file not found or not a file",
120 | ));
121 | }
122 | let name = path.file_name().unwrap();
123 | let from = image_dir.join(name);
124 | let to = Path::new(&from);
125 | if !to.exists() {
126 | copy(path, to).await?;
127 | }
128 | to.to_str().unwrap().to_owned()
129 | },
130 |
131 | CQImage::Binary(bytes) => {
132 | let name = md5::Md5::digest(bytes).encode_hex::();
133 | let to = image_dir.join(name);
134 | if !to.exists() {
135 | self.save_file(bytes, &to).await?;
136 | }
137 | to.to_str().unwrap().to_owned()
138 | },
139 |
140 | CQImage::Base64(b64) => {
141 | let bytes = base64::decode(b64).expect("Invalid base64 - CQImage");
142 | let name = md5::Md5::digest(&bytes).encode_hex::();
143 | let to = image_dir.join(name);
144 | if !to.exists() {
145 | self.save_file(&bytes, &to).await?;
146 | }
147 | to.to_str().unwrap().to_owned()
148 | },
149 | })
150 | }
151 |
152 | async fn save_file(&self, data: &[u8], path: &Path) -> std::io::Result<()> {
153 | let mut file = File::create(path).await?;
154 | file.write_all(data).await?;
155 | file.sync_all().await
156 | }
157 |
158 | /// 有异步版本[`to_file_name`]
159 | ///
160 | /// [`to_file_name`]: CQImage::to_file_name
161 | pub fn to_file_name_blocking(&self) -> std::io::Result {
162 | tokio::runtime::Runtime::new()?.block_on(self.to_file_name())
163 | }
164 | }
165 |
166 | pub fn clean(s: &str) -> String {
167 | tag_regex.replace_all(s, "").to_string()
168 | }
169 |
170 | pub trait CQStr {
171 | fn has_cq_code(&self) -> bool;
172 | fn no_cq_code(&self) -> String;
173 | }
174 | impl CQStr for str {
175 | fn has_cq_code(&self) -> bool {
176 | tag_regex.is_match(self)
177 | }
178 |
179 | fn no_cq_code(&self) -> String {
180 | let mut s = String::new();
181 | for c in self.chars() {
182 | match c {
183 | '&' => s.push_str("&"),
184 | '[' => s.push_str("["),
185 | ']' => s.push_str("]"),
186 | ',' => s.push_str(","),
187 | _ => s.push(c),
188 | };
189 | }
190 | s
191 | }
192 | }
193 |
194 | pub fn parse(msg: &str) -> Vec {
195 | tag_regex
196 | .captures_iter(msg)
197 | .map(|codes| {
198 | let tag = codes.get(1).unwrap();
199 | let args: HashMap = if let Some(arg) = codes.get(2) {
200 | args_regex
201 | .captures_iter(arg.as_str())
202 | .map(|a| {
203 | (
204 | a.get(1).unwrap().as_str().to_string(),
205 | a.get(2).unwrap().as_str().to_string(),
206 | )
207 | })
208 | .collect()
209 | } else {
210 | HashMap::new()
211 | };
212 | let get_arg =
213 | |name: &str| -> String { args.get(name).unwrap_or(&String::new()).clone() };
214 | match tag.as_str() {
215 | "face" => CQCode::Face(get_arg("id").parse::().unwrap_or(-1)),
216 | "emoji" => CQCode::Emoji(get_arg("id").parse::().unwrap_or(-1)),
217 | "bface" => CQCode::Bface(get_arg("id").parse::().unwrap_or(-1)),
218 | "sface" => CQCode::Sface(get_arg("id").parse::().unwrap_or(-1)),
219 | "image" => CQCode::Image(get_arg("file").to_owned()),
220 | "record" => CQCode::Record(get_arg("file").to_owned(), get_arg("magic") == "true"),
221 | "at" => {
222 | if get_arg("qq") == "all" {
223 | CQCode::AtAll()
224 | } else {
225 | CQCode::At(get_arg("qq").parse::().unwrap_or(-1))
226 | }
227 | }
228 | "rps" => CQCode::Sface(get_arg("type").parse::().unwrap_or(-1)),
229 | "shake" => CQCode::Shake(),
230 | "location" => CQCode::Location(
231 | get_arg("lat").parse::().unwrap_or(-1f32),
232 | get_arg("lon").parse::().unwrap_or(-1f32),
233 | get_arg("title").to_owned(),
234 | get_arg("content").to_owned(),
235 | ),
236 | "sign" => CQCode::Sign(
237 | get_arg("location").to_owned(),
238 | get_arg("title").to_owned(),
239 | get_arg("image").to_owned(),
240 | ),
241 | "share" => CQCode::Share(
242 | get_arg("url").to_owned(),
243 | get_arg("title").to_owned(),
244 | get_arg("content").to_owned(),
245 | get_arg("image").to_owned(),
246 | ),
247 | "contact" => CQCode::Contact(
248 | get_arg("id").parse::().unwrap_or(-1),
249 | get_arg("type").to_owned(),
250 | ),
251 | _ => CQCode::Unknown(codes.get(0).unwrap().as_str().to_owned()),
252 | }
253 | })
254 | .collect()
255 | }
256 |
--------------------------------------------------------------------------------
/src/targets/group.rs:
--------------------------------------------------------------------------------
1 | use std::{
2 | convert::TryInto,
3 | io::{Cursor, Result},
4 | };
5 |
6 | use byteorder::{BigEndian, ReadBytesExt};
7 |
8 | use crate::{
9 | api::{
10 | get_group_info, get_group_member_info_v2, get_group_member_list, send_group_msg,
11 | set_group_anonymous, set_group_ban, set_group_kick, set_group_whole_ban, Convert,
12 | },
13 | targets::{
14 | message::SendMessage,
15 | user::{Authority, UserSex},
16 | ReadString,
17 | },
18 | };
19 |
20 | #[derive(Debug, Clone)]
21 | pub enum GroupRole {
22 | Member,
23 | Admin,
24 | Owner,
25 | }
26 |
27 | impl From for GroupRole {
28 | fn from(i: i32) -> Self {
29 | match i {
30 | 1 => GroupRole::Member,
31 | 2 => GroupRole::Admin,
32 | 3 => GroupRole::Owner,
33 | _ => GroupRole::Member,
34 | }
35 | }
36 | }
37 |
38 | #[derive(Debug, Clone)]
39 | pub struct GroupMember {
40 | pub group_id: i64,
41 | pub user_id: i64,
42 | pub nickname: String,
43 | pub card: String,
44 | pub sex: UserSex,
45 | pub age: i32,
46 | pub area: String,
47 | pub join_time: i32,
48 | pub last_sent_time: i32,
49 | pub level: String,
50 | pub role: GroupRole,
51 | pub unfriendly: bool,
52 | pub title: String,
53 | pub title_expire_time: i32,
54 | pub card_changeable: bool,
55 | pub authority: Authority,
56 | }
57 |
58 | impl SendMessage for GroupMember {
59 | fn send(&self, msg: impl ToString) -> crate::api::Result> {
60 | send_group_msg(self.group_id, msg.to_string())
61 | }
62 | }
63 |
64 | impl GroupMember {
65 | pub(crate) fn decode(b: &[u8]) -> std::io::Result {
66 | let mut b = Cursor::new(base64::decode(&b).expect("Invalid base64 - decode GroupMember"));
67 | let mut gm = GroupMember {
68 | group_id: b.read_i64::()?,
69 | user_id: b.read_i64::()?,
70 | nickname: b.read_string()?,
71 | card: b.read_string()?,
72 | sex: UserSex::from(b.read_i32::()?),
73 | age: b.read_i32::()?,
74 | area: b.read_string()?,
75 | join_time: b.read_i32::()?,
76 | last_sent_time: b.read_i32::()?,
77 | level: b.read_string()?,
78 | role: GroupRole::from(b.read_i32::()?),
79 | unfriendly: b.read_i32::()? > 0,
80 | title: b.read_string()?,
81 | title_expire_time: b.read_i32::()?,
82 | card_changeable: b.read_i32::()? > 0,
83 | authority: Authority::User,
84 | };
85 | gm.authority = Authority::from_group_member(&gm);
86 | Ok(gm)
87 | }
88 | }
89 |
90 | #[derive(Debug, Default, Clone)]
91 | pub struct Group {
92 | pub group_id: i64,
93 | pub group_name: String,
94 | pub member_count: i32,
95 | pub max_member_count: i32,
96 | }
97 |
98 | impl SendMessage for Group {
99 | fn send(&self, msg: impl ToString) -> crate::api::Result> {
100 | send_group_msg(self.group_id, msg.to_string())
101 | }
102 | }
103 |
104 | impl Group {
105 | pub fn new(group_id: i64) -> Group {
106 | if let Ok(c) = get_group_info(group_id, false) {
107 | c.try_into().expect("cannot decode group")
108 | } else {
109 | let mut group = Group::default();
110 | group.group_id = group_id;
111 | group
112 | }
113 | }
114 |
115 | /// 部分参数如 area、title 等等无法获取到(为空)。要获取全部参数请使用 get_member。
116 | pub fn get_members(&self) -> crate::api::Result> {
117 | Ok(get_group_member_list(self.group_id)?
118 | .try_into()
119 | .expect("cannot decode GroupMember list"))
120 | }
121 |
122 | pub fn get_member(&self, user_id: i64) -> crate::api::Result {
123 | Ok(get_group_member_info_v2(self.group_id, user_id, false)?
124 | .try_into()
125 | .expect("cannot decode GroupMember"))
126 | }
127 |
128 | pub fn set_can_anonymous(&self, enable: bool) -> crate::api::Result> {
129 | set_group_anonymous(self.group_id, enable)
130 | }
131 |
132 | pub fn set_whole_ban(&self, enable: bool) -> crate::api::Result> {
133 | set_group_whole_ban(self.group_id, enable)
134 | }
135 |
136 | pub fn set_ban(&self, user_id: i64, time: i64) -> crate::api::Result> {
137 | set_group_ban(self.group_id, user_id, time)
138 | }
139 |
140 | pub fn set_kick(&self, user_id: i64, refuse_rejoin: bool) -> crate::api::Result> {
141 | set_group_kick(self.group_id, user_id, refuse_rejoin)
142 | }
143 |
144 | pub fn update(&mut self) -> crate::api::Result {
145 | Ok(get_group_info(self.group_id, true)?
146 | .try_into()
147 | .expect("cannot decode Group"))
148 | }
149 |
150 | /// 用于get_group_list
151 | /// 没有群人数信息
152 | pub(crate) fn decode_small(b: &[u8]) -> Result {
153 | let mut b = Cursor::new(b);
154 | Ok(Group {
155 | group_id: b.read_i64::()?,
156 | group_name: b.read_string()?,
157 | ..Default::default()
158 | })
159 | }
160 |
161 | pub(crate) fn decode(b: &[u8]) -> Result {
162 | let mut b = Cursor::new(base64::decode(&b).unwrap());
163 | Ok(Group {
164 | group_id: b.read_i64::()?,
165 | group_name: b.read_string()?,
166 | member_count: b.read_i32::()?,
167 | max_member_count: b.read_i32::()?,
168 | })
169 | }
170 | }
171 |
--------------------------------------------------------------------------------
/src/targets/message.rs:
--------------------------------------------------------------------------------
1 | use std::fmt::{Debug, Display, Error, Formatter};
2 |
3 | use crate::{
4 | api::{delete_msg, Convert, Result},
5 | targets::{cqcode, cqcode::CQCode},
6 | };
7 |
8 | #[derive(Debug, Default, Clone)]
9 | pub struct Message {
10 | pub msg: String,
11 | msg_id: i32,
12 | pub raw_msg: String,
13 | pub cqcodes: Vec,
14 | }
15 |
16 | impl Message {
17 | pub fn new(msg: impl Into>, msg_id: i32) -> Self {
18 | let msg = msg.into().to_string();
19 | Message {
20 | msg: Self::escape(cqcode::clean(msg.as_ref())),
21 | cqcodes: cqcode::parse(msg.as_ref()),
22 | raw_msg: msg,
23 | msg_id,
24 | }
25 | }
26 |
27 | /// 撤回消息
28 | pub fn delete(&self) -> bool {
29 | delete_msg(self.msg_id).is_ok()
30 | }
31 |
32 | pub fn has_cqcode(&self) -> bool {
33 | self.cqcodes.iter().count() != 0
34 | }
35 |
36 | // 将因为防止与cq码混淆而转义的字符还原
37 | fn escape(s: String) -> String {
38 | s.replace("&", "&")
39 | .replace("[", "[")
40 | .replace("]", "]")
41 | .replace(",", ",")
42 | }
43 | }
44 |
45 | /// Examples:
46 | /// ```
47 | /// use coolq_sdk_rust::targets::message::MessageSegment;
48 | ///
49 | /// let msg = MessageSegment::new()
50 | /// .add("hi")
51 | /// .newline()
52 | /// .face(10);
53 | ///
54 | /// // xx.send_message(msg);
55 | /// // api::send_private_msg(qq, msg);
56 | /// // event.reply(msg);
57 | /// ```
58 | #[derive(Clone)]
59 | pub struct MessageSegment(String);
60 |
61 | impl MessageSegment {
62 | pub fn new() -> Self {
63 | MessageSegment(String::new())
64 | }
65 |
66 | pub fn add(&mut self, msg: impl ToString) -> &mut Self {
67 | self.0.push_str(msg.to_string().as_ref());
68 | self
69 | }
70 |
71 | pub fn at_all(&mut self) -> &mut Self {
72 | self.add(CQCode::AtAll())
73 | }
74 |
75 | pub fn at(&mut self, user_id: i64) -> &mut Self {
76 | self.add(CQCode::At(user_id))
77 | }
78 |
79 | pub fn face(&mut self, face_id: i32) -> &mut Self {
80 | self.add(CQCode::Face(face_id))
81 | }
82 |
83 | pub fn bface(&mut self, bface_id: i32) -> &mut Self {
84 | self.add(CQCode::Bface(bface_id))
85 | }
86 |
87 | pub fn sface(&mut self, sface_id: i32) -> &mut Self {
88 | self.add(CQCode::Sface(sface_id))
89 | }
90 |
91 | pub fn emoji(&mut self, emoji_id: i32) -> &mut Self {
92 | self.add(CQCode::Emoji(emoji_id))
93 | }
94 |
95 | pub fn newline(&mut self) -> &mut Self {
96 | self.add("\n")
97 | }
98 |
99 | pub fn newlines(&mut self, count: usize) -> &mut Self {
100 | self.add("\n".repeat(count))
101 | }
102 | }
103 |
104 | impl Display for MessageSegment {
105 | fn fmt(&self, f: &mut Formatter<'_>) -> std::result::Result<(), Error> {
106 | write!(f, "{}", self.0)
107 | }
108 | }
109 |
110 | impl Debug for MessageSegment {
111 | fn fmt(&self, f: &mut Formatter<'_>) -> std::result::Result<(), Error> {
112 | write!(f, "{}", self.0)
113 | }
114 | }
115 |
116 | pub trait SendMessage {
117 | /// `@return` msg id
118 | fn send_message(&self, msg: impl ToString) -> Result {
119 | match self.send(msg) {
120 | Ok(msg_id) => Ok(msg_id.into()),
121 | Err(err) => Err(err),
122 | }
123 | }
124 |
125 | fn send(&self, msg: impl ToString) -> crate::api::Result>;
126 |
127 | /// type参数暂不支持
128 | fn send_rps(&self) -> Result {
129 | self.send_message(CQCode::Rps(0))
130 | }
131 |
132 | /// type参数暂不支持
133 | fn send_dice(&self) -> Result {
134 | self.send_message(CQCode::Dice(0))
135 | }
136 |
137 | fn send_shake(&self) -> Result {
138 | self.send_message(CQCode::Shake())
139 | }
140 |
141 | fn send_anonymous(&self, ignore: bool, msg: impl ToString) -> Result {
142 | self.send_message(
143 | MessageSegment::new()
144 | .add(CQCode::Anonymous(ignore))
145 | .add(msg),
146 | )
147 | }
148 |
149 | fn send_location(
150 | &self, latitude: f32, longitude: f32, title: &str, content: &str,
151 | ) -> Result {
152 | self.send_message(CQCode::Location(
153 | latitude,
154 | longitude,
155 | title.to_owned(),
156 | content.to_owned(),
157 | ))
158 | }
159 |
160 | fn send_music(&self, _type: &str, id: i32, style: i32) -> Result {
161 | self.send_message(CQCode::Music(_type.to_owned(), id, style))
162 | }
163 |
164 | fn send_music_custom(
165 | &self, url: &str, audio: &str, title: &str, content: &str, image: &str,
166 | ) -> Result {
167 | self.send_message(CQCode::MusicCustom(
168 | url.to_owned(),
169 | audio.to_owned(),
170 | title.to_owned(),
171 | content.to_owned(),
172 | image.to_owned(),
173 | ))
174 | }
175 |
176 | fn send_share(&self, url: &str, title: &str, content: &str, image: &str) -> Result {
177 | self.send_message(CQCode::Share(
178 | url.to_owned(),
179 | title.to_owned(),
180 | content.to_owned(),
181 | image.to_owned(),
182 | ))
183 | }
184 |
185 | fn at(&self, user_id: i64, msg: impl ToString) -> Result {
186 | self.send_message(MessageSegment::new().add(CQCode::At(user_id)).add(msg))
187 | }
188 | }
189 |
--------------------------------------------------------------------------------
/src/targets/mod.rs:
--------------------------------------------------------------------------------
1 | use std::io::{Cursor, Read, Result as IOResult};
2 |
3 | use byteorder::{BigEndian, ReadBytesExt};
4 |
5 | use crate::{
6 | api::{set_group_anonymous_ban, Convert, Flag},
7 | iconv::IconvDecodable,
8 | };
9 |
10 | pub mod cqcode;
11 | pub mod message;
12 |
13 | pub mod group;
14 | pub mod user;
15 |
16 | pub(crate) fn read_multi_object(b: &[u8]) -> IOResult>> {
17 | let mut b = Cursor::new(base64::decode(&b).expect("Invalid base64 - read_multi_object"));
18 | let count = b.read_i32::()?;
19 | let mut vs = Vec::new();
20 | for _ in 0..count {
21 | let mut v = vec![0u8; b.read_i16::()? as usize];
22 | b.read_exact(&mut v)?;
23 | vs.push(v);
24 | }
25 | Ok(vs)
26 | }
27 |
28 | pub trait ReadString: Read {
29 | fn read_string(&mut self) -> IOResult {
30 | let len = self.read_i16::()?;
31 | if len > 0 {
32 | let mut v = vec![0u8; len as usize];
33 | self.read_exact(&mut v)?;
34 | Ok(v.decode_with_encoding("GB18030").unwrap())
35 | } else {
36 | Ok(String::new())
37 | }
38 | }
39 | }
40 |
41 | impl ReadString for R {}
42 |
43 | #[derive(Debug, Clone)]
44 | pub struct File {
45 | pub id: String,
46 | pub name: String,
47 | pub size: i64,
48 | pub busid: i64,
49 | }
50 |
51 | impl File {
52 | pub(crate) fn decode(b: &[u8]) -> IOResult {
53 | let mut b = Cursor::new(base64::decode(&b).expect("Invalid base64 - decode File"));
54 | Ok(File {
55 | id: b.read_string()?,
56 | name: b.read_string()?,
57 | size: b.read_i64::()?,
58 | busid: b.read_i64::()?,
59 | })
60 | }
61 | }
62 |
63 | #[derive(Debug, Default, Clone)]
64 | pub struct Anonymous {
65 | pub group_id: i64,
66 | pub user_id: i64,
67 | pub name: String,
68 | pub flag: Flag,
69 | }
70 |
71 | impl Anonymous {
72 | pub fn ban(&self, time: i64) -> crate::api::Result> {
73 | set_group_anonymous_ban(self.group_id, self.flag.clone(), time)
74 | }
75 |
76 | pub(crate) fn decode(b: &[u8], group_id: i64) -> IOResult {
77 | let mut c = Cursor::new(base64::decode(&b).expect("Invalid base64 - decode Anonymous"));
78 | Ok(Anonymous {
79 | group_id: group_id,
80 | user_id: c.read_i64::()?,
81 | name: c.read_string()?,
82 | flag: unsafe { String::from_utf8_unchecked(b.to_vec()) },
83 | })
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/src/targets/user.rs:
--------------------------------------------------------------------------------
1 | //! 在获取陌生人信息和好友列表时使用
2 | //!
3 | //!
4 | //! 权限分组请看[Authority]。算是一个小小的权限管理吧
5 | //!
6 | //! 使用[`add_master`]和[`add_super_admin`]来添加主人和管理员。
7 | //!
8 | //! 使用[`check_authority`]来检查用户权限。
9 | //!
10 | //! [Authorit]: Authority
11 | //! [`add_master`]: User::add_master
12 | //! [`add_super_admin`]: User::add_super_admin
13 | //! [`check_authority`]: Authority::check_authority
14 |
15 | use std::{convert::TryInto, io::Cursor, sync::RwLock};
16 |
17 | use byteorder::{BigEndian, ReadBytesExt};
18 |
19 | use crate::{
20 | api::{get_stranger_info, send_private_msg, Convert},
21 | targets::{
22 | group::{GroupMember, GroupRole},
23 | message::SendMessage,
24 | ReadString,
25 | },
26 | };
27 |
28 | lazy_static! {
29 | static ref MasterList: RwLock> = RwLock::new(Vec::new());
30 | static ref SuperAdminList: RwLock> = RwLock::new(Vec::new());
31 | }
32 |
33 | #[derive(Debug, Clone)]
34 | pub enum UserSex {
35 | Male,
36 | Female,
37 | Unknown,
38 | }
39 |
40 | impl From for UserSex {
41 | fn from(i: i32) -> Self {
42 | match i {
43 | 0 => UserSex::Male,
44 | 1 => UserSex::Female,
45 | _ => UserSex::Unknown,
46 | }
47 | }
48 | }
49 |
50 | impl Default for UserSex {
51 | fn default() -> Self {
52 | UserSex::Unknown
53 | }
54 | }
55 |
56 | #[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Copy)]
57 | pub enum Authority {
58 | Master = 0,
59 | SuperAdmin = 1,
60 | GroupOwner = 2,
61 | GroupAdmin = 3,
62 | User = 4,
63 | }
64 |
65 | impl Authority {
66 | pub fn check_authority(&self, authority: Authority) -> bool {
67 | self <= &authority
68 | }
69 |
70 | pub fn new(id: i64) -> Authority {
71 | if !SuperAdminList
72 | .read()
73 | .expect("cannot read SuperAdminList")
74 | .iter()
75 | .all(|qq| *qq != id)
76 | {
77 | Authority::SuperAdmin
78 | } else if !MasterList
79 | .read()
80 | .expect("cannot read MasterList")
81 | .iter()
82 | .all(|qq| *qq != id)
83 | {
84 | Authority::Master
85 | } else {
86 | Authority::User
87 | }
88 | }
89 |
90 | pub(crate) fn from_group_member(gm: &GroupMember) -> Authority {
91 | let authority = Authority::new(gm.user_id);
92 | if authority != Authority::User {
93 | authority
94 | } else {
95 | match gm.role {
96 | GroupRole::Member => Authority::User,
97 | GroupRole::Admin => Authority::GroupAdmin,
98 | GroupRole::Owner => Authority::GroupOwner,
99 | }
100 | }
101 | }
102 | }
103 |
104 | impl Default for Authority {
105 | fn default() -> Self {
106 | Authority::User
107 | }
108 | }
109 |
110 | /// get_friend_list
111 | #[derive(Debug, Clone)]
112 | pub struct FriendInfo {
113 | pub user_id: i64,
114 | pub nickname: String,
115 | pub remark: String,
116 | }
117 |
118 | impl FriendInfo {
119 | pub(crate) fn decode(b: &[u8]) -> std::io::Result {
120 | let mut b = Cursor::new(&b);
121 | Ok(FriendInfo {
122 | user_id: b.read_i64::()?,
123 | nickname: b.read_string()?,
124 | remark: b.read_string()?,
125 | })
126 | }
127 | }
128 |
129 | #[derive(Debug, Clone, Default)]
130 | pub struct User {
131 | pub user_id: i64,
132 | pub nickname: String,
133 | pub sex: UserSex,
134 | pub age: i32,
135 | pub authority: Authority,
136 | }
137 |
138 | impl SendMessage for User {
139 | fn send(&self, msg: impl ToString) -> crate::api::Result> {
140 | send_private_msg(self.user_id, msg.to_string())
141 | }
142 | }
143 |
144 | impl User {
145 | pub fn add_master(user_id: i64) {
146 | MasterList.write().unwrap().push(user_id);
147 | }
148 |
149 | pub fn add_super_admin(user_id: i64) {
150 | SuperAdminList.write().unwrap().push(user_id);
151 | }
152 |
153 | pub fn get_masters() -> Vec {
154 | MasterList.read().unwrap().clone()
155 | }
156 |
157 | pub fn get_super_admins() -> Vec {
158 | SuperAdminList.read().unwrap().clone()
159 | }
160 |
161 | //为了防止获取频率过大,所有从事件获取到的User皆是从缓存取的。
162 | //如果想获得最新信息,请使用update。
163 | pub(crate) fn new(user_id: i64) -> User {
164 | let mut user: User = if let Ok(c) = get_stranger_info(user_id, false) {
165 | c.try_into().expect("cannot decode User")
166 | } else {
167 | Default::default()
168 | };
169 | user.user_id = user_id;
170 | user.set_authority(Authority::new(user_id));
171 | user
172 | }
173 |
174 | pub(crate) fn set_authority(&mut self, authority: Authority) {
175 | if !self.authority.check_authority(authority) {
176 | self.authority = authority
177 | }
178 | }
179 |
180 | pub fn update(&mut self) -> crate::api::Result {
181 | Ok(get_stranger_info(self.user_id, true)?
182 | .try_into()
183 | .expect("cannot decode User"))
184 | }
185 |
186 | pub(crate) fn decode(b: &[u8]) -> std::io::Result {
187 | let mut b = Cursor::new(base64::decode(&b).expect("Invalid base64 - decode User"));
188 | Ok(User {
189 | user_id: b.read_i64::()?,
190 | nickname: b.read_string()?,
191 | sex: UserSex::from(b.read_i32::()?),
192 | age: b.read_i32::()?,
193 | authority: Authority::User,
194 | })
195 | }
196 | }
197 |
--------------------------------------------------------------------------------
/tests/test_bench.rs:
--------------------------------------------------------------------------------
1 | #![feature(test)]
2 |
3 | use test::Bencher;
4 |
5 | extern crate test;
6 |
7 | #[bench]
8 | fn bench_escape_cqcode(b: &mut Bencher) {
9 | b.iter(|| {
10 | for _ in 0..10000 {
11 | escape(String::from(test::black_box("[CQ:at,qq=1230]")));
12 | }
13 | })
14 | }
15 |
16 | #[test]
17 | fn test() {
18 | dbg!(escape("[CQ:at,qq=1230]".to_string()));
19 | }
20 |
21 | fn escape(s: String) -> String {
22 | s.replace("&", "&")
23 | .replace("[", "[")
24 | .replace("]", "]")
25 | .replace(",", ",")
26 | }
27 |
--------------------------------------------------------------------------------