232 Commits
Author SHA1 Message Date
RTAkland a134406764 Remove 2025-05-22 01:11:36 +08:00
RTAkland 93a2bf949a Fix nslookup 2025-05-22 01:08:19 +08:00
RTAkland 11e65d99ba Update 2025-05-16 02:36:37 +08:00
RTAkland c049fb1d8a Upgrade rob 2025-05-06 02:42:19 +08:00
RTAkland bfbba46d0a fix: fix niuzi manager command 2025-03-17 08:29:15 +08:00
RTAkland f6f83e2454 update rob version 2025-02-21 18:35:14 +08:00
RTAkland cb5b989b38 feat: add mrrhr image generate 2025-02-21 17:38:54 +08:00
RTAkland 586714d704 remove unused func 2025-02-20 19:36:24 +08:00
RTAkland 510fdc0c60 faet: add good news command 2025-02-20 19:19:18 +08:00
RTAkland d68753df98 feat: add today somke 2025-02-18 22:18:16 +08:00
RTAkland 5852ceb5ba feat: add osu pp query 2025-02-06 22:39:24 +08:00
RTAkland b7db3b3e98 chore: update osu command progress style 2025-02-05 13:28:27 +08:00
RTAkland 6bce847a0f feat: add osu command 2025-02-05 13:24:18 +08:00
RTAkland 24c8ef0cb7 fix 2025-01-18 19:27:37 +08:00
RTAkland 411b468e5e fix 2025-01-18 19:14:09 +08:00
RTAkland 5060afd43e adjust font position 2025-01-15 16:23:24 +08:00
RTAkland 5e4d4e4bfc add sh command 2025-01-15 16:21:41 +08:00
RTAkland d406e74659 add sj command 2025-01-15 16:21:24 +08:00
RTAkland a6b8698a68 add xnm command 2025-01-15 14:06:02 +08:00
RTAkland 4be2791c92 add command alia 2025-01-15 13:51:34 +08:00
RTAkland cc0c0f4022 chore: adjust font size 2025-01-15 13:46:56 +08:00
RTAkland 8589951cc4 feat: add tjbgxl command 2025-01-15 13:45:54 +08:00
RTAkland 7f199959f1 feat: add nwwn command 2025-01-15 00:27:37 +08:00
RTAkland 5584b18f2f feat: add nwwn command 2025-01-15 00:24:50 +08:00
RTAkland 64f256fb25 feat: add nwwn command 2025-01-15 00:23:31 +08:00
RTAkland 8107117e5f update maven repo 2025-01-12 16:45:50 +08:00
RTAkland 59105331fb fix 2025-01-03 19:52:05 +08:00
RTAkland c1172b8867 fix: remove genshin reply and fix bug 2025-01-03 19:10:30 +08:00
RTAkland f925a77cdb bump ronebot 2024-12-17 18:13:23 +08:00
RTAkland c376612d77 chore: update maven repository 2024-12-17 13:40:07 +08:00
RTAkland 8b90790395 feat: add split frame and upgrade ronebot to 2.5.2 2024-11-14 20:09:15 +08:00
RTAkland ca304874dc chore: format code 2024-11-09 20:55:14 +08:00
RTAkland 56270771b0 chore: upgrade ronebot to 2.4.8 use space split command 2024-11-09 20:45:32 +08:00
RTAkland 12064dfe10 feat: upgrade ronebot to 2.4.7, now it can send command without space 2024-11-09 19:05:13 +08:00
RTAkland 6b5ca9c38b chore: format code 2024-11-09 13:22:12 +08:00
RTAkland 5b8de20430 chore: upgrade ronebot to 2.4.5 2024-11-08 20:59:51 +08:00
RTAkland a2bdcc5232 chore: upgrade ronebot to 2.4.4 2024-11-08 20:33:55 +08:00
RTAkland 9838d90855 chore: remove nailong dectect 2024-11-08 12:22:25 +08:00
RTAkland 43b3e96881 chore: upgrade ronebot to 2.4.3 2024-11-08 12:20:13 +08:00
RTAkland be03a90d72 chore: upgrade ronebot to 2.4.2 2024-11-07 19:44:07 +08:00
RTAkland 159f83a89f chore: upgrade ronebot to 2.4.1 2024-11-07 19:09:08 +08:00
RTAkland 760aeeca82 chore: remove sensitive word filter 2024-11-07 18:34:59 +08:00
RTAkland 42c215420a feat: add command execution times counter 2024-11-07 17:07:05 +08:00
RTAkland 5320836d26 feat: add sensitive word filter 2024-11-07 17:02:49 +08:00
RTAkland 4117c3d253 chore: upgrade ronebot to 2.3.6 2024-11-07 15:24:11 +08:00
RTAkland 9a018c0bdd chore: upgrade gradle to 8.10.2 2024-11-04 19:04:46 +08:00
RTAkland 8dee66d022 chore: upgrade ronebot 2024-11-04 18:45:10 +08:00
RTAkland b3410e6d07 feat: add ai tts command 2024-11-03 20:15:53 +08:00
RTAkland 7935597a05 chore: upgrade ronebot 2024-11-03 15:35:31 +08:00
RTAkland 217e465d62 chore: reduce jar file size and unsupport sqlite/postgresql/mysql 2024-11-02 19:19:57 +08:00
RTAkland 1cd2242c6f chore: remove repeater 2024-11-01 19:06:05 +08:00
RTAkland 229c73b6ac fix: while repeat 2024-11-01 18:55:09 +08:00
RTAkland 4be023282e fix: fix repeater 2024-11-01 17:21:49 +08:00
RTAkland 8541358348 feat: add repeater 2024-11-01 17:12:53 +08:00
RTAkland 81e2169c1e chore: remove unused maven repo 2024-11-01 09:38:52 +08:00
RTAkland fac2d5491a feat: add more feature 2024-10-31 21:32:42 +08:00
RTAkland 2c0ca2f739 chore: bump rob 2024-10-31 14:02:04 +08:00
RTAkland b85b0bf33a feat: add help help 2024-10-30 18:14:26 +08:00
RTAkland 321a82e599 chore: bump rob 2024-10-30 15:33:38 +08:00
RTAkland 8c7227c77d chore: bump rob 2024-10-30 12:05:03 +08:00
RTAkland 82c1f4f89d chore: bumo rob 2024-10-29 19:32:38 +08:00
RTAkland 9af2916dfc feat: support greyscale a gif 2024-10-29 11:47:25 +08:00
RTAkland 40d4661e1b chore: bump rob 2024-10-28 19:09:19 +08:00
RTAkland b1405ad3e8 chore: bump rob 2024-10-28 12:32:51 +08:00
RTAkland edfbb820a0 chore: remove double reply 2024-10-28 11:26:44 +08:00
RTAkland c139c09b1a fix: fix reaction target 2024-10-27 18:42:30 +08:00
RTAkland 9ee543b1e1 feat: add greyscale command 2024-10-27 18:25:34 +08:00
RTAkland d45ab4e286 ci: remove code line count 2024-10-27 18:20:25 +08:00
RTAkland ad12ff5ce8 feat: add get avatar command 2024-10-27 18:19:32 +08:00
RTAkland f9b52c8c4b chore: add command prefix 2024-10-27 18:01:54 +08:00
RTAkland a99f636158 fix: fix speed up will reverse gif 2024-10-27 18:00:34 +08:00
RTAkland c0fa5c3a49 feat: add gif speed up and speed down command 2024-10-27 17:43:06 +08:00
RTAkland c3dc3fa4e5 chore: bump rob 2024-10-27 11:02:06 +08:00
RTAkland 7e851e831a feat: add nailong detect 2024-10-26 11:46:32 +08:00
RTAkland b238ec5f30 chore: bump rob version 2024-10-25 19:44:33 +08:00
RTAkland b0446d9baf ci 2024-10-25 17:50:36 +08:00
RTAkland 7994861348 ci 2024-10-25 17:32:52 +08:00
RTAkland 64c4fd04b1 chore: format code 2024-10-25 14:26:45 +08:00
RTAkland 2115f3d63a fix: fix music command not work and bump ronebot version 2024-10-25 14:25:43 +08:00
RTAkland c1ba1cd5b1 fix: bump rob to fix command pattern 2024-10-24 21:29:40 +08:00
RTAkland b1f13b6754 chore: bump rob 2024-10-24 19:27:06 +08:00
RTAkland 41463b8b18 feat: migrate to multi-instance ronebot 2024-10-24 16:55:14 +08:00
RTAkland bc28a4fefc chore: bump rob version 2024-10-23 20:31:45 +08:00
RTAkland f0d92d8fe7 feat: add http dog command 2024-10-23 18:40:47 +08:00
RTAkland 184378c907 chore: move http cat command to misc folder 2024-10-23 15:15:26 +08:00
RTAkland fc864529b5 chore: use local image file instaed of api 2024-10-23 15:12:14 +08:00
RTAkland 4e0cb17f4e feat: add custom http cat 2024-10-23 15:07:43 +08:00
RTAkland 2ce9d3dc8f feat: add random http cat 2024-10-23 13:45:44 +08:00
RTAkland f48b2ea8b3 feat: support ping srv record mc server 2024-10-23 13:32:46 +08:00
RTAkland 57d0737f21 feat: add ip geo 2024-10-23 13:29:56 +08:00
RTAkland 0d73a29d34 feat: add qq image bed 2024-10-22 20:31:09 +08:00
RTAkland 7511dfd37f chore: bump rob version 2024-10-22 18:24:49 +08:00
RTAkland 3eabe254af fix 2024-10-21 20:27:14 +08:00
RTAkland 97cd846b53 feat: add srv record lookup 2024-10-21 17:58:11 +08:00
RTAkland d8b4f5003b chore: update ts language color 2024-10-21 13:45:04 +08:00
RTAkland ac96431552 chore: format code 2024-10-19 19:29:53 +08:00
RTAkland 8fd92b8fed chore: use user role instead of admin list 2024-10-19 19:29:03 +08:00
RTAkland 9381ef8637 chore: bump ronebot version 2024-10-19 18:46:08 +08:00
RTAkland b64f3580d0 chore: bump ronebot 2024-10-19 14:57:57 +08:00
RTAkland 3fba603b17 chore: at sender 2024-10-19 10:43:10 +08:00
RTAkland 492f36d6a5 nothing 2024-10-19 10:06:07 +08:00
RTAkland 10a61fbee7 chore: remove bot limit 2024-10-18 18:23:18 +08:00
RTAkland b0d295d97f feat: add max player tip 2024-10-18 17:38:04 +08:00
RTAkland f5d6a3acb7 chore: remove dnsjava 2024-10-18 13:22:23 +08:00
RTAkland f5cc673836 chore: move to version catalog 2024-10-18 13:19:49 +08:00
RTAkland 838d95391e feat: improve mcbot command 2024-10-18 11:38:45 +08:00
RTAkland 8999e8912a chore: migrate to version catalog 2024-10-18 01:11:59 +08:00
RTAkland 5d3a31dd28 chore: format code 2024-10-18 01:03:38 +08:00
RTAkland 28cf80c271 feat: add mc bot command 2024-10-18 00:56:37 +08:00
RTAkland b3c3e230bf feat: support reverse gif using reply 2024-10-17 22:16:27 +08:00
RTAkland 1b7f09732f feat: add today dont eat/drink command 2024-10-17 21:36:45 +08:00
RTAkland e5b8b42d7a chore: bump rob 2024-10-17 21:26:40 +08:00
RTAkland f22934c325 fix static image not make short url 2024-10-17 15:54:59 +08:00
RTAkland 9a7d3af844 chore: rename 2024-10-17 15:32:12 +08:00
RTAkland bbb552dc14 chore: format code 2024-10-17 13:02:19 +08:00
RTAkland 2ac27d29e7 feat: support mysql and postgresql database 2024-10-17 12:51:20 +08:00
RTAkland 087d55ba28 chore: remove unused import 2024-10-15 19:08:15 +08:00
RTAkland fbcdbe301a feat: add shuffled echo command 2024-10-15 19:07:18 +08:00
RTAkland 6b4ef38443 chore: format code 2024-10-15 18:58:12 +08:00
RTAkland c4e6f9e348 chore: format number 2024-10-15 15:49:56 +08:00
RTAkland a59a544792 fix: fix cover 2024-10-15 15:47:48 +08:00
RTAkland c2e1ec91b5 feat: support parse youtube video 2024-10-15 15:40:33 +08:00
RTAkland 88fd259712 chore: remove message reaction 2024-10-15 15:16:52 +08:00
RTAkland ae3eae4e71 chore: add command usage tip 2024-10-15 13:59:11 +08:00
RTAkland ac88dcc296 chore: use short api 2024-10-15 11:13:54 +08:00
RTAkland 7f3b1fd1b5 feat: auto reaction admin 2024-10-14 23:17:53 +08:00
RTAkland e375f20e9c feat: support gen short link and create pastebin using reply 2024-10-14 22:41:01 +08:00
RTAkland bfbeee2880 chore: add fail fallback exception catch 2024-10-14 19:24:45 +08:00
RTAkland c4d8c0eaf0 feat: add pastebin generate action log 2024-10-14 17:43:26 +08:00
RTAkland f96f7b7055 fix: fix bili video parse error and add pastebin command 2024-10-14 17:40:04 +08:00
RTAkland 6f32323fec feat: add gravatar command 2024-10-14 17:26:59 +08:00
RTAkland 73b417775a feat: add rcon command 2024-10-13 14:16:00 +08:00
RTAkland c9d2ddeff7 fix: fix wrong avatar and image scale 2024-10-13 12:20:37 +08:00
RTAkland f0e6f31faf chore: format code 2024-10-13 11:58:31 +08:00
RTAkland a78a365ebd feat: support parse douyin video 2024-10-13 11:57:56 +08:00
RTAkland d036c2bdba chore: add image expired exception catch 2024-10-13 10:55:08 +08:00
RTAkland c831a76850 chore: split image bed command to single file 2024-10-13 10:52:37 +08:00
RTAkland ec6e186284 fix: fix sign out of image 2024-10-12 21:04:11 +08:00
RTAkland 95355d92fe feat: support generate ascii art with reply 2024-10-12 20:59:31 +08:00
RTAkland ff65333a8f feat: support parse bilibili user 2024-10-12 18:35:16 +08:00
RTAkland 499eab9549 docs: update docs 2024-10-12 17:29:09 +08:00
RTAkland 242ce3e60e docs: update docs 2024-10-12 17:28:05 +08:00
RTAkland 83e3404665 chore: format code 2024-10-12 15:23:21 +08:00
RTAkland 6bc77fb4ac chore: add command description for mc login command 2024-10-11 20:50:26 +08:00
RTAkland 217364ffb8 feat: add mclogin command 2024-10-11 20:49:32 +08:00
RTAkland 2ac7d3b20b fix: fix message target 2024-10-11 19:36:22 +08:00
RTAkland 5ac05f052a feat: add reaction other's message command 2024-10-11 19:28:22 +08:00
RTAkland 1b1a579f23 chore: add command description annotation 2024-10-11 19:24:33 +08:00
RTAkland 1aab83ae51 feat: complete today's eat and drink commands 2024-10-11 19:10:06 +08:00
RTAkland c428b84e10 chore: nothing 2024-10-11 19:03:42 +08:00
RTAkland e01a98d254 chore: nothing 2024-10-11 19:03:21 +08:00
RTAkland f783f8b4ec feat: add new command 2024-10-11 17:13:20 +08:00
RTAkland 1c3f582c86 feat: add black list manager to manage group who kicked the bot 2024-10-11 17:12:50 +08:00
RTAkland a0b74d3df4 feat: add niuzi manager to manage user's niuzi length and bank balance 2024-10-11 14:00:00 +08:00
RTAkland ccb72a7277 feat: add ascii art upload to image bed 2024-10-11 13:47:26 +08:00
RTAkland 7fda4dc333 chore: add api rtast key 2024-10-11 11:08:18 +08:00
RTAkland 68b7c60a4d chore: set r2 mime type 2024-10-10 19:47:27 +08:00
RTAkland e0e89d1ecb chore: add slash 2024-10-10 18:20:32 +08:00
RTAkland 6da5553d1f docs: update docs -> update jar size 2024-10-10 18:07:45 +08:00
RTAkland 90225ae147 feat: add idiom explain and add mcping latency 2024-10-10 16:03:51 +08:00
RTAkland 1c00093560 fix: fix wikitext parse 2024-10-10 14:59:09 +08:00
RTAkland 7b8120dbc5 chore: rename dep 2024-10-10 11:58:48 +08:00
RTAkland af6f580583 feat: add minecraft wiki content query 2024-10-10 11:57:53 +08:00
RTAkland ac7910d20c docs: update docs 2024-10-10 10:55:16 +08:00
RTAkland b8568c00e7 chore: format code 2024-10-10 10:42:15 +08:00
RTAkland 52c791d3ed feat: add gaokao days remain command 2024-10-09 23:28:57 +08:00
RTAkland f34e12b3d2 feat: add more commands about minecraft 2024-10-09 23:14:41 +08:00
RTAkland c280c02a4e chore: change default r2 public url 2024-10-09 21:05:04 +08:00
RTAkland 829f858840 feat: use cloudflare r2 oss instead of github repository 2024-10-09 20:52:24 +08:00
RTAkland fdad0054ef chore: use cloudflare kv instead of vercel kv 2024-10-09 18:35:28 +08:00
RTAkland cd69d9a18e feat: support get multi image get 2024-10-09 14:55:03 +08:00
RTAkland 44c622190d feat: add short url and origin url 2024-10-09 13:27:01 +08:00
RTAkland f9e6bac23d chore: add catch exception 2024-10-09 13:17:13 +08:00
RTAkland 06a385db1a feat: add image bed command 2024-10-09 13:06:43 +08:00
RTAkland b660b504fd chore: add less language color 2024-10-08 22:16:03 +08:00
RTAkland 8504533659 feat: add image bed api 2024-10-08 20:48:02 +08:00
RTAkland 92bf62347c chore: format size 2024-10-08 20:47:49 +08:00
RTAkland 8ae4ce7870 feat: add image bed api 2024-10-08 20:45:56 +08:00
RTAkland 54354e0a52 chore: add vb6.0 language color 2024-10-08 18:57:46 +08:00
RTAkland a80cbdcb1a chore: format code 2024-10-08 17:19:54 +08:00
RTAkland 9ff4f03b24 chore: use ext property instead of func 2024-10-08 17:06:01 +08:00
RTAkland 95a6abbfb3 chore: use proxy 2024-10-08 17:03:48 +08:00
RTAkland f62055d2ac fix: fix repo language null throw npr 2024-10-08 16:58:05 +08:00
RTAkland 9e198c0084 chore: suppress warning 2024-10-08 16:54:03 +08:00
RTAkland 7dda9ad4bc feat: add more language color 2024-10-08 16:49:45 +08:00
RTAkland 80557e9ccc feat: add the history of today command 2024-10-08 15:15:51 +08:00
RTAkland e4d6e683d3 feat: add the cat command 2024-10-08 15:08:27 +08:00
RTAkland 97e62d5422 chore: format code 2024-10-08 14:44:52 +08:00
RTAkland a850ab9152 chore: remove unused language color 2024-10-08 11:55:08 +08:00
RTAkland 33f4014bae feat: add more language color 2024-10-08 11:54:09 +08:00
RTAkland 327426988b chore: chore: chore 2024-10-07 20:44:26 +08:00
RTAkland 938ef0792a feat: add pay for manzi sub command 2024-10-07 20:43:03 +08:00
RTAkland af9a117561 chore: add more command aliases 2024-10-07 20:18:50 +08:00
RTAkland 0ad3314ddb feat: add buy me a coffee command(打钱) 2024-10-07 20:12:04 +08:00
RTAkland b0ae938722 chore: introduce logger and save log into file 2024-10-07 19:13:51 +08:00
RTAkland 91906d9711 feat: add slime chunk helper command 2024-10-07 17:01:04 +08:00
RTAkland b7f82446f9 chore: format code 2024-10-07 16:11:06 +08:00
RTAkland 69743ca42b feat: add tts command 2024-10-07 16:07:07 +08:00
RTAkland 35fd368cb6 chore: format code 2024-10-07 15:36:03 +08:00
RTAkland 63c1de608d chore: format code 2024-10-07 12:47:10 +08:00
RTAkland c6f1bf9629 fix: fix bv parse 2024-10-07 12:44:31 +08:00
RTAkland ed85575e14 feat: support av code parse and use regex to match bv and av code instead of string split 2024-10-07 12:16:51 +08:00
RTAkland 2b725ab71e chore: format code 2024-10-06 21:21:05 +08:00
RTAkland 0a5c9b63c2 feat: add scan qrcode command 2024-10-06 21:18:23 +08:00
RTAkland 262cfa532c chore: nothing 2024-10-06 20:48:50 +08:00
RTAkland 956b29d076 chore: use local json file instead of api 2024-10-06 18:20:53 +08:00
RTAkland 8f7a5e5f0e chore: move root path 2024-10-06 18:07:32 +08:00
RTAkland 04b6823d7e chore: control 2024-10-06 11:28:56 +08:00
RTAkland 6bb6979467 feat: add scheduler to send like 2024-10-05 20:59:30 +08:00
RTAkland d42fd55228 feat: support translate image to zdjd 2024-10-05 20:05:13 +08:00
RTAkland 88a513864a feat: add zdjd command 2024-10-05 19:40:44 +08:00
RTAkland 98a6ebb3d4 chore: rename command name 2024-10-05 19:14:45 +08:00
RTAkland 4078af7c29 chore: add another method for base64 util 2024-10-05 19:08:45 +08:00
RTAkland 05efdaebff chore: suppress deprecated class warning 2024-10-05 16:36:51 +08:00
RTAkland 9abbf2830a chore: nothing 2024-10-04 20:52:54 +08:00
RTAkland c9c0f8e360 chore: fix jupyter language color 2024-10-04 15:21:14 +08:00
RTAkland b82a895d99 feat: add more language color 2024-10-04 12:36:56 +08:00
RTAkland 5f0921410d bump rob to 1.12.7, simple-java-mail to 8.12.1 2024-10-04 12:15:24 +08:00
RTAkland 45fafd4a4d feat: add random song command 2024-10-03 17:15:12 +08:00
RTAkland d7d7e237e6 feat: add latest commmit info and add language name on github repo card 2024-10-03 16:59:07 +08:00
RTAkland a349687e4f chore: update github repo parser 2024-10-03 16:35:38 +08:00
RTAkland 385dcb4328 chore: update comment 2024-10-03 11:12:26 +08:00
RTAkland 02ab1e816f chore: add command description 2024-10-02 21:43:09 +08:00
RTAkland 93338a9d64 feat: add query mc skin command 2024-10-02 21:41:28 +08:00
RTAkland 3a033d2694 chore: use application plugin instead of MANIFEST attribute 2024-10-02 21:10:17 +08:00
RTAkland 57885473bd feat: add command execute record in db 2024-10-02 21:02:05 +08:00
RTAkland 767bd7c0f3 docs: update docs 2024-10-02 20:05:52 +08:00
RTAkland 4ac5d6603b chore: update compiler command 2024-10-02 18:32:47 +08:00
RTAkland 9ba1c135d8 chore: bump rob to 1.12.2 2024-10-02 15:37:18 +08:00
RTAkland a07560fa37 chore: reduce reaction count 2024-10-01 20:20:46 +08:00
RTAkland 2eb8a3e387 feat: add reaction 2024-10-01 20:18:19 +08:00
RTAkland 805c6d56c2 feat: add reaction command 2024-10-01 18:54:25 +08:00
324 changed files with 5249 additions and 1082 deletions

No files matched your search

-19
View File
@@ -1,19 +0,0 @@
name: Count Lines of Code
on:
push:
branches:
- main
jobs:
count-loc:
runs-on: ubuntu-latest
steps:
- name: Check out code
uses: actions/checkout@v3
- name: Install cloc
run: sudo apt-get install -y cloc
- name: Count lines of code
id: cloc
run: |
cloc . >> $GITHUB_STEP_SUMMARY
+19 -5
View File
@@ -15,13 +15,14 @@
# 概述 # 概述
> 这是一个使用了 `ROneBot` 框架的QQ机器人, 包含了20多种命令, 例如: 点歌, 今日人品, 签到... > 这是一个使用了 `ROneBot` 框架的QQ机器人, 包含了超多命令, 例如: 点歌, 今日人品, 签到...
> 你可以在[这里](src/main/kotlin/cn/rtast/fancybot/commands/)来查看所有的命令 > 你可以在[这里](src/main/kotlin/cn/rtast/fancybot/commands/)来查看所有的命令
> ROneBot项目地址: [Gitlab](https://repo.rtast.cn/RTAkland/ronebot) [Github](https://github.com/RTAkland/ROneBot) > ROneBot项目地址: [Gitlab](https://repo.rtast.cn/RTAkland/ronebot) [Github](https://github.com/RTAkland/ROneBot)
# 构建 # 构建
由于项目使用的依赖过多项目构建出的产物在28Mb左右并且项目开启了
由于项目使用的依赖过多项目构建出的产物在40+Mb左右并且项目开启了
Github Action来自动化部署机器人, github Action的作用仅用作判断 Github Action来自动化部署机器人, github Action的作用仅用作判断
构建是否成功以及自动部署机器人, 并不带有上传构建产物的功能(占用的空间太多) 构建是否成功以及自动部署机器人, 并不带有上传构建产物的功能(占用的空间太多)
所以你需要自行构建出机器人的jar包来运行 所以你需要自行构建出机器人的jar包来运行
@@ -33,10 +34,23 @@ $ ./gradlew build clean
> 产物在`build/libs/`目录下你可以找到`-all`结尾的jar包 > 产物在`build/libs/`目录下你可以找到`-all`结尾的jar包
# 一些想说的话 # 部署
OneBot协议本身并不是一个很好的协议, 在Kritor、Laana等新型协议完善后机器人会全面迁移到上述协议中 > 部署并不是一件很难的事, 只是需要准备的前期工作有点多
并且将OneBot版本的分支放置到另外一个分支中不再更新, 后续将主要更新Kritor/Laana的分支
1. 申请一个Github Classic Token (需要`repo`的所有权限)(如果你想使用github作为图床)
2. 申请一个Cloudflare Token (需要`R2`的编辑权限)(如果你想使用cf作为图床)
3. 获取Cloudflare的Account Id以及R2的ID
4. 一个天聚数行API(TianAPI),并开通历史上的今天API权限
5. 一个openai平台的密钥和模型以及镜像地址
6. 和风天气的API key(免费订阅)
7. smtp服务器和端口
8. 网易云音乐API
9. QQ音乐API
10. 一个Azure APP (用于获取MC AccessToken)
11. 一个RTAST api token (用于生短链接, 你可以自行搭建但是源代码不公开)
12. 一个APISpace key
13. llama的api地址
# 开源 # 开源
+21 -17
View File
@@ -4,6 +4,7 @@ import org.jetbrains.kotlin.gradle.dsl.JvmTarget
plugins { plugins {
alias(libs.plugins.kotlin) alias(libs.plugins.kotlin)
alias(libs.plugins.shadow) alias(libs.plugins.shadow)
id("application")
} }
val fancyBotVersion: String by project val fancyBotVersion: String by project
@@ -14,8 +15,10 @@ version = fancyBotVersion
repositories { repositories {
mavenCentral() mavenCentral()
mavenLocal() mavenLocal()
maven("https://repo.rtast.cn/api/v4/projects/33/packages/maven") maven("https://repo.opencollab.dev/main/")
maven("https://repo.rtast.cn/api/v4/projects/19/packages/maven") maven("https://oss.sonatype.org/content/repositories/snapshots")
maven("https://repo.maven.rtast.cn/releases/")
maven("https://libraries.minecraft.net")
} }
tasks.compileKotlin { tasks.compileKotlin {
@@ -27,18 +30,23 @@ tasks.compileJava {
targetCompatibility = "17" targetCompatibility = "17"
} }
dependencies { dependencies {
implementation(libs.rOneBot) implementation(libs.ronebot)
implementation(libs.okhttp) implementation(libs.okhttp)
implementation(libs.exposedCore) implementation(libs.exposed.core)
implementation(libs.exposedJDBC) implementation(libs.exposed.jdbc)
implementation(libs.sqliteJDBC) implementation(libs.exposed.dao)
implementation(libs.motdPinger) implementation(libs.h2.jdbc)
implementation(libs.motd.pinger)
implementation(libs.animated.gif.lib) implementation(libs.animated.gif.lib)
implementation(libs.simple.java.mail) implementation(libs.simple.java.mail)
implementation(libs.jsoup) implementation(libs.jsoup)
implementation(libs.nashorn.core) implementation(libs.nashorn.core)
implementation(libs.zxing.core)
implementation(libs.zxing.javase)
implementation(libs.aws.s3.sdk)
implementation(libs.rconlib)
implementation(libs.mcprotocollib)
} }
tasks.build { tasks.build {
@@ -49,15 +57,6 @@ tasks.jar {
enabled = false enabled = false
} }
tasks.shadowJar {
manifest {
attributes(
"Main-Class" to "cn.rtast.fancybot.FancyBotKt",
"Manifest-Version" to "1.0"
)
}
}
tasks.register("deployBot") { tasks.register("deployBot") {
val apiKey = System.getenv("MCSM_API_KEY") val apiKey = System.getenv("MCSM_API_KEY")
val apiUrl = System.getenv("MCSM_API_URL") val apiUrl = System.getenv("MCSM_API_URL")
@@ -65,3 +64,8 @@ tasks.register("deployBot") {
val instanceId = System.getenv("MCSM_INSTANCE_ID") val instanceId = System.getenv("MCSM_INSTANCE_ID")
MCSMDeploy(apiUrl, apiKey, daemonId, instanceId).deploy(file("build/libs/FancyBot-1.0.0-all.jar")) MCSMDeploy(apiUrl, apiKey, daemonId, instanceId).deploy(file("build/libs/FancyBot-1.0.0-all.jar"))
} }
application {
mainClass = "cn.rtast.fancybot.FancyBotKt"
applicationName = "FancyBot"
}
+5
View File
@@ -1,2 +1,7 @@
kotlin.code.style=official kotlin.code.style=official
fancyBotVersion=1.0.0 fancyBotVersion=1.0.0
#systemProp.http.proxyHost=127.0.0.1
#systemProp.http.proxyPort=12334
#systemProp.https.proxyHost=127.0.0.1
#systemProp.https.proxyPort=12334
+20 -10
View File
@@ -1,27 +1,37 @@
[versions] [versions]
kotlinVersion = "2.0.10" kotlinVersion = "2.0.20"
ronebotVersion = "1.11.8" ronebotVersion = "2.7.4"
shadowVersion = "8.3.0" shadowVersion = "8.3.0"
okhttpVersion = "5.0.0-alpha.14" okhttpVersion = "5.0.0-alpha.14"
exposedVersion = "0.53.0" exposedVersion = "0.53.0"
sqliteJDBCVersion = "3.46.1.0" motdPingerVersion = "1.1.4"
motdPingerVersion = "1.1.2"
animatedGifLibVersion = "1.4" animatedGifLibVersion = "1.4"
simpleJavaMailVersion = "8.11.3" simpleJavaMailVersion = "8.12.2"
jsoupVersion = "1.18.1" jsoupVersion = "1.18.1"
nashornVersion = "15.4" nashornVersion = "15.4"
zxingVersion = "3.5.3"
awsS3SDKVersion = "2.28.18"
rconLibVersion = "1.1.1"
mcprotolobVersion = "1.21-SNAPSHOT"
h2Version = "2.3.232"
[libraries] [libraries]
rOneBot = { group = "cn.rtast", name = "ROneBot", version.ref = "ronebotVersion" } ronebot = { group = "cn.rtast.rob", name = "ronebot-onebot-v11", version.ref = "ronebotVersion" }
motdPinger = { group = "cn.rtast", name = "MotdPinger", version.ref = "motdPingerVersion" } motd-pinger = { group = "cn.rtast", name = "motd-pinger", version.ref = "motdPingerVersion" }
okhttp = { group = "com.squareup.okhttp3", name = "okhttp", version.ref = "okhttpVersion" } okhttp = { group = "com.squareup.okhttp3", name = "okhttp", version.ref = "okhttpVersion" }
exposedCore = { group = "org.jetbrains.exposed", name = "exposed-core", version.ref = "exposedVersion" } exposed-core = { group = "org.jetbrains.exposed", name = "exposed-core", version.ref = "exposedVersion" }
exposedJDBC = { group = "org.jetbrains.exposed", name = "exposed-jdbc", version.ref = "exposedVersion" } exposed-jdbc = { group = "org.jetbrains.exposed", name = "exposed-jdbc", version.ref = "exposedVersion" }
sqliteJDBC = { group = "org.xerial", name = "sqlite-jdbc", version.ref = "sqliteJDBCVersion" } exposed-dao = { group = "org.jetbrains.exposed", name = "exposed-dao", version.ref = "exposedVersion" }
animated-gif-lib = { group = "com.madgag", name = "animated-gif-lib", version.ref = "animatedGifLibVersion" } animated-gif-lib = { group = "com.madgag", name = "animated-gif-lib", version.ref = "animatedGifLibVersion" }
simple-java-mail = { group = "org.simplejavamail", name = "simple-java-mail", version.ref = "simpleJavaMailVersion" } simple-java-mail = { group = "org.simplejavamail", name = "simple-java-mail", version.ref = "simpleJavaMailVersion" }
jsoup = { group = "org.jsoup", name = "jsoup", version.ref = "jsoupVersion" } jsoup = { group = "org.jsoup", name = "jsoup", version.ref = "jsoupVersion" }
nashorn-core = { group = "org.openjdk.nashorn", name = "nashorn-core", version.ref = "nashornVersion" } nashorn-core = { group = "org.openjdk.nashorn", name = "nashorn-core", version.ref = "nashornVersion" }
zxing-core = { group = "com.google.zxing", name = "core", version.ref = "zxingVersion" }
zxing-javase = { group = "com.google.zxing", name = "javase", version.ref = "zxingVersion" }
aws-s3-sdk = { group = "software.amazon.awssdk", name = "s3", version.ref = "awsS3SDKVersion" }
rconlib = { group = "cn.rtast", name = "rconlib", version.ref = "rconLibVersion" }
mcprotocollib = { group = "org.geysermc.mcprotocollib", name = "protocol", version.ref = "mcprotolobVersion" }
h2-jdbc = { group = "com.h2database", name = "h2", version.ref = "h2Version" }
[bundles] [bundles]
Binary file not shown.
+3 -2
View File
@@ -1,6 +1,7 @@
#Mon Aug 26 20:47:33 CST 2024
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
Vendored
+31 -13
View File
@@ -15,6 +15,8 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
# #
# SPDX-License-Identifier: Apache-2.0
#
############################################################################## ##############################################################################
# #
@@ -55,7 +57,7 @@
# Darwin, MinGW, and NonStop. # Darwin, MinGW, and NonStop.
# #
# (3) This script is generated from the Groovy template # (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project. # within the Gradle project.
# #
# You can find Gradle at https://github.com/gradle/gradle/. # You can find Gradle at https://github.com/gradle/gradle/.
@@ -80,13 +82,12 @@ do
esac esac
done done
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit # This is normally unused
# shellcheck disable=SC2034
APP_NAME="Gradle"
APP_BASE_NAME=${0##*/} APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' ' "$PWD" ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value. # Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum MAX_FD=maximum
@@ -133,22 +134,29 @@ location of your Java installation."
fi fi
else else
JAVACMD=java JAVACMD=java
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. if ! command -v java >/dev/null 2>&1
then
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the Please set the JAVA_HOME variable in your environment to match the
location of your Java installation." location of your Java installation."
fi
fi fi
# Increase the maximum file descriptors if we can. # Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #( case $MAX_FD in #(
max*) max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
MAX_FD=$( ulimit -H -n ) || MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit" warn "Could not query maximum file descriptor limit"
esac esac
case $MAX_FD in #( case $MAX_FD in #(
'' | soft) :;; #( '' | soft) :;; #(
*) *)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
ulimit -n "$MAX_FD" || ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD" warn "Could not set maximum file descriptor limit to $MAX_FD"
esac esac
@@ -193,11 +201,15 @@ if "$cygwin" || "$msys" ; then
done done
fi fi
# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
# shell script including quotes and variable substitutions, so put them in DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# double quotes to make sure that they get re-expanded; and
# * put everything else in single quotes, so that it's not re-expanded. # Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.
set -- \ set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \ "-Dorg.gradle.appname=$APP_BASE_NAME" \
@@ -205,6 +217,12 @@ set -- \
org.gradle.wrapper.GradleWrapperMain \ org.gradle.wrapper.GradleWrapperMain \
"$@" "$@"
# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi
# Use "xargs" to parse quoted args. # Use "xargs" to parse quoted args.
# #
# With -n1 it outputs one arg per line, with the quotes and backslashes removed. # With -n1 it outputs one arg per line, with the quotes and backslashes removed.
Vendored
+21 -16
View File
@@ -13,8 +13,10 @@
@rem See the License for the specific language governing permissions and @rem See the License for the specific language governing permissions and
@rem limitations under the License. @rem limitations under the License.
@rem @rem
@rem SPDX-License-Identifier: Apache-2.0
@rem
@if "%DEBUG%" == "" @echo off @if "%DEBUG%"=="" @echo off
@rem ########################################################################## @rem ##########################################################################
@rem @rem
@rem Gradle startup script for Windows @rem Gradle startup script for Windows
@@ -25,7 +27,8 @@
if "%OS%"=="Windows_NT" setlocal if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0 set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=. if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused
set APP_BASE_NAME=%~n0 set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME% set APP_HOME=%DIRNAME%
@@ -40,13 +43,13 @@ if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1 %JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto execute if %ERRORLEVEL% equ 0 goto execute
echo. echo. 1>&2
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
echo. echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. echo location of your Java installation. 1>&2
goto fail goto fail
@@ -56,11 +59,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute if exist "%JAVA_EXE%" goto execute
echo. echo. 1>&2
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
echo. echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. echo location of your Java installation. 1>&2
goto fail goto fail
@@ -75,13 +78,15 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
:end :end
@rem End local scope for the variables with windows NT shell @rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd if %ERRORLEVEL% equ 0 goto mainEnd
:fail :fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code! rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 set EXIT_CODE=%ERRORLEVEL%
exit /b 1 if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd :mainEnd
if "%OS%"=="Windows_NT" endlocal if "%OS%"=="Windows_NT" endlocal
+53 -40
View File
@@ -4,31 +4,26 @@
* Date: 2024/8/27 * Date: 2024/8/27
*/ */
@file:Suppress("DEPRECATION")
package cn.rtast.fancybot package cn.rtast.fancybot
import cn.rtast.fancybot.commands.AboutCommand import cn.rtast.fancybot.commands.*
import cn.rtast.fancybot.commands.EchoCommand
import cn.rtast.fancybot.commands.HelpCommand
import cn.rtast.fancybot.commands.StatusCommand
import cn.rtast.fancybot.commands.lookup.* import cn.rtast.fancybot.commands.lookup.*
import cn.rtast.fancybot.commands.misc.* import cn.rtast.fancybot.commands.misc.*
import cn.rtast.fancybot.commands.niuzi.* import cn.rtast.fancybot.commands.niuzi.*
import cn.rtast.fancybot.commands.parse.AsciiArtCommand import cn.rtast.fancybot.commands.parse.AsciiArtCommand
import cn.rtast.fancybot.commands.parse.ReverseGIFCommand import cn.rtast.fancybot.commands.parse.ReverseGIFCommand
import cn.rtast.fancybot.commands.misc.JrrpCommand
import cn.rtast.fancybot.commands.niuzi.NiuziRedeemCommand
import cn.rtast.fancybot.commands.misc.ShortLinkCommand
import cn.rtast.fancybot.entity.Config import cn.rtast.fancybot.entity.Config
import cn.rtast.fancybot.entity.enums.ImageType import cn.rtast.fancybot.enums.ImageBedType
import cn.rtast.fancybot.entity.enums.WSType import cn.rtast.fancybot.enums.ImageType
import cn.rtast.fancybot.enums.WSType
import cn.rtast.fancybot.items.* import cn.rtast.fancybot.items.*
import cn.rtast.fancybot.util.file.ConfigManager import cn.rtast.fancybot.util.file.*
import cn.rtast.fancybot.util.file.NiuziBankManager
import cn.rtast.fancybot.util.file.NiuziManager
import cn.rtast.fancybot.util.item.ItemManager import cn.rtast.fancybot.util.item.ItemManager
import com.google.gson.Gson import com.google.gson.Gson
import com.google.gson.GsonBuilder import com.google.gson.GsonBuilder
import java.io.File
import java.time.Instant import java.time.Instant
val gson: Gson = GsonBuilder() val gson: Gson = GsonBuilder()
@@ -37,6 +32,10 @@ val gson: Gson = GsonBuilder()
.create() .create()
const val ROOT_PATH = "./data" const val ROOT_PATH = "./data"
val ROOT_PATH_ = File(ROOT_PATH).apply { mkdirs() }
const val ASSETS_BASE_URL = "https://static.rtast.cn"
const val API_RTAST_URL = "https://api.rtast.cn"
const val PBI_API_URL = "https://pbi.us.kg"
val DEFAULT_CONFIG = Config( val DEFAULT_CONFIG = Config(
ncmAPI = "https://ncm.rtast.cn", ncmAPI = "https://ncm.rtast.cn",
@@ -61,14 +60,32 @@ val DEFAULT_CONFIG = Config(
llamaUrl = "http://127.0.0.1", llamaUrl = "http://127.0.0.1",
llamaModel = "llama3.1", llamaModel = "llama3.1",
qqMusicApiUrl = "http://127.0.0.1:3200", qqMusicApiUrl = "http://127.0.0.1:3200",
startUpNoticeUser = 114514L, noticeUser = 114514L,
selfId = 114514L selfId = 114514L,
tianXingApiKey = "1145141919810",
githubUser = "RTAkland",
githubImageRepo = "Static-Images",
imageBedType = ImageBedType.Github,
cloudflareAccountId = "111",
cloudflareR2AccessKeyId = "11111",
cloudflareR2SecretKey = "222",
cloudflareR2BucketName = "fancybot",
cloudflareR2PublicUrl = "https://114514.com",
apiSpaceKey = "114514",
apiRtastKey = "114514",
azureAppClientId = "114514",
youtubeDataApiKey = "114514",
naiLongApiUrl = "http://127.0.0.1:5000/nailong",
osuClientId = 111,
osuClientSecret = "222"
) )
val configManager = ConfigManager() val configManager = ConfigManager()
val itemManager = ItemManager() val itemManager = ItemManager()
val niuziManager = NiuziManager() val niuziManager = NiuziManager()
val niuziBankManager = NiuziBankManager() val niuziBankManager = NiuziBankManager()
val blackListManager = BlackListManager()
val rconManager = RCONManager()
val START_UP_TIME = Instant.now().epochSecond val START_UP_TIME = Instant.now().epochSecond
@@ -79,30 +96,26 @@ val items = listOf(
) )
val commands = listOf( val commands = listOf(
EchoCommand(), JrrpCommand(), EchoCommand(), JrrpCommand(), NiuziRedeemCommand(), HitokotoCommand(),
NiuziRedeemCommand(), HitokotoCommand(), KFCCommand(), GenerateQRCodeCommand(), AntiRevokeCommand(), MCPingCommand(),
KFCCommand(), QRCodeCommand(), WeatherCommand(), CigaretteCommand(), RemakeCommand(), PixivCommand(),
AntiRevokeCommand(), MCPingCommand(), RUACommand(), CompilerCommand(), AICommand(),
WeatherCommand(), CigaretteCommand(), GithubUserCommand(), AboutCommand(), MusicPlayUrlCommand(), DomainPriceCommand(),
RemakeCommand(), PixivCommand(), SendMailCommand(), ZiBiCommand(), UnsetZiBiCommand(), WikipediaCommand(),
RUACommand(), NslookupCommand(), AsciiArtCommand(), StatusCommand(), JueCommand(), ShortLinkCommand(),
CompilerCommand(), AICommand(), ShotOtherCommand(), ReverseGIFCommand(), HelpCommand(), MusicCommand(),
GithubUserCommand(), AboutCommand(), SendLikeCommand(), NiuziTransferCommand(), NiuziSignCommand(), NiuziJiJianCommand(),
MusicPlayUrlCommand(), DomainPriceCommand(), NiuziQueryCommand(), MyNiuziCommand(), NiuziBankCommand(), WithdrawCommand(),
SendMailCommand(), ZiBiCommand(), BankTransferCommand(), CreateBankAccountCommand(), BankBalanceCommand(), DepositCommand(),
UnsetZiBiCommand(), WikipediaCommand(), AnimeSearchCommand(), LlamaCommand(), QQMusicCommand(), BullShitGenerateCommand(),
AsciiArtCommand(), StatusCommand(), NiuziRankCommand(), NiuziBankRankCommand(), ShotSelfCommand(), TenSetuCommand(),
JueCommand(), ShortLinkCommand(), ReactionCommand(), MCSkinCommand(), GithubLatestCommitCommand(), RandomMusicCommand(),
ShotOtherCommand(), ReverseGIFCommand(), ZDJDCommand(), ScanQRCodeCommand(), TTSCommand(), SlimeChunkHelperCommand(),
HelpCommand(), MusicCommand(), SupportMeCommand(), TheCatCommand(), TheHistoryOfTodayCommand(), MCVersionCommand(),
LikeMeCommand(), NiuziTransferCommand(), MinecraftWikiCommand(), GaoKaoDaysRemainCommand(), IdiomExplainCommand(), NiuziManagerCommand(),
NiuziSignCommand(), NiuziJiJianCommand(), TodayEatCommand(), TodayDrinkCommand(), MCLoginCommand(), RCONCommand(),
NiuziQueryCommand(), MyNiuziCommand(), GravatarCommand(), PastebinCommand(), ShuffleEchoCommand(), TodayDontEatCommand(),
NiuziBankCommand(), WithdrawCommand(), TodayDontDrinkCommand(), MCBotCommand(), HTTPCatCommand(), HTTPDogCommand(),
BankTransferCommand(), CreateBankAccountCommand(), AITTSCommand(), NWWNCommand(), TJBGXLCommand(), XNMCommand(), SJCommand(),
BankBalanceCommand(), DepositCommand(), OSUCommand(), TodaySmokeCommand(), GoodNewsCommand(), MRRHRCommand()
DMSearchCommand(), LlamaCommand(),
QQMusicCommand(), BullShitGenerateCommand(),
NiuziRankCommand(), NiuziBankRankCommand(),
ShotSelfCommand(), TenSetuCommand()
) )
+194 -61
View File
@@ -7,28 +7,54 @@
package cn.rtast.fancybot package cn.rtast.fancybot
import cn.rtast.fancybot.commands.misc.JueCommand
import cn.rtast.fancybot.commands.misc.PastebinCommand
import cn.rtast.fancybot.commands.misc.ReactionCommand
import cn.rtast.fancybot.commands.misc.ScanQRCodeCommand
import cn.rtast.fancybot.commands.misc.ShortLinkCommand.Companion.makeShortLink
import cn.rtast.fancybot.commands.parse.* import cn.rtast.fancybot.commands.parse.*
import cn.rtast.fancybot.entity.enums.WSType import cn.rtast.fancybot.commands.reply.ImageBedCommand
import cn.rtast.fancybot.commands.reply.InvertImageCommand
import cn.rtast.fancybot.commands.reply.RandomRGBCommand
import cn.rtast.fancybot.commands.reply.SpeedUpGIFCommand
import cn.rtast.fancybot.enums.WSType
import cn.rtast.fancybot.util.Logger
import cn.rtast.fancybot.util.initDatabase import cn.rtast.fancybot.util.initDatabase
import cn.rtast.fancybot.util.misc.*
import cn.rtast.fancybot.util.str.encodeToBase64
import cn.rtast.rob.ROneBotFactory import cn.rtast.rob.ROneBotFactory
import cn.rtast.rob.entity.* import cn.rtast.rob.entity.*
import cn.rtast.rob.entity.custom.BeKickEvent
import cn.rtast.rob.entity.custom.ErrorEvent
import cn.rtast.rob.entity.custom.MemberKickEvent
import cn.rtast.rob.entity.custom.MemberLeaveEvent
import cn.rtast.rob.entity.lagrange.FileEvent import cn.rtast.rob.entity.lagrange.FileEvent
import cn.rtast.rob.entity.lagrange.PokeEvent import cn.rtast.rob.entity.lagrange.PokeEvent
import cn.rtast.rob.enums.ArrayMessageType import cn.rtast.rob.enums.ArrayMessageType
import cn.rtast.rob.util.ob.MessageChain import cn.rtast.rob.onebot.MessageChain
import cn.rtast.rob.util.ob.OneBotListener import cn.rtast.rob.onebot.OneBotAction
import cn.rtast.rob.onebot.OneBotListener
import cn.rtast.rob.segment.NewLine
import cn.rtast.rob.segment.Text
import com.madgag.gif.fmsware.GifDecoder
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.io.ByteArrayOutputStream
import java.io.File import java.io.File
import java.net.URI import java.net.URI
import java.util.zip.ZipEntry
import java.util.zip.ZipOutputStream
import javax.imageio.ImageIO
class FancyBot : OneBotListener { class FancyBot : OneBotListener {
private val logger = Logger.getLogger<FancyBot>()
private val coroutineScope = CoroutineScope(Dispatchers.IO) private val coroutineScope = CoroutineScope(Dispatchers.IO)
override suspend fun onWebsocketOpenEvent() { override suspend fun onWebsocketOpenEvent(action: OneBotAction) {
this.sendPrivateMessage(configManager.startUpNoticeUser, "FancyBot启动完成~") action.sendPrivateMessage(configManager.noticeUser, "FancyBot启动完成~")
} }
override suspend fun onGroupMessage(message: GroupMessage, json: String) { override suspend fun onGroupMessage(message: GroupMessage, json: String) {
@@ -37,51 +63,146 @@ class FancyBot : OneBotListener {
val msg = message.rawMessage val msg = message.rawMessage
val groupId = message.groupId val groupId = message.groupId
val messageId = message.messageId val messageId = message.messageId
println("$sender($senderId: $groupId >>> $messageId): $msg") logger.info("$sender($senderId: $groupId >>> $messageId): $msg")
logger.trace("$sender($senderId: $groupId >>> $messageId: $json")
if (message.message.any { it.type == ArrayMessageType.face && it.data.id.toString() == "419" }) { // if (message.message.any { it.type == ArrayMessageType.face && it.data.id.toString() == "419" }) {
message.reply("你发牛魔的火车呢, 我直接就是打断") // message.reply("你发牛魔的火车呢, 我直接就是打断")
} // }
if (message.rawMessage.contains("原神")) { // if (message.text.contains("原神")) {
message.reply("你原神牛魔呢") // (0..10).forEach { _ -> message.reaction(QQFace.entries.random()) }
} // message.reply("你原神牛魔呢")
// delay(1500L)
// message.reply("但是话又说回来.....启动!!")
// }
GitHubParseCommand.parse(message)
ReverseGIFCommand.callback(message) ReverseGIFCommand.callback(message)
AsciiArtCommand.callback(message) AsciiArtCommand.callback(message)
ScanQRCodeCommand.callback(message)
BiliVideoParseCommand.parse(message)
BiliUserParseCommand.parse(message)
DouyinVideoParseCommand.parse(message)
YoutubeVideoParseCommand.parse(message)
if (message.rawMessage.toList().any { it in arrayListOf('*', '-', '/', '+', '=') }) { if (message.text.toList().any { it in arrayListOf('*', '-', '/', '+', '=') }) {
val calculateResult = CalculateCommand.parse(message.rawMessage) val calculateResult = CalculateCommand.parse(message.rawMessage)
calculateResult?.let { message.reply(calculateResult) } calculateResult?.let { message.reply(calculateResult) }
} }
if (message.message.any { it.type == ArrayMessageType.reply }) { // Image url parse if (message.message.any { it.type == ArrayMessageType.at }) {
val command = message.message.reversed().find { it.type == ArrayMessageType.text }!!.data.text!! if (message.text.contains("/取头像")) {
val replyId = message.message.find { it.type == ArrayMessageType.reply }!!.data.id!! val target = message.message.find { it.type == ArrayMessageType.at }?.data?.qq!!
if (command.contains("图来") || command.contains("图链")) { val avatar = JueCommand.AVATAR_REPLACE_URL.replace("#{}", target)
val getMsg = this.getMessage(replyId.toString().toLong()) val msg = MessageChain.Builder()
ImageURLCommand.callback(message, getMsg) if (message.text.contains("黑白")) {
val greyscale = avatar.toURL()
.readBytes().toBufferedImage()
.toGrayscale().toByteArray()
val greyscaleShortLink = ImageBed.upload(greyscale).makeShortLink()
msg.addImage(greyscale.encodeToBase64(), true)
.addText(greyscaleShortLink)
} else {
val shortLink = avatar.makeShortLink()
msg.addImage(avatar).addText(shortLink)
}
message.reply(msg.build())
} }
} }
if (message.rawMessage.startsWith("https://github.com/") || message.rawMessage.startsWith("git@github.com:")) { if (message.message.any { it.type == ArrayMessageType.reply }) {
GitHubParseCommand.parse(this, message) val command = message.text.trim()
} val replyId = message.message.find { it.type == ArrayMessageType.reply }!!.data.id!!
val getMsg = message.action.getMessage(replyId.toString().toLong())
val plainTextContent = getMsg.text
when (command) {
"/倒放", "/df" -> ReverseGIFCommand.reverse(message, getMsg)
"/图来", "/图链" -> ImageURLCommand.callback(message, getMsg)
"/图床" -> ImageBedCommand.execute(getMsg, message)
"/reaction" -> ReactionCommand.reaction(message.action, message.groupId, getMsg.messageId)
"/sl", "/short" -> message.reply(plainTextContent.trim().makeShortLink())
"/rc", "/随机颜色" -> RandomRGBCommand.random(message, getMsg)
"/颜色反转", "/iv" -> InvertImageCommand.invert(message, getMsg)
"/ascii", "/asc" -> {
val url = AsciiArtCommand.getImageUrl(getMsg)
val image = AsciiArtCommand.generateAsciiArt(url)
message.reply(image)
}
if (message.rawMessage.startsWith("BV") || "/pb", "/pastebin" -> {
message.rawMessage.startsWith("https://www.bilibili.com") || val pastebinUrl = PastebinCommand.createPastebin(plainTextContent)
message.rawMessage.contains("https://b23.tv/") || message.reply(pastebinUrl)
message.message.find { it.type == ArrayMessageType.json } != null }
) {
BVParseCommand.parse(this, message) "/加速" -> {
val multiply = command.split("加速").last().toFloat()
SpeedUpGIFCommand.speedUp(message, getMsg, multiply)
}
"/黑白", "/灰度" -> {
val msg = MessageChain.Builder()
getMsg.message.filter { it.type == ArrayMessageType.image || it.type == ArrayMessageType.mface }
.forEach {
val imgUrl = it.data.file!!
val decoder = GifDecoder()
val imageStream = withContext(Dispatchers.IO) { imgUrl.toURL().openStream() }
decoder.read(imageStream)
if (decoder.frameCount == 0) {
msg.addImage(
imageStream.readBytes().toBufferedImage().toByteArray().encodeToBase64(), true
)
} else {
val frames = (0 until decoder.frameCount).map { decoder.getFrame(it).toGrayscale() }
val base64 = decoder.makeGif(frames).encodeToBase64()
msg.addImage(base64, true)
}
}
message.reply(msg.build())
}
"/拆帧", "/cj" -> {
val gifStream = getMsg.images.first().file.toURL().openStream()
val decoder = GifDecoder()
decoder.read(gifStream)
val frames = (0 until decoder.frameCount).map { decoder.getFrame(it) }
val byteArrayOutputStream = ByteArrayOutputStream()
ZipOutputStream(byteArrayOutputStream).use { zipOut ->
for ((index, image) in frames.withIndex()) {
val imageOutputStream = ByteArrayOutputStream()
ImageIO.write(image, "png", imageOutputStream)
val entry = ZipEntry("frame_$index.png")
zipOut.putNextEntry(entry)
zipOut.write(imageOutputStream.toByteArray())
zipOut.closeEntry()
}
}
val zipBytes = byteArrayOutputStream.toByteArray()
logger.info("Split frame size: ${zipBytes.size / 1024 / 1024}MB")
val imgBedUrl = ImageBed.upload(zipBytes, "zip")
val shortLink = imgBedUrl.makeShortLink()
val msg = Text(imgBedUrl) + NewLine() + Text(shortLink)
message.reply(msg)
}
}
} }
coroutineScope.launch { coroutineScope.launch {
// if (message.images.isNotEmpty()) {
// message.images.forEach {
// val payload = NaiLongDetectPayload(it.file).toJson()
// val result = Http.post<NaiLongDetectResponse>(configManager.naiLongApiUrl, payload)
// logger.info("奶龙识别结果: ${result.result}")
// if (result.result) {
// message.reply("本群禁止发奶龙!")
// }
// }
// }
message.message.forEach { message.message.forEach {
if (it.type == ArrayMessageType.image) { if (it.type == ArrayMessageType.image) {
val filename = it.data.file!!.split("=").last() + ".png" val filename = it.data.file!!.split("=").last() + ".png"
URI(it.data.file!!).toURL().openConnection().inputStream.use { input -> URI(it.data.file!!).toURL().openConnection().inputStream.use { input ->
File("./files/images/$filename").outputStream().use { output -> File("$ROOT_PATH/caches/images/$filename").outputStream().use { output ->
output.write(input.readBytes()) output.write(input.readBytes())
} }
} }
@@ -97,65 +218,77 @@ class FancyBot : OneBotListener {
.addNewLine() .addNewLine()
.addText("使用/revoke ${message.messageId} 来获取被撤回的消息") .addText("使用/revoke ${message.messageId} 来获取被撤回的消息")
.build() .build()
this.sendGroupMessage(message.groupId, msg) message.action.sendGroupMessage(message.groupId, msg)
} }
override suspend fun onWebsocketErrorEvent(ex: Exception) { override suspend fun onWebsocketErrorEvent(event: ErrorEvent) {
ex.printStackTrace() event.exception.printStackTrace()
} }
override suspend fun onAddFriendRequest(event: AddFriendRequest) { override suspend fun onAddFriendRequest(event: AddFriendRequestEvent) {
event.approve() event.approve()
} }
override suspend fun onGroupFileUpload(event: FileEvent) { override suspend fun onGroupFileUpload(event: FileEvent) {
event.saveTo("./files") event.saveTo("$ROOT_PATH/caches/")
} }
override suspend fun onLeaveEvent(groupId: Long, userId: Long, operator: Long, time: Long) { override suspend fun onLeaveEvent(event: MemberLeaveEvent) {
val nickname = event.action.getGroupMemberInfo(event.groupId, event.userId).nickname
val msg = MessageChain.Builder() val msg = MessageChain.Builder()
.addText("有人坐飞船离开了本星系~") .addText("有人坐飞船离开了本星系~(主动退出)")
.addNewLine() .addNewLine()
.addText("QQ: $userId | 操作者: ${if (operator == 0L) "主动退出" else operator}") .addText("QQ: $nickname(${event.userId})")
.addNewLine() .addNewLine()
.addText("下次再见吧~~~") .addText("下次再见吧~~~")
.build() .build()
this.sendGroupMessage(groupId, msg) event.action.sendGroupMessage(event.groupId, msg)
} }
override suspend fun onPoke(event: PokeEvent) { override suspend fun onMemberKick(event: MemberKickEvent) {
event.groupId?.let { val nickname = event.action.getGroupMemberInfo(event.groupId, event.userId).nickname
if (event.targetId == configManager.selfId) { val msg = MessageChain.Builder()
val msg = MessageChain.Builder() .addText("有人坐飞船离开了本星系~(管理员踢出)")
.addAt(event.userId) .addNewLine()
.addText("${event.action.first()}牛魔呢") .addText("QQ: $nickname(${event.userId})")
.build() .addNewLine()
this.sendGroupMessage(it, msg) .addText("下次再见吧~~~")
} .build()
event.action.sendGroupMessage(event.groupId, msg)
}
override suspend fun onGroupPoke(event: PokeEvent) {
if (event.targetId == configManager.selfId) {
val msg = MessageChain.Builder()
.addAt(event.userId)
.addText("${event.pokeAction.first()}牛魔呢")
.build()
event.action.sendGroupMessage(event.groupId!!, msg)
} }
} }
}
fun initFilesDir() { override suspend fun onBeKicked(event: BeKickEvent) {
File("./files/images").also { it.mkdirs() } blackListManager.insertGroup(event.groupId, event.operator, event.time)
event.action.sendPrivateMessage(
configManager.noticeUser,
"被: ${event.groupId}踢出! 操作人: ${event.operator} 时间: ${event.time.convertToDate()} 已将其拉入黑名单!"
)
}
} }
suspend fun main() { suspend fun main() {
val fancyBot = FancyBot() val fancyBot = FancyBot()
val workType = configManager.wsType val workType = configManager.wsType
val accessToken = configManager.accessToken val accessToken = configManager.accessToken
val rob = if (workType == WSType.Client) { val instance = when (workType) {
val address = configManager.wsAddress WSType.Client -> ROneBotFactory.createClient(configManager.wsAddress, accessToken, fancyBot)
ROneBotFactory.createClient(address, accessToken, fancyBot) WSType.Server -> ROneBotFactory.createServer(configManager.wsPort, accessToken, fancyBot)
.also { it.addListeningGroups(*configManager.listeningGroups.toLongArray()) }
} else {
val port = configManager.wsPort
ROneBotFactory.createServer(port, accessToken, fancyBot)
.also { it.addListeningGroups(*configManager.listeningGroups.toLongArray()) }
} }
instance.addListeningGroups(*configManager.listeningGroups.toLongArray())
initDatabase() initDatabase()
initFilesDir() initFilesDir()
val commandManager = rob.commandManager initCommand()
commands.forEach { commandManager.register(it) } initSetuIndex()
items.forEach { itemManager.register(it) } initItems()
initBackgroundTasks(instance)
} }
@@ -9,15 +9,14 @@ package cn.rtast.fancybot.commands
import cn.rtast.fancybot.annotations.CommandDescription import cn.rtast.fancybot.annotations.CommandDescription
import cn.rtast.rob.entity.GroupMessage import cn.rtast.rob.entity.GroupMessage
import cn.rtast.rob.onebot.MessageChain
import cn.rtast.rob.util.BaseCommand import cn.rtast.rob.util.BaseCommand
import cn.rtast.rob.util.ob.MessageChain
import cn.rtast.rob.util.ob.OneBotListener
@CommandDescription("关于") @CommandDescription("关于")
class AboutCommand : BaseCommand() { class AboutCommand : BaseCommand() {
override val commandNames = listOf("/about", "/关于") override val commandNames = listOf("/about", "/关于")
override suspend fun executeGroup(listener: OneBotListener, message: GroupMessage, args: List<String>) { override suspend fun executeGroup(message: GroupMessage, args: List<String>) {
val msg = MessageChain.Builder() val msg = MessageChain.Builder()
.addText("Bot名称: FancyBot") .addText("Bot名称: FancyBot")
.addNewLine() .addNewLine()
@@ -8,15 +8,35 @@
package cn.rtast.fancybot.commands package cn.rtast.fancybot.commands
import cn.rtast.fancybot.annotations.CommandDescription import cn.rtast.fancybot.annotations.CommandDescription
import cn.rtast.fancybot.enums.CommandAction
import cn.rtast.fancybot.util.file.insertActionRecord
import cn.rtast.rob.entity.GroupMessage import cn.rtast.rob.entity.GroupMessage
import cn.rtast.rob.util.BaseCommand import cn.rtast.rob.util.BaseCommand
import cn.rtast.rob.util.ob.OneBotListener
@CommandDescription("Echo") @CommandDescription("Echo")
class EchoCommand : BaseCommand() { class EchoCommand : BaseCommand() {
override val commandNames = listOf("/echo") override val commandNames = listOf("/echo")
override suspend fun executeGroup(listener: OneBotListener, message: GroupMessage, args: List<String>) { override suspend fun executeGroup(message: GroupMessage, args: List<String>) {
listener.sendGroupMessage(message.groupId, args.joinToString(" ")) if (message.sender.isAdmin || message.sender.isOwner) {
message.reply(args.joinToString(" "))
insertActionRecord(CommandAction.Echo, message.sender.userId)
} else {
message.reply("你不许用echo")
}
}
}
@CommandDescription("精神错乱(×), 胡言乱语(√)")
class ShuffleEchoCommand : BaseCommand() {
override val commandNames = listOf("/secho", "/se")
override suspend fun executeGroup(message: GroupMessage, args: List<String>) {
if (message.sender.isAdmin || message.sender.isOwner) {
message.reply(message.rawMessage.toList().shuffled().joinToString(""))
insertActionRecord(CommandAction.Echo, message.sender.userId)
} else {
message.reply("你不许用echo")
}
} }
} }
@@ -11,10 +11,9 @@ import cn.rtast.fancybot.annotations.CommandDescription
import cn.rtast.fancybot.commands import cn.rtast.fancybot.commands
import cn.rtast.fancybot.configManager import cn.rtast.fancybot.configManager
import cn.rtast.rob.entity.GroupMessage import cn.rtast.rob.entity.GroupMessage
import cn.rtast.rob.onebot.MessageChain
import cn.rtast.rob.onebot.NodeMessageChain
import cn.rtast.rob.util.BaseCommand import cn.rtast.rob.util.BaseCommand
import cn.rtast.rob.util.ob.MessageChain
import cn.rtast.rob.util.ob.NodeMessageChain
import cn.rtast.rob.util.ob.OneBotListener
import kotlin.reflect.full.findAnnotation import kotlin.reflect.full.findAnnotation
import kotlin.reflect.full.hasAnnotation import kotlin.reflect.full.hasAnnotation
@@ -22,19 +21,22 @@ import kotlin.reflect.full.hasAnnotation
class HelpCommand : BaseCommand() { class HelpCommand : BaseCommand() {
override val commandNames = listOf("/help", "/帮助") override val commandNames = listOf("/help", "/帮助")
override suspend fun executeGroup(listener: OneBotListener, message: GroupMessage, args: List<String>) { override suspend fun executeGroup(message: GroupMessage, args: List<String>) {
if (args.isEmpty()) { if (args.isEmpty()) {
val node = NodeMessageChain.Builder() val node = NodeMessageChain.Builder()
val msg = MessageChain.Builder() val msg = MessageChain.Builder()
commands.sortedBy { it::class.simpleName }.forEach { commands.sortedBy { it::class.simpleName }.toMutableList().forEach {
val description = if (!it::class.hasAnnotation<CommandDescription>()) "暂无描述" val description = if (!it::class.hasAnnotation<CommandDescription>()) "暂无描述"
else it::class.findAnnotation<CommandDescription>()?.description!! else it::class.findAnnotation<CommandDescription>()?.description!!
val commandName = it::class.simpleName?.replace("Command", "")!! val commandName = it::class.simpleName?.replace("Command", "")!!
val commandNames = it.commandNames.joinToString(",") val commandNames = it.commandNames.joinToString(",")
msg.addText("[$commandName] [$description] 命令: $commandNames") msg.addText("[$commandName] [$description] 命令: $commandNames").addNewLine()
.addNewLine()
} }
msg.addText("共计${commands.size}条命令") msg.addText("共计${commands.size}条命令")
node.addMessageChain(
MessageChain.Builder().addText("你可以使用`/help <指令>`来获取这个指令的帮助信息").build(),
configManager.selfId
)
node.addMessageChain(msg.build(), configManager.selfId) node.addMessageChain(msg.build(), configManager.selfId)
message.reply(node.build()) message.reply(node.build())
} else { } else {
@@ -9,17 +9,18 @@ package cn.rtast.fancybot.commands
import cn.rtast.fancybot.START_UP_TIME import cn.rtast.fancybot.START_UP_TIME
import cn.rtast.fancybot.annotations.CommandDescription import cn.rtast.fancybot.annotations.CommandDescription
import cn.rtast.fancybot.enums.CommandAction
import cn.rtast.fancybot.util.file.insertActionRecord
import cn.rtast.rob.entity.GroupMessage import cn.rtast.rob.entity.GroupMessage
import cn.rtast.rob.onebot.MessageChain
import cn.rtast.rob.util.BaseCommand import cn.rtast.rob.util.BaseCommand
import cn.rtast.rob.util.ob.MessageChain
import cn.rtast.rob.util.ob.OneBotListener
import java.time.Instant import java.time.Instant
@CommandDescription("状态") @CommandDescription("状态")
class StatusCommand : BaseCommand() { class StatusCommand : BaseCommand() {
override val commandNames = listOf("/status") override val commandNames = listOf("/status")
override suspend fun executeGroup(listener: OneBotListener, message: GroupMessage, args: List<String>) { override suspend fun executeGroup(message: GroupMessage, args: List<String>) {
val runtime = Runtime.getRuntime() val runtime = Runtime.getRuntime()
val maxMemory = runtime.maxMemory() / (1024 * 1024) val maxMemory = runtime.maxMemory() / (1024 * 1024)
val allocatedMemory = runtime.totalMemory() / (1024 * 1024) val allocatedMemory = runtime.totalMemory() / (1024 * 1024)
@@ -44,5 +45,6 @@ class StatusCommand : BaseCommand() {
.addText("Java版本: $javaVersion / Kotlin版本: $kotlinVersion") .addText("Java版本: $javaVersion / Kotlin版本: $kotlinVersion")
.build() .build()
message.reply(msg) message.reply(msg)
insertActionRecord(CommandAction.Status, message.sender.userId)
} }
} }
@@ -0,0 +1,34 @@
/*
* Copyright © 2024 RTAkland
* Author: RTAkland
* Date: 2024/10/7
*/
package cn.rtast.fancybot.commands
import cn.rtast.fancybot.ASSETS_BASE_URL
import cn.rtast.fancybot.annotations.CommandDescription
import cn.rtast.fancybot.util.str.encodeToBase64
import cn.rtast.rob.entity.GroupMessage
import cn.rtast.rob.onebot.MessageChain
import cn.rtast.rob.util.BaseCommand
import java.net.URI
@CommandDescription("给我打钱!")
class SupportMeCommand : BaseCommand() {
override val commandNames = listOf("打钱", "给我打钱", "给满子打钱")
override suspend fun executeGroup(message: GroupMessage, args: List<String>, matchedCommand: String) {
if (matchedCommand == "给满子打钱") {
message.reply("你打牛魔呢?")
return
}
val image = URI("$ASSETS_BASE_URL/images/048cc8af57f19850ca176f29e50b6215.png")
.toURL().readBytes().encodeToBase64()
val msg = MessageChain.Builder()
.addImage(image, true)
.build()
message.reply(msg)
}
}
@@ -13,13 +13,14 @@ import cn.rtast.fancybot.entity.gpt.ChatCompletionsPayload
import cn.rtast.fancybot.entity.gpt.ChatCompletionsResponse import cn.rtast.fancybot.entity.gpt.ChatCompletionsResponse
import cn.rtast.fancybot.entity.gpt.LlamaResponse import cn.rtast.fancybot.entity.gpt.LlamaResponse
import cn.rtast.fancybot.entity.gpt.ModelList import cn.rtast.fancybot.entity.gpt.ModelList
import cn.rtast.fancybot.enums.CommandAction
import cn.rtast.fancybot.util.Http import cn.rtast.fancybot.util.Http
import cn.rtast.fancybot.util.file.insertActionRecord
import cn.rtast.fancybot.util.str.toJson import cn.rtast.fancybot.util.str.toJson
import cn.rtast.rob.entity.GroupMessage import cn.rtast.rob.entity.GroupMessage
import cn.rtast.rob.onebot.MessageChain
import cn.rtast.rob.onebot.NodeMessageChain
import cn.rtast.rob.util.BaseCommand import cn.rtast.rob.util.BaseCommand
import cn.rtast.rob.util.ob.MessageChain
import cn.rtast.rob.util.ob.NodeMessageChain
import cn.rtast.rob.util.ob.OneBotListener
@CommandDescription("问AI(GPT)") @CommandDescription("问AI(GPT)")
class AICommand : BaseCommand() { class AICommand : BaseCommand() {
@@ -27,7 +28,7 @@ class AICommand : BaseCommand() {
private val openAIModel = configManager.openAIModel private val openAIModel = configManager.openAIModel
override suspend fun executeGroup(listener: OneBotListener, message: GroupMessage, args: List<String>) { override suspend fun executeGroup(message: GroupMessage, args: List<String>) {
if (args.isEmpty()) { if (args.isEmpty()) {
val msg = MessageChain.Builder() val msg = MessageChain.Builder()
.addText("发送`/ai [模型] <问题>`即可询问AI哦~") .addText("发送`/ai [模型] <问题>`即可询问AI哦~")
@@ -67,7 +68,8 @@ class AICommand : BaseCommand() {
.addText(response.choices.first().message.content) .addText(response.choices.first().message.content)
.build() .build()
nodeMsg.addMessageChain(msg, configManager.selfId) nodeMsg.addMessageChain(msg, configManager.selfId)
listener.sendGroupForwardMsg(message.groupId, nodeMsg.build()) message.action.sendGroupForwardMsg(message.groupId, nodeMsg.build())
insertActionRecord(CommandAction.AI, message.sender.userId, "$content-$model-GPT")
} }
} }
@@ -78,7 +80,7 @@ class LlamaCommand : BaseCommand() {
private val llamaURL = configManager.llamaUrl private val llamaURL = configManager.llamaUrl
private val llamaModel = configManager.llamaModel private val llamaModel = configManager.llamaModel
override suspend fun executeGroup(listener: OneBotListener, message: GroupMessage, args: List<String>) { override suspend fun executeGroup(message: GroupMessage, args: List<String>) {
if (args.isEmpty()) { if (args.isEmpty()) {
message.reply("发送`/llama <问题>`即可使用llama模型来回复") message.reply("发送`/llama <问题>`即可使用llama模型来回复")
return return
@@ -93,6 +95,7 @@ class LlamaCommand : BaseCommand() {
.addText(response.message.content) .addText(response.message.content)
.build() .build()
nodeMsg.addMessageChain(msg, configManager.selfId) nodeMsg.addMessageChain(msg, configManager.selfId)
listener.sendGroupForwardMsg(message.groupId, nodeMsg.build()) message.action.sendGroupForwardMsg(message.groupId, nodeMsg.build())
insertActionRecord(CommandAction.AI, message.sender.userId, "$prompt-LLAMA")
} }
} }
@@ -13,15 +13,14 @@ import cn.rtast.fancybot.entity.bgm.BGMSearch
import cn.rtast.fancybot.util.Http import cn.rtast.fancybot.util.Http
import cn.rtast.fancybot.util.str.encodeToBase64 import cn.rtast.fancybot.util.str.encodeToBase64
import cn.rtast.rob.entity.GroupMessage import cn.rtast.rob.entity.GroupMessage
import cn.rtast.rob.onebot.MessageChain
import cn.rtast.rob.onebot.NodeMessageChain
import cn.rtast.rob.util.BaseCommand import cn.rtast.rob.util.BaseCommand
import cn.rtast.rob.util.ob.MessageChain
import cn.rtast.rob.util.ob.NodeMessageChain
import cn.rtast.rob.util.ob.OneBotListener
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
@CommandDescription("搜索动漫") @CommandDescription("搜索动漫(Bangumi.tv)")
class DMSearchCommand : BaseCommand() { class AnimeSearchCommand : BaseCommand() {
override val commandNames = listOf("/dm", "动漫") override val commandNames = listOf("/dm", "动漫")
private val client = OkHttpClient() private val client = OkHttpClient()
@@ -37,7 +36,7 @@ class DMSearchCommand : BaseCommand() {
} }
} }
override suspend fun executeGroup(listener: OneBotListener, message: GroupMessage, args: List<String>) { override suspend fun executeGroup(message: GroupMessage, args: List<String>) {
if (args.isEmpty()) { if (args.isEmpty()) {
message.reply("发送`/dm <关键词>`即可搜索番剧啦~") message.reply("发送`/dm <关键词>`即可搜索番剧啦~")
return return
@@ -58,7 +57,7 @@ class DMSearchCommand : BaseCommand() {
} }
val footerNode = MessageChain.Builder().addText("数据来源: Bangumi").build() val footerNode = MessageChain.Builder().addText("数据来源: Bangumi").build()
nodeMsg.addMessageChain(footerNode, configManager.selfId) nodeMsg.addMessageChain(footerNode, configManager.selfId)
listener.sendGroupForwardMsg(message.groupId, nodeMsg.build()) message.action.sendGroupForwardMsg(message.groupId, nodeMsg.build())
} catch (_: Exception) { } catch (_: Exception) {
message.reply("没有搜索到结果呢~") message.reply("没有搜索到结果呢~")
} }
@@ -13,10 +13,9 @@ import cn.rtast.fancybot.entity.cigarette.Cigarette
import cn.rtast.fancybot.util.Http import cn.rtast.fancybot.util.Http
import cn.rtast.fancybot.util.str.encodeToBase64 import cn.rtast.fancybot.util.str.encodeToBase64
import cn.rtast.rob.entity.GroupMessage import cn.rtast.rob.entity.GroupMessage
import cn.rtast.rob.onebot.MessageChain
import cn.rtast.rob.onebot.NodeMessageChain
import cn.rtast.rob.util.BaseCommand import cn.rtast.rob.util.BaseCommand
import cn.rtast.rob.util.ob.MessageChain
import cn.rtast.rob.util.ob.NodeMessageChain
import cn.rtast.rob.util.ob.OneBotListener
import java.net.URI import java.net.URI
@CommandDescription("查询香烟价格") @CommandDescription("查询香烟价格")
@@ -25,7 +24,7 @@ class CigaretteCommand : BaseCommand() {
private val apiUrl = "https://www.yanyue.cn/api/rc/product/yanlist" private val apiUrl = "https://www.yanyue.cn/api/rc/product/yanlist"
override suspend fun executeGroup(listener: OneBotListener, message: GroupMessage, args: List<String>) { override suspend fun executeGroup(message: GroupMessage, args: List<String>) {
if (args.isEmpty()) { if (args.isEmpty()) {
val msg = MessageChain.Builder() val msg = MessageChain.Builder()
.addText("发送/tobacco <名称> [数量] 来搜索结果哦~") .addText("发送/tobacco <名称> [数量] 来搜索结果哦~")
@@ -61,6 +60,6 @@ class CigaretteCommand : BaseCommand() {
.build() .build()
nodeMsg.addMessageChain(footerMsg, configManager.selfId) nodeMsg.addMessageChain(footerMsg, configManager.selfId)
message.reply(nodeMsg.build()) message.reply(nodeMsg.build())
message.sender.groupPoke() message.sender.poke()
} }
} }
@@ -11,13 +11,12 @@ import cn.rtast.fancybot.annotations.CommandDescription
import cn.rtast.fancybot.entity.domain.PricePayload import cn.rtast.fancybot.entity.domain.PricePayload
import cn.rtast.fancybot.entity.domain.PriceResponse import cn.rtast.fancybot.entity.domain.PriceResponse
import cn.rtast.fancybot.util.Http import cn.rtast.fancybot.util.Http
import cn.rtast.fancybot.util.misc.toByteArray
import cn.rtast.fancybot.util.str.encodeToBase64 import cn.rtast.fancybot.util.str.encodeToBase64
import cn.rtast.fancybot.util.str.toJson import cn.rtast.fancybot.util.str.toJson
import cn.rtast.fancybot.util.toByteArray
import cn.rtast.rob.entity.GroupMessage import cn.rtast.rob.entity.GroupMessage
import cn.rtast.rob.onebot.MessageChain
import cn.rtast.rob.util.BaseCommand import cn.rtast.rob.util.BaseCommand
import cn.rtast.rob.util.ob.MessageChain
import cn.rtast.rob.util.ob.OneBotListener
import java.awt.Color import java.awt.Color
import java.awt.Font import java.awt.Font
import java.awt.image.BufferedImage import java.awt.image.BufferedImage
@@ -61,7 +60,7 @@ class DomainPriceCommand : BaseCommand() {
domain: String, domain: String,
available: Boolean, available: Boolean,
premium: Boolean, premium: Boolean,
domainPrice: Pair<Int, Int> domainPrice: Pair<Int, Int>,
): String { ): String {
val canvas = this.createCommonCanvas() val canvas = this.createCommonCanvas()
val g2d = canvas.createGraphics() val g2d = canvas.createGraphics()
@@ -77,7 +76,7 @@ class DomainPriceCommand : BaseCommand() {
} }
override suspend fun executeGroup(listener: OneBotListener, message: GroupMessage, args: List<String>) { override suspend fun executeGroup(message: GroupMessage, args: List<String>) {
if (args.isEmpty()) { if (args.isEmpty()) {
val msg = MessageChain.Builder() val msg = MessageChain.Builder()
.addText("发送`/domain <域名>`即可查询对应的注册和续费价格~") .addText("发送`/domain <域名>`即可查询对应的注册和续费价格~")
@@ -0,0 +1,57 @@
/*
* Copyright © 2024 RTAkland
* Author: RTAkland
* Date: 2024/10/3
*/
package cn.rtast.fancybot.commands.lookup
import cn.rtast.fancybot.annotations.CommandDescription
import cn.rtast.fancybot.commands.parse.GitHubParseCommand
import cn.rtast.fancybot.configManager
import cn.rtast.fancybot.entity.github.LatestCommit
import cn.rtast.fancybot.util.Http
import cn.rtast.fancybot.util.str.fromArrayJson
import cn.rtast.rob.entity.GroupMessage
import cn.rtast.rob.onebot.MessageChain
import cn.rtast.rob.util.BaseCommand
@CommandDescription("查询Github上的仓库的最新一次的提交信息")
class GithubLatestCommitCommand : BaseCommand() {
override val commandNames = listOf(".commit")
private val userInfoUrl = "https://api.github.com/repos"
override suspend fun executeGroup(message: GroupMessage, args: List<String>) {
if (args.isEmpty()) {
message.reply("发送`.commit <用户名/仓库名>`即可查询仓库最新一次的提交信息")
}
try {
val (user, repo) = if (args.size == 1) {
args.first().split("/").first() to args.first().split("/").last()
} else {
args.first() to args.last()
}
val response = Http.get(
"$userInfoUrl/$user/$repo/commits",
headers = mapOf("Authorization" to configManager.githubKey)
).fromArrayJson<List<LatestCommit>>().first()
val image = GitHubParseCommand.creatRepoImage(user, repo)
val msg = MessageChain.Builder()
.addImage(image, true)
.addText("最新提交ID: ${response.sha.substring(0, 7)}")
.addNewLine()
.addText("提交者: ${response.commit.committer.name}(${response.commit.committer.email})")
.addNewLine()
.addText("提交日期: ${response.commit.committer.date}")
.addNewLine()
.addText("https://github.com/$user/$repo")
.build()
message.reply(msg)
} catch (e: Exception) {
e.printStackTrace()
message.reply("查询失败~")
}
}
}
@@ -11,15 +11,14 @@ import cn.rtast.fancybot.annotations.CommandDescription
import cn.rtast.fancybot.configManager import cn.rtast.fancybot.configManager
import cn.rtast.fancybot.entity.github.UserInfo import cn.rtast.fancybot.entity.github.UserInfo
import cn.rtast.fancybot.util.Http import cn.rtast.fancybot.util.Http
import cn.rtast.fancybot.util.drawCustomImage import cn.rtast.fancybot.util.misc.drawCustomImage
import cn.rtast.fancybot.util.drawString import cn.rtast.fancybot.util.misc.drawString
import cn.rtast.fancybot.util.misc.toBufferedImage
import cn.rtast.fancybot.util.misc.toByteArray
import cn.rtast.fancybot.util.str.encodeToBase64 import cn.rtast.fancybot.util.str.encodeToBase64
import cn.rtast.fancybot.util.toBufferedImage
import cn.rtast.fancybot.util.toByteArray
import cn.rtast.rob.entity.GroupMessage import cn.rtast.rob.entity.GroupMessage
import cn.rtast.rob.onebot.MessageChain
import cn.rtast.rob.util.BaseCommand import cn.rtast.rob.util.BaseCommand
import cn.rtast.rob.util.ob.MessageChain
import cn.rtast.rob.util.ob.OneBotListener
import java.awt.Color import java.awt.Color
import java.awt.Font import java.awt.Font
import java.awt.image.BufferedImage import java.awt.image.BufferedImage
@@ -67,7 +66,7 @@ class GithubUserCommand : BaseCommand() {
return canvas.toByteArray().encodeToBase64() return canvas.toByteArray().encodeToBase64()
} }
override suspend fun executeGroup(listener: OneBotListener, message: GroupMessage, args: List<String>) { override suspend fun executeGroup(message: GroupMessage, args: List<String>) {
if (args.isEmpty()) { if (args.isEmpty()) {
val msg = MessageChain.Builder().addText("发送`gh <用户名>`即可查询用户的信息~").build() val msg = MessageChain.Builder().addText("发送`gh <用户名>`即可查询用户的信息~").build()
message.reply(msg) message.reply(msg)
@@ -0,0 +1,33 @@
/*
* Copyright © 2024 RTAkland
* Author: RTAkland
* Date: 2024/10/14
*/
package cn.rtast.fancybot.commands.lookup
import cn.rtast.fancybot.annotations.CommandDescription
import cn.rtast.fancybot.util.misc.toURL
import cn.rtast.fancybot.util.str.encodeToBase64
import cn.rtast.fancybot.util.str.getMD5
import cn.rtast.rob.entity.GroupMessage
import cn.rtast.rob.onebot.MessageChain
import cn.rtast.rob.util.BaseCommand
@CommandDescription("查询用户的Gravatar头像")
class GravatarCommand : BaseCommand() {
override val commandNames = listOf("/g", "/gravatar")
companion object {
private const val GRAVATAR_URL = "https://gravatar.rtast.cn/avatar"
}
override suspend fun executeGroup(message: GroupMessage, args: List<String>) {
val emailMD5 = if (args.first().trim().lowercase().contains("@")) args.first().trim().lowercase().getMD5()
else args.first().trim().lowercase()
val avatarImage = "$GRAVATAR_URL/$emailMD5?size=256".toURL().readBytes().encodeToBase64()
val msg = MessageChain.Builder().addImage(avatarImage, true).build()
message.reply(msg)
}
}
@@ -0,0 +1,50 @@
/*
* Copyright © 2024 RTAkland
* Author: RTAkland
* Date: 2024/10/10
*/
package cn.rtast.fancybot.commands.lookup
import cn.rtast.fancybot.annotations.CommandDescription
import cn.rtast.fancybot.configManager
import cn.rtast.fancybot.entity.apispace.IdiomExplainResponse
import cn.rtast.fancybot.util.Http
import cn.rtast.rob.entity.GroupMessage
import cn.rtast.rob.onebot.MessageChain
import cn.rtast.rob.util.BaseCommand
@CommandDescription("解释一个成语")
class IdiomExplainCommand : BaseCommand() {
override val commandNames = listOf("成语解释")
private val apiSpaceUrl = "https://eolink.o.apispace.com/cyudac/api/v1/idioms/SearchIdiomsByPinyin/search"
override suspend fun executeGroup(message: GroupMessage, args: List<String>) {
if (args.isEmpty()) {
message.reply("发送`成语解释 <成语>`就能解释这个成语的意思了")
return
}
try {
val idiom = args.joinToString(" ").trim()
val response = Http.get<IdiomExplainResponse>(
apiSpaceUrl,
mapOf("name" to idiom, "search_type" to "name"),
mapOf("X-APISpace-Token" to configManager.apiSpaceKey)
).data.first()
val msg = MessageChain.Builder()
.addText("成语: ${response.name} 的解释如下")
.addNewLine()
.addText(response.explanation)
.addNewLine(2)
.addText(response.provenance)
.addNewLine(2)
.addText(response.sound)
.build()
message.reply(msg)
} catch (_: Exception) {
message.reply("这个词似乎不是一个成语呢~~")
}
}
}
@@ -1,92 +0,0 @@
/*
* Copyright © 2024 RTAkland
* Author: RTAkland
* Date: 2024/9/3
*/
package cn.rtast.fancybot.commands.lookup
import cn.rtast.fancybot.annotations.CommandDescription
import cn.rtast.motdpinger.BedrockPing
import cn.rtast.motdpinger.JavaPing
import cn.rtast.motdpinger.removeColorCodes
import cn.rtast.rob.entity.GroupMessage
import cn.rtast.rob.util.BaseCommand
import cn.rtast.rob.util.ob.MessageChain
import cn.rtast.rob.util.ob.OneBotListener
import java.net.SocketException
@CommandDescription("PingMC服务器")
class MCPingCommand : BaseCommand() {
override val commandNames = listOf("/mcping")
override suspend fun executeGroup(listener: OneBotListener, message: GroupMessage, args: List<String>) {
if (args.isEmpty()) {
val msg = MessageChain.Builder()
.addText("使用方法: /mcping <host>:[port] [java|be]")
.addNewLine()
.addText("不添加平台默认Java版~")
.build()
message.reply(msg)
return
}
val platform = if (args.size == 2) args.last() else "java"
when (platform) {
"java", "je", "Java", "JAVA" -> {
val parts = args.first().split(":")
val port = if (parts.size == 1) {
25565
} else {
parts.last().toInt()
}
val host = parts.first()
val response = JavaPing().ping(host, port, 10000)
if (response == null) {
message.reply("无法获取服务器的消息请检查输入是否正确 >>> $host:$port")
return
}
val msg = MessageChain.Builder()
response.favicon?.let {
msg.addImage(response.favicon!!.replace("data:image/png;base64,", ""), true)
}
msg.addText("服务器地址: $host:$port | 服务器类型: Java")
.addNewLine()
.addText("服务器版本: ${response.version.name}/${response.version.protocol}")
.addNewLine()
.addText("在线玩家数: ${response.players.online}/${response.players.max}")
message.reply(msg.build())
}
"be", "bedrock", "BE", "Bedrock" -> {
val parts = args.first().split(":")
val port = if (parts.size == 1) {
19132
} else {
parts.last().toInt()
}
val host = parts.first()
try {
val response = BedrockPing().ping(host, port, 10000)
val msg = MessageChain.Builder()
.addText("服务器地址: $host:$port | 服务器类型: Bedrock")
.addNewLine()
.addText("服务器版本: ${response.version}/${response.protocolVersion}")
.addNewLine()
.addText("在线玩家: ${response.onlinePlayers}/${response.maxPlayers}")
.addNewLine()
.addText("MOTD: ${response.motd.removeColorCodes()}")
.addNewLine()
.addText(response.subTitle.removeColorCodes())
message.reply(msg.build())
} catch (_: SocketException) {
message.reply("无法从服务器接收Ping结果请检查服务器地址是否正确 >>> $host:$port")
}
}
else -> {
message.reply("输入错误请在 `java` 和 `be`中选择 >>> $platform")
}
}
}
}
@@ -16,9 +16,8 @@ import cn.rtast.fancybot.entity.music.qq.QQMSearch
import cn.rtast.fancybot.util.Http import cn.rtast.fancybot.util.Http
import cn.rtast.rob.entity.GroupMessage import cn.rtast.rob.entity.GroupMessage
import cn.rtast.rob.enums.MusicShareType import cn.rtast.rob.enums.MusicShareType
import cn.rtast.rob.onebot.MessageChain
import cn.rtast.rob.util.BaseCommand import cn.rtast.rob.util.BaseCommand
import cn.rtast.rob.util.ob.MessageChain
import cn.rtast.rob.util.ob.OneBotListener
import java.time.Instant import java.time.Instant
@CommandDescription("网易云音乐点歌") @CommandDescription("网易云音乐点歌")
@@ -38,7 +37,7 @@ class MusicCommand : BaseCommand() {
} }
} }
private suspend fun legacyMusicCommand(listener: OneBotListener, message: GroupMessage, args: List<String>) { private suspend fun legacyMusicCommand(message: GroupMessage, args: List<String>) {
if (args.isEmpty()) { if (args.isEmpty()) {
message.reply("发送`/点歌` <关键词> 来搜索音乐~") message.reply("发送`/点歌` <关键词> 来搜索音乐~")
return return
@@ -63,7 +62,7 @@ class MusicCommand : BaseCommand() {
val msg = MessageChain.Builder() val msg = MessageChain.Builder()
.addMusicShare(MusicShareType.Netease, finalResult.id.toString()) .addMusicShare(MusicShareType.Netease, finalResult.id.toString())
.build() .build()
listener.sendGroupMessage(message.groupId, msg) message.reply(msg)
} catch (_: Exception) { } catch (_: Exception) {
message.reply("输入有误请重新搜索本次搜索结果已清除") message.reply("输入有误请重新搜索本次搜索结果已清除")
} }
@@ -93,9 +92,9 @@ class MusicCommand : BaseCommand() {
} }
} }
override suspend fun executeGroup(listener: OneBotListener, message: GroupMessage, args: List<String>) { override suspend fun executeGroup(message: GroupMessage, args: List<String>) {
if (args.last() == "legacy" || args.last() == "l" || args.last() in "0".."4") { if (args.last() == "legacy" || args.last() == "l" || args.last() in "0".."4") {
this.legacyMusicCommand(listener, message, args) this.legacyMusicCommand(message, args)
return return
} }
if (args.isEmpty()) { if (args.isEmpty()) {
@@ -105,7 +104,7 @@ class MusicCommand : BaseCommand() {
.addNewLine() .addNewLine()
.addText("发送`点歌 <歌名> [l|legacy]`即可使用旧版点歌系统") .addText("发送`点歌 <歌名> [l|legacy]`即可使用旧版点歌系统")
.build() .build()
listener.sendGroupMessage(message.groupId, msg) message.reply(msg)
return return
} }
@@ -114,7 +113,7 @@ class MusicCommand : BaseCommand() {
val msg = MessageChain.Builder() val msg = MessageChain.Builder()
.addMusicShare(MusicShareType.Netease, result.toString()) .addMusicShare(MusicShareType.Netease, result.toString())
.build() .build()
listener.sendGroupMessage(message.groupId, msg) message.action.sendGroupMessage(message.groupId, msg)
} }
} }
@@ -128,7 +127,7 @@ class MusicPlayUrlCommand : BaseCommand() {
return Http.get<SongUrl>("$musicApiUrl/song/url", mapOf("id" to id)).data.first().url return Http.get<SongUrl>("$musicApiUrl/song/url", mapOf("id" to id)).data.first().url
} }
override suspend fun executeGroup(listener: OneBotListener, message: GroupMessage, args: List<String>) { override suspend fun executeGroup(message: GroupMessage, args: List<String>) {
val url = if (args.last() == "id" || args.last() == "i") { val url = if (args.last() == "id" || args.last() == "i") {
val id = args.first() val id = args.first()
this.getPlayUrl(id) this.getPlayUrl(id)
@@ -141,7 +140,7 @@ class MusicPlayUrlCommand : BaseCommand() {
.addReply(message.messageId) .addReply(message.messageId)
.addText(url) .addText(url)
.build() .build()
listener.sendGroupMessage(message.groupId, msg) message.reply(msg)
} }
} }
@@ -151,7 +150,7 @@ class QQMusicCommand : BaseCommand() {
private val qqMusicApiUrl = configManager.qqMusicApiUrl private val qqMusicApiUrl = configManager.qqMusicApiUrl
override suspend fun executeGroup(listener: OneBotListener, message: GroupMessage, args: List<String>) { override suspend fun executeGroup(message: GroupMessage, args: List<String>) {
if (args.isEmpty()) { if (args.isEmpty()) {
message.reply("发送`/qm <关键词>`即可搜索qq音乐的音乐哦") message.reply("发送`/qm <关键词>`即可搜索qq音乐的音乐哦")
return return
@@ -162,6 +161,6 @@ class QQMusicCommand : BaseCommand() {
mapOf("key" to keyword) mapOf("key" to keyword)
).response.data.song.list.first().songId ).response.data.song.list.first().songId
val msg = MessageChain.Builder().addMusicShare(MusicShareType.QQ, response.toString()).build() val msg = MessageChain.Builder().addMusicShare(MusicShareType.QQ, response.toString()).build()
listener.sendGroupMessage(message.groupId, msg) message.action.sendGroupMessage(message.groupId, msg)
} }
} }
@@ -1,38 +0,0 @@
/*
* Copyright © 2024 RTAkland
* Author: RTAkland
* Date: 2024/9/6
*/
package cn.rtast.fancybot.commands.lookup
import cn.rtast.fancybot.annotations.CommandDescription
import cn.rtast.rob.entity.GroupMessage
import cn.rtast.rob.util.BaseCommand
import cn.rtast.rob.util.ob.MessageChain
import cn.rtast.rob.util.ob.OneBotListener
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.net.InetAddress
import java.net.UnknownHostException
@CommandDescription("解析域名获取IP地址")
class NslookupCommand : BaseCommand() {
override val commandNames = listOf("/ns")
override suspend fun executeGroup(listener: OneBotListener, message: GroupMessage, args: List<String>) {
val host = args.joinToString(" ")
try {
val msg = MessageChain.Builder()
.addText("域名: ${host}解析后的IP地址为: ")
.addText(withContext(Dispatchers.IO) { InetAddress.getByName(host) }.hostAddress)
.build()
message.reply(msg)
} catch (_: UnknownHostException) {
message.reply("未知的主机名: $host")
} catch (e: Exception) {
message.reply("未知错误: ${e.message}")
}
}
}
@@ -0,0 +1,87 @@
/*
* Copyright © 2025 RTAkland
* Author: RTAkland
* Date: 2025/2/4
*/
package cn.rtast.fancybot.commands.lookup
import cn.rtast.fancybot.annotations.CommandDescription
import cn.rtast.fancybot.configManager
import cn.rtast.fancybot.entity.osu.OSUAccessToken
import cn.rtast.fancybot.entity.osu.OSUUserData
import cn.rtast.fancybot.util.Http
import cn.rtast.rob.entity.GroupMessage
import cn.rtast.rob.onebot.MessageChain
import cn.rtast.rob.util.BaseCommand
import java.awt.Color
import java.awt.RenderingHints
import java.awt.image.BufferedImage
@CommandDescription("osu!")
class OSUCommand : BaseCommand() {
override val commandNames = listOf("/osu")
private val clientSecret = configManager.osuClientSecret
private val clientId = configManager.osuClientId
private fun getAccessToken(): String {
return Http.post<OSUAccessToken>(
"https://osu.ppy.sh/oauth/token",
formBody = mapOf(
"client_id" to clientId, "client_secret" to clientSecret,
"grant_type" to "client_credentials", "scope" to "public"
),
headers = mapOf("Content-Type" to "application/x-www-form-urlencoded", "Accept" to "application/json")
).accessToken
}
private fun getUser(username: String): OSUUserData {
val token = this.getAccessToken()
val resp = Http.get<OSUUserData>(
"https://osu.ppy.sh/api/v2/users/$username/osu",
headers = mapOf("Authorization" to "Bearer $token"),
)
return resp
}
private fun generateProgressBar(percent: Int, length: Int = 10): String {
val completedLength = (percent * length) / 100
val remainingLength = length - completedLength
val progressBar = "".repeat(completedLength) + "=".repeat(remainingLength)
return "[$progressBar] $percent%"
}
private fun formatDuration(seconds: Long): String {
val days = seconds / (24 * 3600)
val hours = (seconds % (24 * 3600)) / 3600
val minutes = (seconds % 3600) / 60
val parts = mutableListOf<String>()
if (days > 0) parts.add("${days}d")
if (hours > 0) parts.add("${hours}h")
if (minutes > 0) parts.add("${minutes}m")
return parts.joinToString(" ")
}
override suspend fun executeGroup(message: GroupMessage, args: List<String>) {
val username = args.first()
val userData = getUser(username)
val msg = MessageChain.Builder()
.addText(userData.username)
.addNewLine()
.addText("等级 | Lv.${userData.statistics.level.current} ${generateProgressBar(userData.statistics.level.progress)}")
.addNewLine()
.addText("排名 | #${userData.statistics.globalRank} / ${userData.countryCode} #${userData.statistics.countryRank}")
.addNewLine()
.addText("准确率 | ${userData.statistics.hitAccuracy}%")
.addNewLine()
.addText("游玩时间 | ${formatDuration(userData.statistics.playTime)}")
.addNewLine()
.addText("游玩次数 | ${userData.statistics.playCount}")
.addNewLine()
.addText("表现分/PP | ${userData.statistics.pp.toInt()}")
.build()
message.reply(msg)
}
}
@@ -13,10 +13,9 @@ import cn.rtast.fancybot.entity.pixiv.Ranking
import cn.rtast.fancybot.util.Http import cn.rtast.fancybot.util.Http
import cn.rtast.fancybot.util.str.encodeToBase64 import cn.rtast.fancybot.util.str.encodeToBase64
import cn.rtast.rob.entity.GroupMessage import cn.rtast.rob.entity.GroupMessage
import cn.rtast.rob.onebot.MessageChain
import cn.rtast.rob.onebot.NodeMessageChain
import cn.rtast.rob.util.BaseCommand import cn.rtast.rob.util.BaseCommand
import cn.rtast.rob.util.ob.MessageChain
import cn.rtast.rob.util.ob.NodeMessageChain
import cn.rtast.rob.util.ob.OneBotListener
import java.net.URI import java.net.URI
@CommandDescription("获取Pixiv上的图片") @CommandDescription("获取Pixiv上的图片")
@@ -26,7 +25,7 @@ class PixivCommand : BaseCommand() {
private val pixivRankingURL = "https://proxy.rtast.cn/https/www.pixiv.net/ranking.php?format=json&mode=daily&p=1" private val pixivRankingURL = "https://proxy.rtast.cn/https/www.pixiv.net/ranking.php?format=json&mode=daily&p=1"
private val imageProxyURL = "https://pixiv.re" private val imageProxyURL = "https://pixiv.re"
override suspend fun executeGroup(listener: OneBotListener, message: GroupMessage, args: List<String>) { override suspend fun executeGroup(message: GroupMessage, args: List<String>) {
if (args.isEmpty()) { if (args.isEmpty()) {
val msg = MessageChain.Builder() val msg = MessageChain.Builder()
.addText("发送`/pixiv <id> | rank` 来进行操作哦~") .addText("发送`/pixiv <id> | rank` 来进行操作哦~")
@@ -56,9 +55,11 @@ class PixivCommand : BaseCommand() {
} catch (_: Exception) { } catch (_: Exception) {
} }
} }
node.addMessageChain(MessageChain.Builder() node.addMessageChain(
.addText("图片来源: Pixiv") MessageChain.Builder()
.build(), configManager.selfId) .addText("图片来源: Pixiv")
.build(), configManager.selfId
)
message.reply(node.build()) message.reply(node.build())
return return
} }
@@ -1,27 +0,0 @@
/*
* Copyright © 2024 RTAkland
* Author: RTAkland
* Date: 2024/8/30
*/
package cn.rtast.fancybot.commands.lookup
import cn.rtast.fancybot.annotations.CommandDescription
import cn.rtast.rob.entity.GroupMessage
import cn.rtast.rob.util.BaseCommand
import cn.rtast.rob.util.ob.MessageChain
import cn.rtast.rob.util.ob.OneBotListener
@CommandDescription("生成二维码")
class QRCodeCommand : BaseCommand() {
override val commandNames = listOf("/qr")
override suspend fun executeGroup(listener: OneBotListener, message: GroupMessage, args: List<String>) {
val content = args.joinToString(" ")
val msg = MessageChain.Builder()
.addImage("https://api.rtast.cn/api/generate_qr?data=$content")
.build()
message.reply(msg)
}
}
@@ -10,10 +10,9 @@ package cn.rtast.fancybot.commands.lookup
import cn.rtast.fancybot.annotations.CommandDescription import cn.rtast.fancybot.annotations.CommandDescription
import cn.rtast.rob.entity.GroupMessage import cn.rtast.rob.entity.GroupMessage
import cn.rtast.rob.enums.ArrayMessageType import cn.rtast.rob.enums.ArrayMessageType
import cn.rtast.rob.onebot.MessageChain
import cn.rtast.rob.onebot.NodeMessageChain
import cn.rtast.rob.util.BaseCommand import cn.rtast.rob.util.BaseCommand
import cn.rtast.rob.util.ob.MessageChain
import cn.rtast.rob.util.ob.NodeMessageChain
import cn.rtast.rob.util.ob.OneBotListener
private fun generateNodeMessage(targetId: String, times: Int = 10): NodeMessageChain { private fun generateNodeMessage(targetId: String, times: Int = 10): NodeMessageChain {
@@ -29,8 +28,8 @@ private fun generateNodeMessage(targetId: String, times: Int = 10): NodeMessageC
class ShotSelfCommand : BaseCommand() { class ShotSelfCommand : BaseCommand() {
override val commandNames = listOf("骂我") override val commandNames = listOf("骂我")
override suspend fun executeGroup(listener: OneBotListener, message: GroupMessage, args: List<String>) { override suspend fun executeGroup(message: GroupMessage, args: List<String>) {
listener.sendGroupForwardMsg(message.groupId, generateNodeMessage(message.sender.userId.toString())) message.action.sendGroupForwardMsg(message.groupId, generateNodeMessage(message.sender.userId.toString()))
} }
} }
@@ -38,14 +37,16 @@ class ShotSelfCommand : BaseCommand() {
class ShotOtherCommand : BaseCommand() { class ShotOtherCommand : BaseCommand() {
override val commandNames = listOf("") override val commandNames = listOf("")
override suspend fun executeGroup(listener: OneBotListener, message: GroupMessage, args: List<String>) { override suspend fun executeGroup(message: GroupMessage, args: List<String>) {
if (args.isEmpty()) { if (args.isEmpty()) {
listener.sendGroupForwardMsg(message.groupId, generateNodeMessage(message.sender.userId.toString(), 20)) message.action.sendGroupForwardMsg(
message.groupId, generateNodeMessage(message.sender.userId.toString(), 20)
)
return return
} }
var times = if (args.size == 1) 10 else if (args.last() == "") 10 else args.last().toInt() var times = if (args.size == 1) 10 else if (args.last() == "") 10 else args.last().toInt()
if (times >= 200) times = 10 if (times >= 200) times = 10
val target = message.message.find { it.type == ArrayMessageType.at }?.data!! val target = message.message.find { it.type == ArrayMessageType.at }?.data!!
listener.sendGroupForwardMsg(message.groupId, generateNodeMessage(target.qq!!, times)) message.action.sendGroupForwardMsg(message.groupId, generateNodeMessage(target.qq!!, times))
} }
} }
@@ -0,0 +1,34 @@
/*
* Copyright © 2024 RTAkland
* Author: RTAkland
* Date: 2024/10/8
*/
package cn.rtast.fancybot.commands.lookup
import cn.rtast.fancybot.annotations.CommandDescription
import cn.rtast.fancybot.entity.thecat.Cat
import cn.rtast.fancybot.util.Http
import cn.rtast.fancybot.util.misc.toURL
import cn.rtast.fancybot.util.str.encodeToBase64
import cn.rtast.fancybot.util.str.fromArrayJson
import cn.rtast.fancybot.util.str.proxy
import cn.rtast.rob.entity.GroupMessage
import cn.rtast.rob.onebot.MessageChain
import cn.rtast.rob.util.BaseCommand
@CommandDescription("随机猫猫图")
class TheCatCommand : BaseCommand() {
override val commandNames = listOf("随机猫咪", "随机猫猫")
private val theCatApiUrl = "https://api.thecatapi.com/v1/images/search"
override suspend fun executeGroup(message: GroupMessage, args: List<String>) {
val imageBase64 = Http.get(theCatApiUrl)
.fromArrayJson<List<Cat>>().first().url
.proxy.toURL().readBytes().encodeToBase64()
val msg = MessageChain.Builder().addImage(imageBase64, true).build()
message.reply(msg)
}
}
@@ -0,0 +1,51 @@
/*
* Copyright © 2024 RTAkland
* Author: RTAkland
* Date: 2024/10/8
*/
package cn.rtast.fancybot.commands.lookup
import cn.rtast.fancybot.annotations.CommandDescription
import cn.rtast.fancybot.configManager
import cn.rtast.fancybot.entity.tianxing.HistoryOfToday
import cn.rtast.fancybot.util.Http
import cn.rtast.rob.entity.GroupMessage
import cn.rtast.rob.onebot.MessageChain
import cn.rtast.rob.onebot.toNode
import cn.rtast.rob.util.BaseCommand
import java.time.LocalDateTime
import java.time.ZoneId
import java.time.format.DateTimeFormatter
@CommandDescription("历史上的今天~~~")
class TheHistoryOfTodayCommand : BaseCommand() {
override val commandNames = listOf("历史上的今天")
private val tianXingApiUrl = "https://apis.tianapi.com/lishi/index"
private val tianXingApiKey = configManager.tianXingApiKey
override suspend fun executeGroup(message: GroupMessage, args: List<String>) {
val currentDateTime = LocalDateTime.now(ZoneId.of("Asia/Shanghai"))
val formatter = DateTimeFormatter.ofPattern("MMdd")
val currentDate = currentDateTime.format(formatter)
val messages = mutableListOf<MessageChain>()
Http.get<HistoryOfToday>(
tianXingApiUrl,
mapOf(
"key" to tianXingApiKey,
"date" to currentDate,
)
).result.list.forEach {
messages.add(
MessageChain.Builder()
.addText("事件: ${it.title}")
.addNewLine()
.addText("发生日期: ${it.lsDate}")
.build()
)
}
message.reply(messages.toNode(configManager.selfId))
}
}
@@ -12,16 +12,15 @@ import cn.rtast.fancybot.configManager
import cn.rtast.fancybot.entity.weather.Geo import cn.rtast.fancybot.entity.weather.Geo
import cn.rtast.fancybot.entity.weather.Weather import cn.rtast.fancybot.entity.weather.Weather
import cn.rtast.fancybot.util.Http import cn.rtast.fancybot.util.Http
import cn.rtast.fancybot.util.Resources import cn.rtast.fancybot.util.misc.Resources
import cn.rtast.fancybot.util.drawCenteredText import cn.rtast.fancybot.util.misc.drawCenteredText
import cn.rtast.fancybot.util.drawCustomImage import cn.rtast.fancybot.util.misc.drawCustomImage
import cn.rtast.fancybot.util.misc.toBufferedImage
import cn.rtast.fancybot.util.misc.toByteArray
import cn.rtast.fancybot.util.str.encodeToBase64 import cn.rtast.fancybot.util.str.encodeToBase64
import cn.rtast.fancybot.util.toBufferedImage
import cn.rtast.fancybot.util.toByteArray
import cn.rtast.rob.entity.GroupMessage import cn.rtast.rob.entity.GroupMessage
import cn.rtast.rob.onebot.MessageChain
import cn.rtast.rob.util.BaseCommand import cn.rtast.rob.util.BaseCommand
import cn.rtast.rob.util.ob.MessageChain
import cn.rtast.rob.util.ob.OneBotListener
import java.awt.Color import java.awt.Color
import java.awt.Font import java.awt.Font
import java.awt.image.BufferedImage import java.awt.image.BufferedImage
@@ -58,13 +57,13 @@ class WeatherCommand : BaseCommand() {
return emptyImage.toByteArray().encodeToBase64() return emptyImage.toByteArray().encodeToBase64()
} }
override suspend fun executeGroup(listener: OneBotListener, message: GroupMessage, args: List<String>) { override suspend fun executeGroup(message: GroupMessage, args: List<String>) {
if (args.isEmpty()) { if (args.isEmpty()) {
val msg = MessageChain.Builder() val msg = MessageChain.Builder()
.addAt(message.sender.userId) .addAt(message.sender.userId)
.addText("发送/weather <地区名>即可查看实时天气哦~") .addText("发送/weather <地区名>即可查看实时天气哦~")
.build() .build()
listener.sendGroupMessage(message.groupId, msg) message.reply(msg)
return return
} }
try { try {
@@ -81,13 +80,13 @@ class WeatherCommand : BaseCommand() {
.addReply(message.messageId) .addReply(message.messageId)
.addImage(this.createWeatherCard(weather, lookupLocation), true) .addImage(this.createWeatherCard(weather, lookupLocation), true)
.build() .build()
listener.sendGroupMessage(message.groupId, msg) message.reply(msg)
} catch (_: NullPointerException) { } catch (_: NullPointerException) {
val msg = MessageChain.Builder() val msg = MessageChain.Builder()
.addAt(message.sender.userId) .addAt(message.sender.userId)
.addText("没有查询到该城市的天气信息哦~") .addText("没有查询到该城市的天气信息哦~")
.build() .build()
listener.sendGroupMessage(message.groupId, msg) message.reply(msg)
} }
} }
} }
@@ -10,11 +10,12 @@ package cn.rtast.fancybot.commands.lookup
import cn.rtast.fancybot.annotations.CommandDescription import cn.rtast.fancybot.annotations.CommandDescription
import cn.rtast.fancybot.entity.wiki.PageInfoResponse import cn.rtast.fancybot.entity.wiki.PageInfoResponse
import cn.rtast.fancybot.entity.wiki.WikipediaResponse import cn.rtast.fancybot.entity.wiki.WikipediaResponse
import cn.rtast.fancybot.enums.CommandAction
import cn.rtast.fancybot.util.Http import cn.rtast.fancybot.util.Http
import cn.rtast.fancybot.util.file.insertActionRecord
import cn.rtast.rob.entity.GroupMessage import cn.rtast.rob.entity.GroupMessage
import cn.rtast.rob.onebot.MessageChain
import cn.rtast.rob.util.BaseCommand import cn.rtast.rob.util.BaseCommand
import cn.rtast.rob.util.ob.MessageChain
import cn.rtast.rob.util.ob.OneBotListener
import org.jsoup.Jsoup import org.jsoup.Jsoup
@CommandDescription("查询wiki百科") @CommandDescription("查询wiki百科")
@@ -23,12 +24,14 @@ class WikipediaCommand : BaseCommand() {
private val wikipediaAPI = "https://proxy.rtast.cn/https/zh.wikipedia.org/w/api.php" private val wikipediaAPI = "https://proxy.rtast.cn/https/zh.wikipedia.org/w/api.php"
private fun String.extractPlainTextFromHtml(): String { companion object {
val doc = Jsoup.parse(this) fun String.extractPlainTextFromHtml(): String {
return doc.text() val doc = Jsoup.parse(this)
return doc.text()
}
} }
override suspend fun executeGroup(listener: OneBotListener, message: GroupMessage, args: List<String>) { override suspend fun executeGroup(message: GroupMessage, args: List<String>) {
if (args.isEmpty()) { if (args.isEmpty()) {
val msg = MessageChain.Builder() val msg = MessageChain.Builder()
.addText("发送`/wiki <关键字>`即可查询出最匹配的wiki页面~") .addText("发送`/wiki <关键字>`即可查询出最匹配的wiki页面~")
@@ -69,6 +72,8 @@ class WikipediaCommand : BaseCommand() {
.addText("没有查询到指定的wiki页面呢 >>> $title") .addText("没有查询到指定的wiki页面呢 >>> $title")
.build() .build()
message.reply(msg) message.reply(msg)
} finally {
insertActionRecord(CommandAction.Wiki, message.sender.userId, title)
} }
} }
} }
@@ -8,25 +8,29 @@
package cn.rtast.fancybot.commands.misc package cn.rtast.fancybot.commands.misc
import cn.rtast.fancybot.annotations.CommandDescription import cn.rtast.fancybot.annotations.CommandDescription
import cn.rtast.fancybot.configManager import cn.rtast.fancybot.enums.CommandAction
import cn.rtast.fancybot.util.file.insertActionRecord
import cn.rtast.rob.entity.ArrayMessage import cn.rtast.rob.entity.ArrayMessage
import cn.rtast.rob.entity.GroupMessage import cn.rtast.rob.entity.GroupMessage
import cn.rtast.rob.enums.ArrayMessageType import cn.rtast.rob.enums.ArrayMessageType
import cn.rtast.rob.util.BaseCommand import cn.rtast.rob.util.BaseCommand
import cn.rtast.rob.util.ob.OneBotListener
@CommandDescription("防撤回(获取消息)") @CommandDescription("防撤回(获取消息)")
class AntiRevokeCommand : BaseCommand() { class AntiRevokeCommand : BaseCommand() {
override val commandNames = listOf("/revoke", "/rv") override val commandNames = listOf("/revoke", "/rv")
override suspend fun executeGroup(listener: OneBotListener, message: GroupMessage, args: List<String>) { override suspend fun executeGroup(message: GroupMessage, args: List<String>) {
if (message.sender.userId !in configManager.admins) message.reply("你不许用防撤回") if (message.sender.isAdmin || message.sender.isOwner) {
val messageId = args.first().toLong() val messageId = args.first().toLong()
val getMsg = listener.getMessage(messageId) val getMsg = message.action.getMessage(messageId)
val msgList = mutableListOf<ArrayMessage>() val msgList = mutableListOf<ArrayMessage>()
msgList.add(ArrayMessage(ArrayMessageType.at, ArrayMessage.Data(qq = getMsg.sender.userId.toString()))) msgList.add(ArrayMessage(ArrayMessageType.at, ArrayMessage.Data(qq = message.sender.userId.toString())))
msgList.add(ArrayMessage(ArrayMessageType.text, ArrayMessage.Data(text = "\n被撤回的消息如下: \n"))) msgList.add(ArrayMessage(ArrayMessageType.text, ArrayMessage.Data(text = "\n被撤回的消息如下: \n")))
msgList.addAll(getMsg.message) msgList.addAll(getMsg.message)
listener.sendGroupMessage(message.groupId, msgList) message.action.sendGroupMessage(message.groupId, msgList)
insertActionRecord(CommandAction.AntiRevoke, message.sender.userId, getMsg.messageId.toString())
} else {
message.reply("你不许用防撤回")
}
} }
} }
@@ -7,17 +7,18 @@
package cn.rtast.fancybot.commands.misc package cn.rtast.fancybot.commands.misc
import cn.rtast.fancybot.annotations.CommandDescription
import cn.rtast.fancybot.configManager import cn.rtast.fancybot.configManager
import cn.rtast.fancybot.entity.bullshit.BullShit import cn.rtast.fancybot.entity.bullshit.BullShit
import cn.rtast.fancybot.util.Resources import cn.rtast.fancybot.util.misc.Resources
import cn.rtast.fancybot.util.str.fromJson import cn.rtast.fancybot.util.str.fromJson
import cn.rtast.rob.entity.GroupMessage import cn.rtast.rob.entity.GroupMessage
import cn.rtast.rob.onebot.MessageChain
import cn.rtast.rob.onebot.NodeMessageChain
import cn.rtast.rob.util.BaseCommand import cn.rtast.rob.util.BaseCommand
import cn.rtast.rob.util.ob.MessageChain
import cn.rtast.rob.util.ob.NodeMessageChain
import cn.rtast.rob.util.ob.OneBotListener
import kotlin.random.Random import kotlin.random.Random
@CommandDescription("狗屁不通文章生成器")
class BullShitGenerateCommand : BaseCommand() { class BullShitGenerateCommand : BaseCommand() {
override val commandNames = listOf("/gpbt", "狗屁不通") override val commandNames = listOf("/gpbt", "狗屁不通")
@@ -45,7 +46,7 @@ class BullShitGenerateCommand : BaseCommand() {
return ".\n\n " return ".\n\n "
} }
override suspend fun executeGroup(listener: OneBotListener, message: GroupMessage, args: List<String>) { override suspend fun executeGroup(message: GroupMessage, args: List<String>) {
val topic = args.joinToString(" ") val topic = args.joinToString(" ")
val nextFamousQuote = shuffleList(bullShitData.famous) val nextFamousQuote = shuffleList(bullShitData.famous)
val nextBosh = shuffleList(bullShitData.bosh) val nextBosh = shuffleList(bullShitData.bosh)
@@ -65,6 +66,6 @@ class BullShitGenerateCommand : BaseCommand() {
.addMessageChain(MessageChain.Builder().addText(finalText).build(), configManager.selfId) .addMessageChain(MessageChain.Builder().addText(finalText).build(), configManager.selfId)
.build() .build()
message.reply(node) message.reply(node)
message.sender.groupPoke() message.sender.poke()
} }
} }
@@ -12,35 +12,42 @@ import cn.rtast.fancybot.entity.compiler.GLOTPayload
import cn.rtast.fancybot.entity.compiler.GLOTResponse import cn.rtast.fancybot.entity.compiler.GLOTResponse
import cn.rtast.fancybot.entity.compiler.KCSPayload import cn.rtast.fancybot.entity.compiler.KCSPayload
import cn.rtast.fancybot.entity.compiler.KCSResponse import cn.rtast.fancybot.entity.compiler.KCSResponse
import cn.rtast.fancybot.enums.CommandAction
import cn.rtast.fancybot.util.Http import cn.rtast.fancybot.util.Http
import cn.rtast.fancybot.util.file.insertActionRecord
import cn.rtast.fancybot.util.str.toJson import cn.rtast.fancybot.util.str.toJson
import cn.rtast.rob.entity.GroupMessage import cn.rtast.rob.entity.GroupMessage
import cn.rtast.rob.onebot.MessageChain
import cn.rtast.rob.util.BaseCommand import cn.rtast.rob.util.BaseCommand
import cn.rtast.rob.util.ob.MessageChain
import cn.rtast.rob.util.ob.OneBotListener
@CommandDescription("执行不同语言的代码片段") @CommandDescription("执行不同语言的代码")
class CompilerCommand : BaseCommand() { class CompilerCommand : BaseCommand() {
override val commandNames = listOf("/compiler", "/exec") override val commandNames = listOf("/compiler", "/exec", "/e")
private val kotlinCompilerServer = "https://api.kotlinlang.org/api/2.0.20/compiler/run" private val kotlinCompilerServer = "https://api.kotlinlang.org/api/2.0.20/compiler/run"
private val glotCompilerServer = "https://glot.io/run" private val glotCompilerServer = "https://glot.io/run"
override suspend fun executeGroup(listener: OneBotListener, message: GroupMessage, args: List<String>) { override suspend fun executeGroup(message: GroupMessage, args: List<String>) {
if (args.isEmpty()) { if (args.isEmpty()) {
message.reply("使用`/exec` <语言> <代码> 即可执行~") message.reply("使用`/exec` <语言> <代码> 即可执行~")
return return
} }
val language = args.first() var language = args.first().lowercase()
val code = args.drop(1).joinToString(" ") val code = args.drop(1).joinToString(" ")
val response = when (language) { val response = when (language) {
"kotlin", "Kotlin", "kt", "Kt", "KT" -> { "kotlin", "kt" -> {
Http.post<KCSResponse>( Http.post<KCSResponse>(
kotlinCompilerServer, jsonBody = KCSPayload(listOf(KCSPayload.File(code))).toJson() kotlinCompilerServer,
jsonBody = KCSPayload(listOf(KCSPayload.File(code))).toJson()
).text ).text
} }
else -> { else -> {
if (language == "js") language = "javascript"
if (language == "cs") language = "csharp"
if (language == "sh") language = "bash"
if (language == "asm") language = "assembly"
if (language == "py") language = "python"
Http.post<GLOTResponse>( Http.post<GLOTResponse>(
"$glotCompilerServer/$language", "$glotCompilerServer/$language",
params = mapOf("version" to "latest"), params = mapOf("version" to "latest"),
@@ -48,12 +55,18 @@ class CompilerCommand : BaseCommand() {
).stdout ).stdout
} }
} }
val msg = MessageChain.Builder() try {
.addReply(message.messageId) val msg = MessageChain.Builder()
.addText("执行结果如下:") .addReply(message.messageId)
.addNewLine() .addText("执行结果如下:")
.addText(response.replace("<outStream>", "").replace("</outStream>", "")) .addNewLine()
.build() .addText(response.replace("<outStream>", "").replace("</outStream>", ""))
listener.sendGroupMessage(message.groupId, msg) .build()
message.reply(msg)
} catch (e: Exception) {
message.reply("执行错误: ${e.message}")
} finally {
insertActionRecord(CommandAction.Compiler, message.sender.userId)
}
} }
} }
@@ -0,0 +1,27 @@
/*
* Copyright © 2024 RTAkland
* Author: RTAkland
* Date: 2024/10/9
*/
package cn.rtast.fancybot.commands.misc
import cn.rtast.fancybot.annotations.CommandDescription
import cn.rtast.rob.entity.GroupMessage
import cn.rtast.rob.util.BaseCommand
import java.time.LocalDate
import java.time.temporal.ChronoUnit
@CommandDescription("高考倒计时")
class GaoKaoDaysRemainCommand : BaseCommand() {
override val commandNames = listOf("高考倒计时")
override suspend fun executeGroup(message: GroupMessage, args: List<String>) {
val gaoKaoDate = LocalDate.of(LocalDate.now().year, 6, 7)
val currentDate = LocalDate.now()
val adjustedGaoKaoDate = if (currentDate.isAfter(gaoKaoDate)) gaoKaoDate.plusYears(1) else gaoKaoDate
val daysBetween = ChronoUnit.DAYS.between(currentDate, adjustedGaoKaoDate)
message.reply("高考倒计时: ${daysBetween}")
}
}
@@ -0,0 +1,45 @@
/*
* Copyright © 2025 RTAkland
* Author: RTAkland
* Date: 2025/2/20
*/
package cn.rtast.fancybot.commands.misc
import cn.rtast.fancybot.util.misc.Resources
import cn.rtast.fancybot.util.misc.drawCenteredText
import cn.rtast.fancybot.util.misc.toByteArray
import cn.rtast.fancybot.util.str.encodeToBase64
import cn.rtast.rob.entity.GroupMessage
import cn.rtast.rob.segment.Image
import cn.rtast.rob.util.BaseCommand
import java.awt.Color
import java.awt.Font
import java.awt.RenderingHints
import javax.imageio.ImageIO
class GoodNewsCommand: BaseCommand() {
override val commandNames = listOf("xb", "喜报")
private val goodNewsImageBytes = Resources.loadFromResourcesAsBytes("misc/good_news.png")
private val font = Font("SimSun", Font.BOLD, 130)
private fun generateImage(content: String): String {
val img = ImageIO.read(goodNewsImageBytes!!.inputStream())
val g2d = img.createGraphics()
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON)
g2d.color = Color.RED
g2d.font = font
g2d.drawCenteredText(content, 650, 500)
g2d.dispose()
val imgBase64 = img.toByteArray().encodeToBase64()
return imgBase64
}
override suspend fun executeGroup(message: GroupMessage, args: List<String>) {
val content = args.joinToString(" ")
val imgBase64 = generateImage(content)
message.reply(Image(imgBase64, true))
}
}
@@ -0,0 +1,34 @@
/*
* Copyright © 2024 RTAkland
* Author: RTAkland
* Date: 2024/10/23
*/
package cn.rtast.fancybot.commands.misc
import cn.rtast.fancybot.annotations.CommandDescription
import cn.rtast.fancybot.enums.HttpStatusCode
import cn.rtast.fancybot.util.misc.Resources
import cn.rtast.fancybot.util.str.encodeToBase64
import cn.rtast.rob.entity.GroupMessage
import cn.rtast.rob.onebot.MessageChain
import cn.rtast.rob.util.BaseCommand
@CommandDescription("随机HTTP猫猫")
class HTTPCatCommand : BaseCommand() {
override val commandNames = listOf("http猫猫", "HTTP猫猫")
private val notFoundCatBase64 = Resources.loadFromResourcesAsBytes("httpcat/0.jpg")!!.encodeToBase64()
private fun loadHTTPCat(code: String): String {
return Resources.loadFromResourcesAsBytes("httpcat/${code}.jpg")?.encodeToBase64() ?: notFoundCatBase64
}
override suspend fun executeGroup(message: GroupMessage, args: List<String>) {
val statusCode = if (args.isEmpty()) HttpStatusCode.entries.random().code.toString() else args.first()
val image = loadHTTPCat(statusCode)
val msg = MessageChain.Builder().addImage(image, true).build()
message.reply(msg)
}
}
@@ -0,0 +1,34 @@
/*
* Copyright © 2024 RTAkland
* Author: RTAkland
* Date: 2024/10/23
*/
package cn.rtast.fancybot.commands.misc
import cn.rtast.fancybot.annotations.CommandDescription
import cn.rtast.fancybot.enums.HttpStatusCode
import cn.rtast.fancybot.util.misc.Resources
import cn.rtast.fancybot.util.str.encodeToBase64
import cn.rtast.rob.entity.GroupMessage
import cn.rtast.rob.onebot.MessageChain
import cn.rtast.rob.util.BaseCommand
@CommandDescription("随机HTTP狗狗")
class HTTPDogCommand : BaseCommand() {
override val commandNames = listOf("http狗狗", "HTTP狗狗")
private val notFoundCatBase64 = Resources.loadFromResourcesAsBytes("httpdog/999.jpg")!!.encodeToBase64()
private fun loadHTTPDog(code: String): String {
return Resources.loadFromResourcesAsBytes("httpdog/${code}.jpg")?.encodeToBase64() ?: notFoundCatBase64
}
override suspend fun executeGroup(message: GroupMessage, args: List<String>) {
val statusCode = if (args.isEmpty()) HttpStatusCode.entries.random().code.toString() else args.first()
val image = loadHTTPDog(statusCode)
val msg = MessageChain.Builder().addImage(image, true).build()
message.reply(msg)
}
}
@@ -11,15 +11,14 @@ import cn.rtast.fancybot.annotations.CommandDescription
import cn.rtast.fancybot.entity.Hitokoto import cn.rtast.fancybot.entity.Hitokoto
import cn.rtast.fancybot.util.Http import cn.rtast.fancybot.util.Http
import cn.rtast.rob.entity.GroupMessage import cn.rtast.rob.entity.GroupMessage
import cn.rtast.rob.onebot.MessageChain
import cn.rtast.rob.util.BaseCommand import cn.rtast.rob.util.BaseCommand
import cn.rtast.rob.util.ob.MessageChain
import cn.rtast.rob.util.ob.OneBotListener
@CommandDescription("一言") @CommandDescription("一言")
class HitokotoCommand : BaseCommand() { class HitokotoCommand : BaseCommand() {
override val commandNames = listOf("/1") override val commandNames = listOf("/1")
override suspend fun executeGroup(listener: OneBotListener, message: GroupMessage, args: List<String>) { override suspend fun executeGroup(message: GroupMessage, args: List<String>) {
val response = Http.get<Hitokoto>("https://v1.hitokoto.cn/?c=b") val response = Http.get<Hitokoto>("https://v1.hitokoto.cn/?c=b")
val msg = MessageChain.Builder() val msg = MessageChain.Builder()
.addText("${response.sentence}⌉ ---《${response.from}") .addText("${response.sentence}⌉ ---《${response.from}")
@@ -10,9 +10,8 @@ package cn.rtast.fancybot.commands.misc
import cn.rtast.fancybot.annotations.CommandDescription import cn.rtast.fancybot.annotations.CommandDescription
import cn.rtast.fancybot.util.file.JrrpManager import cn.rtast.fancybot.util.file.JrrpManager
import cn.rtast.rob.entity.GroupMessage import cn.rtast.rob.entity.GroupMessage
import cn.rtast.rob.onebot.MessageChain
import cn.rtast.rob.util.BaseCommand import cn.rtast.rob.util.BaseCommand
import cn.rtast.rob.util.ob.MessageChain
import cn.rtast.rob.util.ob.OneBotListener
@CommandDescription("今日人品~") @CommandDescription("今日人品~")
class JrrpCommand : BaseCommand() { class JrrpCommand : BaseCommand() {
@@ -20,13 +19,13 @@ class JrrpCommand : BaseCommand() {
private val jrrpManager = JrrpManager() private val jrrpManager = JrrpManager()
override suspend fun executeGroup(listener: OneBotListener, message: GroupMessage, args: List<String>) { override suspend fun executeGroup(message: GroupMessage, args: List<String>) {
if (jrrpManager.isJrrped(message.sender.userId)) { if (jrrpManager.isJrrped(message.sender.userId)) {
val msg = MessageChain.Builder() val msg = MessageChain.Builder()
.addAt(message.sender.userId) .addAt(message.sender.userId)
.addText("你今天已经今日人品过啦, 明天再来吧~") .addText("你今天已经今日人品过啦, 明天再来吧~")
.build() .build()
listener.sendGroupMessage(message.groupId, msg) message.reply(msg)
return return
} }
val randomPoint = jrrpManager.jrrp(message.sender.userId) val randomPoint = jrrpManager.jrrp(message.sender.userId)
@@ -41,6 +40,6 @@ class JrrpCommand : BaseCommand() {
.addAt(message.sender.userId) .addAt(message.sender.userId)
.addText("$scoreDesc($randomPoint)") .addText("$scoreDesc($randomPoint)")
.build() .build()
listener.sendGroupMessage(message.groupId, msg) message.reply(msg)
} }
} }
@@ -8,16 +8,15 @@
package cn.rtast.fancybot.commands.misc package cn.rtast.fancybot.commands.misc
import cn.rtast.fancybot.annotations.CommandDescription import cn.rtast.fancybot.annotations.CommandDescription
import cn.rtast.fancybot.util.Resources import cn.rtast.fancybot.util.misc.Resources
import cn.rtast.fancybot.util.drawCircularImage import cn.rtast.fancybot.util.misc.drawCircularImage
import cn.rtast.fancybot.util.makeGif import cn.rtast.fancybot.util.misc.makeGif
import cn.rtast.fancybot.util.misc.toBufferedImage
import cn.rtast.fancybot.util.str.encodeToBase64 import cn.rtast.fancybot.util.str.encodeToBase64
import cn.rtast.fancybot.util.toBufferedImage
import cn.rtast.rob.entity.GroupMessage import cn.rtast.rob.entity.GroupMessage
import cn.rtast.rob.enums.ArrayMessageType import cn.rtast.rob.enums.ArrayMessageType
import cn.rtast.rob.onebot.MessageChain
import cn.rtast.rob.util.BaseCommand import cn.rtast.rob.util.BaseCommand
import cn.rtast.rob.util.ob.MessageChain
import cn.rtast.rob.util.ob.OneBotListener
import com.madgag.gif.fmsware.GifDecoder import com.madgag.gif.fmsware.GifDecoder
import java.awt.image.BufferedImage import java.awt.image.BufferedImage
import java.net.URI import java.net.URI
@@ -26,12 +25,14 @@ import java.net.URI
class JueCommand : BaseCommand() { class JueCommand : BaseCommand() {
override val commandNames = listOf("") override val commandNames = listOf("")
private val avatarUrl = "https://q1.qlogo.cn/g?b=qq&nk=#{}&s=640" companion object {
const val AVATAR_REPLACE_URL = "https://q1.qlogo.cn/g?b=qq&nk=#{}&s=640"
}
private fun createJueGIF(source: String, target: String): String { private fun createJueGIF(source: String, target: String): String {
val baseGIF = Resources.loadFromResources("misc/jue.gif") val baseGIF = Resources.loadFromResources("misc/jue.gif")
val sourceAvatar = URI(avatarUrl.replace("#{}", source)).toURL().readBytes().toBufferedImage() val sourceAvatar = URI(AVATAR_REPLACE_URL.replace("#{}", source)).toURL().readBytes().toBufferedImage()
val targetAvatar = URI(avatarUrl.replace("#{}", target)).toURL().readBytes().toBufferedImage() val targetAvatar = URI(AVATAR_REPLACE_URL.replace("#{}", target)).toURL().readBytes().toBufferedImage()
val decoder = GifDecoder() val decoder = GifDecoder()
decoder.read(baseGIF) decoder.read(baseGIF)
val frames = mutableListOf<BufferedImage>() val frames = mutableListOf<BufferedImage>()
@@ -57,11 +58,11 @@ class JueCommand : BaseCommand() {
return gif return gif
} }
override suspend fun executeGroup(listener: OneBotListener, message: GroupMessage, args: List<String>) { override suspend fun executeGroup(message: GroupMessage, args: List<String>) {
val target = message.message.find { it.type == ArrayMessageType.at }?.data?.qq.toString() val target = message.message.find { it.type == ArrayMessageType.at }?.data?.qq.toString()
val source = message.sender.userId.toString() val source = message.sender.userId.toString()
val gif = this.createJueGIF(source, target) val gif = this.createJueGIF(source, target)
val msg = MessageChain.Builder().addImage(gif, true).build() val msg = MessageChain.Builder().addImage(gif, true).build()
listener.sendGroupMessage(message.groupId, msg) message.reply(msg)
} }
} }
@@ -12,7 +12,6 @@ import cn.rtast.fancybot.entity.KFC
import cn.rtast.fancybot.util.Http import cn.rtast.fancybot.util.Http
import cn.rtast.rob.entity.GroupMessage import cn.rtast.rob.entity.GroupMessage
import cn.rtast.rob.util.BaseCommand import cn.rtast.rob.util.BaseCommand
import cn.rtast.rob.util.ob.OneBotListener
import java.time.DayOfWeek import java.time.DayOfWeek
import java.time.Instant import java.time.Instant
import java.time.LocalDateTime import java.time.LocalDateTime
@@ -22,16 +21,17 @@ import java.time.ZoneId
class KFCCommand : BaseCommand() { class KFCCommand : BaseCommand() {
override val commandNames = listOf("/fkxqs", "/kfc") override val commandNames = listOf("/fkxqs", "/kfc")
private fun isThursday(): Boolean { companion object {
val dateTime = LocalDateTime.ofInstant( fun isThursday(): Boolean {
Instant.now(), val dateTime = LocalDateTime.ofInstant(
ZoneId.of("Asia/Shanghai") Instant.now(),
) ZoneId.of("Asia/Shanghai")
return dateTime.dayOfWeek == DayOfWeek.THURSDAY )
return dateTime.dayOfWeek == DayOfWeek.THURSDAY
}
} }
override suspend fun executeGroup(message: GroupMessage, args: List<String>) {
override suspend fun executeGroup(listener: OneBotListener, message: GroupMessage, args: List<String>) {
if (!isThursday()) { if (!isThursday()) {
message.reply("很可惜今天并不是星期四~") message.reply("很可惜今天并不是星期四~")
return return
@@ -0,0 +1,47 @@
/*
* Copyright © 2025 RTAkland
* Author: RTAkland
* Date: 2025/2/21
*/
package cn.rtast.fancybot.commands.misc
import cn.rtast.fancybot.annotations.CommandDescription
import cn.rtast.fancybot.util.misc.Resources
import cn.rtast.fancybot.util.misc.drawCenteredText
import cn.rtast.fancybot.util.misc.toByteArray
import cn.rtast.fancybot.util.str.encodeToBase64
import cn.rtast.rob.entity.GroupMessage
import cn.rtast.rob.segment.Image
import cn.rtast.rob.util.BaseCommand
import java.awt.Color
import java.awt.Font
import java.awt.RenderingHints
import javax.imageio.ImageIO
@CommandDescription("没惹任何人")
class MRRHRCommand : BaseCommand() {
override val commandNames = listOf("/mr")
private val mrrhrImageBytes = Resources.loadFromResourcesAsBytes("misc/mrrhr.png")
private val font = Font("SimSun", Font.BOLD, 60)
private fun generateImage(content: String): String {
val img = ImageIO.read(mrrhrImageBytes!!.inputStream())
val g2d = img.createGraphics()
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON)
g2d.color = Color.BLACK
g2d.font = font
g2d.drawCenteredText(content, 315, 466)
g2d.dispose()
val imgBase64 = img.toByteArray().encodeToBase64()
return imgBase64
}
override suspend fun executeGroup(message: GroupMessage, args: List<String>) {
val content = args.joinToString(" ")
val image = generateImage(content)
message.reply(Image(image, true))
}
}
@@ -0,0 +1,669 @@
/*
* Copyright © 2024 RTAkland
* Author: RTAkland
* Date: 2024/10/9
*/
@file:Suppress("KDocUnresolvedReference", "unused")
package cn.rtast.fancybot.commands.misc
import cn.rtast.fancybot.annotations.CommandDescription
import cn.rtast.fancybot.commands.lookup.NslookupCommand
import cn.rtast.fancybot.commands.lookup.WikipediaCommand.Companion.extractPlainTextFromHtml
import cn.rtast.fancybot.configManager
import cn.rtast.fancybot.db.deleteAccessTokenById
import cn.rtast.fancybot.db.getAccessTokenById
import cn.rtast.fancybot.db.insertNewAccessToken
import cn.rtast.fancybot.entity.mc.DecodedSkin
import cn.rtast.fancybot.entity.mc.MCVersion
import cn.rtast.fancybot.entity.mc.MCWikiPageResponse
import cn.rtast.fancybot.entity.mc.MCWikiSearchResponse
import cn.rtast.fancybot.entity.mc.Skin
import cn.rtast.fancybot.entity.mc.Username
import cn.rtast.fancybot.entity.mc.oauth.DeviceCodeResponse
import cn.rtast.fancybot.entity.mc.oauth.LoginXboxLivePayload
import cn.rtast.fancybot.entity.mc.oauth.LoginXboxLiveResponse
import cn.rtast.fancybot.entity.mc.oauth.ObtainXSTSPayload
import cn.rtast.fancybot.entity.mc.oauth.ObtainXSTSResponse
import cn.rtast.fancybot.entity.mc.oauth.RedeemCodeResponse
import cn.rtast.fancybot.enums.mc.MCVersionType
import cn.rtast.fancybot.rconManager
import cn.rtast.fancybot.util.Http
import cn.rtast.fancybot.util.Logger
import cn.rtast.fancybot.util.mcbot.MCClient
import cn.rtast.fancybot.util.misc.isFullyTransparent
import cn.rtast.fancybot.util.misc.scaleImage
import cn.rtast.fancybot.util.misc.toByteArray
import cn.rtast.fancybot.util.misc.toURL
import cn.rtast.fancybot.util.str.convertToHTML
import cn.rtast.fancybot.util.str.decodeToString
import cn.rtast.fancybot.util.str.encodeToBase64
import cn.rtast.fancybot.util.str.fromJson
import cn.rtast.fancybot.util.str.generateRandomString
import cn.rtast.fancybot.util.str.toJson
import cn.rtast.fancybot.util.str.uriEncode
import cn.rtast.motdpinger.BedrockPing
import cn.rtast.motdpinger.JavaPing
import cn.rtast.rcon.exceptions.AuthFailedException
import cn.rtast.rob.entity.GroupMessage
import cn.rtast.rob.entity.PrivateMessage
import cn.rtast.rob.onebot.MessageChain
import cn.rtast.rob.onebot.toNode
import cn.rtast.rob.segment.NewLine
import cn.rtast.rob.segment.Text
import cn.rtast.rob.segment.toMessageChain
import cn.rtast.rob.util.BaseCommand
import okhttp3.FormBody
import java.awt.AlphaComposite
import java.awt.Color
import java.awt.image.BufferedImage
import java.net.SocketException
import java.net.URI
import java.util.concurrent.Executors
import javax.imageio.ImageIO
import kotlin.random.Random
import kotlin.text.split
@CommandDescription("获取MC最新的版本!")
class MCVersionCommand : BaseCommand() {
override val commandNames = listOf("mc版本")
private val javaEditionAPI = "https://launchermeta.mojang.com/mc/game/version_manifest.json"
override suspend fun executeGroup(message: GroupMessage, args: List<String>) {
val mcVersion = Http.get<MCVersion>(javaEditionAPI)
val msg = MessageChain.Builder()
.addText("最新正式版: ${mcVersion.latest.release}")
.addNewLine()
.addText("发布时间: ${mcVersion.versions.find { it.type == MCVersionType.release }?.releaseTime}")
.addNewLine()
.addText("最新测试版: ${mcVersion.latest.snapshot}")
.addNewLine()
.addText("测试版发布时间: ${mcVersion.versions.find { it.type == MCVersionType.snapshot }?.releaseTime}")
.build()
message.reply(msg)
}
}
@CommandDescription("查询来自minecraft.wiki中的wiki页面")
class MinecraftWikiCommand : BaseCommand() {
override val commandNames = listOf("/mcwiki")
private val baseApiUrl = "https://zh.minecraft.wiki/rest.php/v1"
private val logger = Logger.getLogger<MinecraftWikiCommand>()
override suspend fun executeGroup(message: GroupMessage, args: List<String>) {
val action = args.first()
when (action) {
"s", "搜索" -> {
val searchKeyword = args.drop(1).joinToString(" ").trim()
val messages = mutableListOf<MessageChain>()
val headerMessage = MessageChain.Builder().addText("结果如下:").build()
messages.add(headerMessage)
Http.get<MCWikiSearchResponse>(
"$baseApiUrl/search/title",
mapOf("q" to searchKeyword, "limit" to 10)
).pages.forEach {
val msg = MessageChain.Builder()
val imageUrl = it.thumbnail
if (imageUrl != null) {
val imageBase64 = imageUrl.url.toURL().readBytes().encodeToBase64()
msg.addImage(imageBase64, true)
}
msg.addText("标题: ${it.title}")
.addNewLine()
.addText("ID: ${it.id}")
.addNewLine()
.addText("https://zh.minecraft.wiki/w/${it.title.uriEncode}")
messages.add(msg.build())
}
message.reply(messages.toNode(configManager.selfId))
}
else -> {
val title = args.joinToString(" ").trim()
var response = Http.get<MCWikiPageResponse>("$baseApiUrl/page/$title")
if (response.httpCode != null) {
logger.info("页面($title)不存在")
message.reply("页面($title)不存在")
return
}
if (response.redirectTarget != null) {
logger.info("重定向Wiki页面到: ${response.redirectTarget}")
response = Http.get<MCWikiPageResponse>(response.redirectTarget)
}
val messages = mutableListOf<MessageChain>()
val headerMsg = MessageChain.Builder().addText("标题${response.title}的内容如下").build()
val footerMsg = listOf(
Text("数据来源: https://zh.minecraft.wiki"),
NewLine(),
Text("协议: ${response.license.title}(${response.license.url})")
).toMessageChain()
val bodyMsg = MessageChain.Builder()
.addText(response.source.convertToHTML().extractPlainTextFromHtml())
.build()
messages.add(headerMsg)
messages.add(bodyMsg)
messages.add(footerMsg)
message.reply(messages.toNode(configManager.selfId))
}
}
}
}
@CommandDescription("帮助你寻找指定空置域大小并且在某个区块范围内的最多史莱姆区块范围")
class SlimeChunkHelperCommand : BaseCommand() {
override val commandNames = listOf("/slime")
private fun isSlimeChunk(worldSeed: Long, chunkX: Int, chunkZ: Int): Boolean {
val rng = Random(
worldSeed + (chunkX * chunkX) * 0x4C1906 +
(chunkX * 0x5AC0DB) + (chunkZ * chunkZ) * 0x4307A7L +
(chunkZ * 0x5F24F) xor 0x3AD8025FL
)
return rng.nextInt(10) == 0
}
private fun countSlimeChunksInRegion(xStart: Int, zStart: Int, chunkSize: Int, worldSeed: Long): Int {
var count = 0
for (x in xStart until xStart + chunkSize) {
for (z in zStart until zStart + chunkSize) {
if (isSlimeChunk(worldSeed, x, z)) {
count++
}
}
}
return count
}
private fun findMaxSlimeChunks(
worldSeed: Long,
searchRange: Int,
chunkSize: Int,
): Pair<Pair<Pair<Int, Int>, Pair<Int, Int>>, Int> {
var maxCount = 0
var bestStartLocation = Pair(0, 0)
var bestEndLocation = Pair(0, 0)
for (x in -searchRange until searchRange - chunkSize + 1) {
for (z in -searchRange until searchRange - chunkSize + 1) {
val count = countSlimeChunksInRegion(x, z, chunkSize, worldSeed)
if (count > maxCount) {
maxCount = count
bestStartLocation = Pair(x, z)
bestEndLocation = Pair(x + chunkSize - 1, z + chunkSize - 1)
}
}
}
return Pair(Pair(bestStartLocation, bestEndLocation), maxCount)
}
private fun generateSlimeChunkImage(startX: Int, startZ: Int, endX: Int, endZ: Int, worldSeed: Long): String {
val width = endX - startX + 1
val height = endZ - startZ + 1
val pixelSize = 15
val imageWidth = width * pixelSize
val imageHeight = height * pixelSize
val image = BufferedImage(imageWidth, imageHeight, BufferedImage.TYPE_INT_RGB)
val graphics = image.graphics
val borderColor = Color.LIGHT_GRAY
for (x in startX..endX) {
for (z in startZ..endZ) {
val isSlime = isSlimeChunk(worldSeed, x, z)
val fillColor = if (isSlime) Color.PINK else Color.WHITE
val px = (x - startX) * pixelSize
val pz = (z - startZ) * pixelSize
graphics.color = fillColor
graphics.fillRect(px, pz, pixelSize, pixelSize)
graphics.color = borderColor
graphics.drawRect(px, pz, pixelSize, pixelSize)
}
}
graphics.dispose()
return image.toByteArray().encodeToBase64()
}
override suspend fun executeGroup(message: GroupMessage, args: List<String>) {
if (args.isEmpty()) {
val msg = MessageChain.Builder()
.addText("此指令可以借助一个种子来帮助查找最适合史莱姆农场的位置")
.addNewLine()
.addText("如果不指定史莱姆农场的宽度的话默认为17x17区块大小")
.addNewLine()
.addText("不指定搜索区块范围默认为X: 500~-500, Y: 500~-500, 共计搜索100万个区块")
.addNewLine()
.addText("还可以指定空置域的大小, 不指定默认为17x17")
.addNewLine()
.addText("你可以使用`/slime <种子> [范围(例如600)] [空置域大小]`")
.build()
message.reply(msg)
return
}
val seed = args.first().toLong()
val slimeFarmSize = if (args.size == 1) 17 else args[1].toInt()
val range = if (args.size == 1) 500 else args[2].toInt()
val (location, slimeCount) = findMaxSlimeChunks(seed, range, slimeFarmSize)
val startX = location.first.first
val startZ = location.first.second
val endX = location.second.first
val endZ = location.second.second
val image = generateSlimeChunkImage(startX, startZ, endX, endZ, seed)
val msg = MessageChain.Builder()
.addText("起始区块坐标X:$startX Y:$startZ")
.addNewLine()
.addText("结束区块坐标X:$endX Y:$endZ")
.addNewLine()
.addText("共计史莱姆区块数量: $slimeCount")
.addImage(image, true)
.build()
message.reply(msg)
}
}
@CommandDescription("通过游戏名称或UUID来获取他的皮肤")
class MCSkinCommand : BaseCommand() {
override val commandNames = listOf("/skin")
private val uuidQueryUrl = "https://api.mojang.com/users/profiles/minecraft"
private val skinUrl = "https://sessionserver.mojang.com/session/minecraft/profile"
private fun String.isUsername(): Boolean {
val usernamePattern = Regex("^[a-zA-Z][a-zA-Z0-9_]{2,15}$")
return usernamePattern.matches(this)
}
/**
* 截取整张皮肤中的头部部分, 如果是单层皮肤则直接截取脸部
* 如果是双层皮肤也就将脸部作为背景图层, 将头发绘制在脸部上
* 代码来自
* `[YeeeesMOTD]
* (https://github.com/RTAkland/YeeeesMOTD/blob/main/core/src/main/kotlin/cn/rtast/yeeeesmotd/utils/SkinHeadUtil.kt)`
*/
private fun getSkinHead(skinUrl: String): String {
val url = URI(skinUrl).toURL()
val image = ImageIO.read(url)
var subImage = image.getSubimage(8, 8, 8, 8)
if (subImage.isFullyTransparent()) {
subImage = image.getSubimage(40, 8, 8, 8)
} else {
val hairLayer = image.getSubimage(40, 8, 8, 8)
val combined = BufferedImage(subImage.width, subImage.height, BufferedImage.TYPE_INT_ARGB)
val g2d = combined.createGraphics()
g2d.drawImage(subImage, 0, 0, null)
g2d.composite = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 1.0f)
g2d.drawImage(hairLayer, 0, 0, null)
g2d.dispose()
subImage = combined
}
val zoom = subImage.scaleImage(128 to 128)
return zoom.toByteArray().encodeToBase64()
}
override suspend fun executeGroup(message: GroupMessage, args: List<String>) {
if (args.isEmpty()) {
message.reply("发送`/skin <uuid/游戏名称>`即可查询他的皮肤~ (仅限Java版)")
return
}
try {
val nameOrUUID = args.first().trim()
val uuid = if (nameOrUUID.isUsername()) Http.get<Username>("$uuidQueryUrl/$nameOrUUID").id
else nameOrUUID
val skinUrl = Http.get<Skin>("$skinUrl/$uuid")
.properties.first().value.decodeToString().fromJson<DecodedSkin>()
.textures.skin.url
val headBase64 = this.getSkinHead(skinUrl)
val msg = MessageChain.Builder()
.addImage(headBase64, true)
.addText(skinUrl)
.build()
message.reply(msg)
} catch (_: Exception) {
message.reply("输入错误, 检查一下是否输入正确吧~")
}
}
}
@CommandDescription("PingMC服务器")
class MCPingCommand : BaseCommand() {
override val commandNames = listOf("/mcping")
private fun String.removeColorCodes(): String {
return this.replace(Regex("§."), "")
}
override suspend fun executeGroup(message: GroupMessage, args: List<String>) {
if (args.isEmpty()) {
val msg = MessageChain.Builder()
.addText("使用方法: /mcping <host>:[port] [java|be]")
.addNewLine()
.addText("不添加平台默认Java版~")
.build()
message.reply(msg)
return
}
val platform = if (args.size == 2) args.last() else "java"
when (platform) {
"java", "je", "Java", "JAVA" -> {
val parts = args.first().split(":")
var port = if (parts.size == 1) 25565 else parts.last().toInt()
var host = parts.first()
val srv = NslookupCommand.srv(host)
if (srv.isNotEmpty()) {
val srv = srv.first().split(" ")
port = srv[2].toInt()
host = srv.last()
}
val response = JavaPing().ping(host, port, 10000)
if (response == null) {
message.reply("无法获取服务器的消息请检查输入是否正确 >>> $host:$port")
return
}
val msg = MessageChain.Builder()
response.favicon?.let { msg.addImage(response.favicon!!.replace("data:image/png;base64,", ""), true) }
msg.addText("服务器地址: $host:$port")
.addNewLine()
.addText("服务器类型: Java | 延迟: ${response.latency}ms")
.addNewLine()
.addText("服务器版本: ${response.version.name}/${response.version.protocol}")
.addNewLine()
.addText("在线玩家数: ${response.players.online}/${response.players.max}")
message.reply(msg.build())
}
"be", "bedrock", "BE", "Bedrock" -> {
val parts = args.first().split(":")
val port = if (parts.size == 1) 19132 else parts.last().toInt()
val host = parts.first()
try {
val response = BedrockPing().ping(host, port, 10000)
val msg = MessageChain.Builder()
.addText("服务器地址: $host:$port")
.addNewLine()
.addText("服务器类型: Bedrock(基岩版) | 延迟: ${response.latency}ms")
.addNewLine()
.addText("服务器版本: ${response.version}/${response.protocolVersion}")
.addNewLine()
.addText("在线玩家: ${response.onlinePlayers}/${response.maxPlayers}")
.addNewLine()
.addText("MOTD: ${response.motd.removeColorCodes()}")
.addNewLine()
.addText(response.subTitle.removeColorCodes())
message.reply(msg.build())
} catch (_: SocketException) {
message.reply("无法从服务器接收Ping结果请检查服务器地址是否正确 >>> $host:$port")
}
}
else -> {
message.reply("输入错误请在 `java` 和 `be`中选择 >>> $platform")
}
}
}
}
@CommandDescription("获取MC账号的AccessToken")
class MCLoginCommand : BaseCommand() {
override val commandNames = listOf("/mclogin")
private val deviceCodeUrl = "https://login.microsoftonline.com/consumers/oauth2/v2.0/devicecode"
private val redeemCodeUrl = "https://login.microsoftonline.com/consumers/oauth2/v2.0/token"
private val xboxLiveAuthUrl = "https://user.auth.xboxlive.com/user/authenticate"
private val obtainXSTSUrl = "https://xsts.auth.xboxlive.com/xsts/authorize"
private val clientId = configManager.azureAppClientId
private val waitingList = mutableListOf<Long>()
private val deviceCodeMap = mutableMapOf<Long, String>()
/**
* 获取user code 和device code 和验证url
*/
private fun getDeviceCode(): Triple<String, String, String> {
val form = FormBody.Builder()
.add("client_id", clientId)
.add("scope", "XboxLive.signin")
.build()
val response = Http.post<DeviceCodeResponse>(deviceCodeUrl, form)
return Triple(response.userCode, response.deviceCode, response.verificationUri)
}
/**
* 将这个code兑换成一个access token
*/
private fun redeemCode(deviceCode: String): String {
val form = FormBody.Builder()
.add("client_id", clientId)
.add("grant_type", "urn:ietf:params:oauth:grant-type:device_code")
.add("device_code", deviceCode)
.build()
val response = Http.post<RedeemCodeResponse>(redeemCodeUrl, form)
return response.accessToken
}
/**
* 登录Xbox
*/
private fun loginXboxLive(accessToken: String): Pair<String, String> {
val payload = LoginXboxLivePayload(properties = LoginXboxLivePayload.Properties("d=$accessToken"))
val response = Http.post<LoginXboxLiveResponse>(xboxLiveAuthUrl, payload.toJson())
val token = response.token
val uhs = response.displayClaims.xui.first().uhs
return token to uhs
}
/**
* 获取最后的mc access token, 这个access token可以直接用于登录游戏
*/
private fun obtainXSTSToken(token: String, uhs: String): String {
val payload = ObtainXSTSPayload(properties = ObtainXSTSPayload.Properties(listOf(token)))
val response = Http.post<ObtainXSTSResponse>(obtainXSTSUrl, payload.toJson())
return response.token
}
override suspend fun executeGroup(message: GroupMessage, args: List<String>) {
if (args.isEmpty()) {
message.reply("这个命令可以获取你的MC登录Token发送 `/mclogin login`进行操作吧")
return
}
deleteAccessTokenById(message.sender.userId)
val action = args.first()
if (action == "确认" || action == "confirm") {
if (message.sender.userId !in waitingList) {
message.reply("你还没开始登陆呢")
} else {
waitingList.removeIf { it == message.sender.userId }
val deviceCode = deviceCodeMap[message.sender.userId]!!
deviceCodeMap.remove(message.sender.userId)
val accessToken = this.redeemCode(deviceCode)
val (token, uhs) = this.loginXboxLive(accessToken)
val minecraftAccessToken = this.obtainXSTSToken(token, uhs)
insertNewAccessToken(minecraftAccessToken, message.sender.userId)
message.reply("你的账号token已经被保存到了数据库中, 请加机器人好友然后发送`/mclogin`来获取这个Token, Token会在你获取之后从数据库中删除")
}
} else {
if (message.sender.userId !in waitingList) {
waitingList.add(message.sender.userId)
val deviceInfo = getDeviceCode()
val msg = MessageChain.Builder()
.addText("你的用户代码是: ${deviceInfo.first}")
.addNewLine()
.addText("请前往: ${deviceInfo.third}进行验证")
.addNewLine()
.addText("登陆完成后发送`/mclogin confirm` 或 `/mclogin 确认`")
.build()
deviceCodeMap[message.sender.userId] = deviceInfo.second
message.reply(msg)
} else {
waitingList.removeIf { it == message.sender.userId }
deviceCodeMap.remove(message.sender.userId)
message.reply("回复错误已结束本次登录流程")
}
}
}
override suspend fun executePrivate(message: PrivateMessage, args: List<String>) {
val token = getAccessTokenById(message.sender.userId)
if (token == null) {
message.reply("你还没登录呢!")
} else {
val msg = MessageChain.Builder()
.addText("你的AccessToken如下: ")
.addNewLine()
.addText(token.token)
.addNewLine()
.addText("这个Token是敏感信息请妥善保管")
.build()
deleteAccessTokenById(message.sender.userId)
message.reply(msg)
}
}
}
@CommandDescription("通过RCON来控制MC服务器(仅限私聊执行)")
class RCONCommand : BaseCommand() {
override val commandNames = listOf("/rcon", "/r")
override suspend fun executeGroup(message: GroupMessage, args: List<String>) {
message.reply("请在私聊中使用此命令")
}
override suspend fun executePrivate(message: PrivateMessage, args: List<String>) {
if (args.isEmpty()) {
val msg = MessageChain.Builder()
.addText("这条指令用于在群内操作mc服务器")
.addNewLine()
.addText("使用`/r add <name> <host> <port> <password>` 可以创建一条配置")
.addNewLine()
.addText("使用`/r <name> <command>`可以执行命令")
.addNewLine()
.addText("使用`/r remove <name>`可以移除这条RCON配置")
.build()
message.reply(msg)
return
}
val action = args.first()
when (action) {
"get" -> {
val allRcons = rconManager.getAllRconsById(message.sender.userId)
if (allRcons.isEmpty()) {
message.reply("你还没有RCON配置呢, 发送`/r add <name> <host> <port> <password>`来创建一个配置吧")
return
}
val messages = mutableListOf<MessageChain>()
.also { it.add(MessageChain.Builder().addText("所有配置如下").build()) }
allRcons.forEach {
val msg = MessageChain.Builder()
.addText("名称: ${it.name}")
.addNewLine()
.addText("地址: ${it.host}:${it.port}")
.addNewLine()
.addText("密码: *********")
.build()
messages.add(msg)
}
message.reply(messages.toNode(configManager.selfId))
}
"add" -> {
val name = args[1]
val host = args[2]
val port = args[3].toInt()
val password = args[4]
rconManager.insertConfig(message.sender.userId, name, host, port, password)
message.reply("成功增加一条记录: $name")
}
"remove" -> {
val name = args.last()
val count = rconManager.removeRRCON(message.sender.userId, name)
if (count == 0) {
message.reply("RCON($name)不存在")
} else {
message.reply("成功移除了$name")
}
}
else -> {
val name = args.first().trim()
val command = args.drop(1).joinToString(" ").trim()
val rconData = rconManager.getRcon(message.sender.userId, name)
if (rconData == null) {
message.reply("RCON($name)配置不存在")
} else {
try {
val result = rconManager.executeCommand(message.sender.userId, name, command)
val messages = mutableListOf<MessageChain>()
.also { it.add(MessageChain.Builder().addText("执行结果如下").build()) }
.also { it.add(MessageChain.Builder().addText(result).build()) }
.toNode(configManager.selfId)
message.reply(messages)
} catch (_: AuthFailedException) {
message.reply("执行失败, 密码不正确")
}
}
}
}
}
}
@CommandDescription("Java版服务器假人压测")
class MCBotCommand : BaseCommand() {
override val commandNames = listOf("/mcbot")
private val userClientMap = mutableMapOf<Long, MutableList<MCClient>>()
private fun getPlayerList(host: String, port: Int): Pair<Int, Int> {
val response = JavaPing().ping(host, port, 8000)!!
return response.players.max to response.players.online
}
override suspend fun executeGroup(message: GroupMessage, args: List<String>) {
val clients = mutableListOf<MCClient>()
val action = args.first()
when (action) {
"start" -> {
if (userClientMap.containsKey(message.sender.userId)) {
message.reply("你有一个正在进行的任务不能开启另外一个任务请先使用`/mcbot cancel`取消任务")
return
}
val address = args[1]
val host = address.split(":").first()
val port = address.split(":").last().toInt()
val count = args[2].toInt()
val executor = Executors.newFixedThreadPool(count)
val (maxPlayer, onlinePlayer) = this.getPlayerList(host, port)
val availableSit = maxPlayer - onlinePlayer
if (count > availableSit) {
message.replyAsync("当前服务器仅剩余${availableSit}个玩家可进入!!")
}
(1..count).forEach {
val client = MCClient(host, port, generateRandomString())
.also { it.createClient() }
executor.execute { clients.add(client.runBot()) }
}
userClientMap[message.sender.userId] = clients
message.reply("成功开启任务: $address, $count")
}
"cancel" -> {
if (userClientMap.containsKey(message.sender.userId)) {
userClientMap.entries.forEach {
if (it.key == message.sender.userId) {
it.value.forEach { it.disconnect() }
userClientMap.remove(it.key)
}
}
message.reply("成功取消任务")
} else {
message.reply("你还没开启任务呢")
}
}
"message" -> {
val content = args.drop(1).joinToString(" ").trim()
userClientMap[message.sender.userId]!!.forEach { it.sendChat(content) }
}
}
}
}
@@ -0,0 +1,126 @@
/*
* Copyright © 2025 RTAkland
* Author: RTAkland
* Date: 2025/1/15
*/
package cn.rtast.fancybot.commands.misc
import cn.rtast.fancybot.annotations.CommandDescription
import cn.rtast.fancybot.util.misc.Resources
import cn.rtast.fancybot.util.misc.drawCenteredText
import cn.rtast.fancybot.util.misc.toByteArray
import cn.rtast.fancybot.util.str.encodeToBase64
import cn.rtast.rob.entity.GroupMessage
import cn.rtast.rob.segment.Image
import cn.rtast.rob.util.BaseCommand
import java.awt.Color
import java.awt.Font
import java.awt.RenderingHints
import javax.imageio.ImageIO
@CommandDescription("那我问你")
class NWWNCommand : BaseCommand() {
override val commandNames = listOf("nwwn", "那我问你")
private val originNWWNImage = Resources.loadFromResourcesAsBytes("misc/nwwn.png")
private val font = Font("SimSun", Font.BOLD, 55)
override suspend fun executeGroup(message: GroupMessage, args: List<String>) {
try {
val keyword = args.joinToString(" ")
val img = ImageIO.read(originNWWNImage!!.inputStream())
val g2d = img.createGraphics()
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON)
g2d.color = Color.BLACK
g2d.font = font
g2d.drawString(keyword, 290, 70)
g2d.dispose()
val imgBase64 = img.toByteArray().encodeToBase64()
val msg = Image(imgBase64, true)
message.reply(msg)
} catch (e: Exception) {
message.reply("生成失败: ${e.message}")
}
}
}
@CommandDescription("太几把搞笑了")
class TJBGXLCommand: BaseCommand() {
override val commandNames = listOf("太几把搞笑了", "tjbgxl", "jb")
private val originImage = Resources.loadFromResourcesAsBytes("misc/tjbgxl.png")
private val font = Font("SimSun", Font.BOLD, 45)
override suspend fun executeGroup(message: GroupMessage, args: List<String>) {
try {
val keyword = args.joinToString(" ")
val img = ImageIO.read(originImage!!.inputStream())
val g2d = img.createGraphics()
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON)
g2d.color = Color.BLACK
g2d.font = font
g2d.drawCenteredText(keyword, 380, 620)
g2d.dispose()
val imgBase64 = img.toByteArray().encodeToBase64()
val msg = Image(imgBase64, true)
message.reply(msg)
} catch (e: Exception) {
message.reply("生成失败: ${e.message}")
}
}
}
@CommandDescription("香奈美语句生成")
class XNMCommand: BaseCommand() {
override val commandNames = listOf("xnm", "香奈美")
private val font = Font("SimSun", Font.BOLD, 55)
private val originImage = Resources.loadFromResourcesAsBytes("misc/xnm.png")
override suspend fun executeGroup(message: GroupMessage, args: List<String>) {
try {
val keyword = args.first()
val keyword2 = args.last()
val img = ImageIO.read(originImage!!.inputStream())
val g2d = img.createGraphics()
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON)
g2d.color = Color.BLACK
g2d.font = font
g2d.drawCenteredText(keyword, 510, 55)
g2d.drawCenteredText(keyword2, 510, 140)
g2d.dispose()
val imgBase64 = img.toByteArray().encodeToBase64()
val msg = Image(imgBase64, true)
message.reply(msg)
} catch (e: Exception) {
message.reply("生成失败: ${e.message}")
}
}
}
@CommandDescription("设静")
class SJCommand: BaseCommand() {
override val commandNames = listOf("sj")
private val font = Font("SimSun", Font.BOLD, 20)
private val originImage = Resources.loadFromResourcesAsBytes("misc/sj.png")
override suspend fun executeGroup(message: GroupMessage, args: List<String>) {
try {
val keyword = args.joinToString(" ")
val img = ImageIO.read(originImage!!.inputStream())
val g2d = img.createGraphics()
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON)
g2d.color = Color.BLACK
g2d.font = font
g2d.drawCenteredText(keyword, 126, 210)
g2d.dispose()
val imgBase64 = img.toByteArray().encodeToBase64()
val msg = Image(imgBase64, true)
message.reply(msg)
} catch (e: Exception) {
message.reply("生成失败: ${e.message}")
}
}
}
@@ -0,0 +1,47 @@
/*
* Copyright © 2024 RTAkland
* Author: RTAkland
* Date: 2024/10/14
*/
package cn.rtast.fancybot.commands.misc
import cn.rtast.fancybot.API_RTAST_URL
import cn.rtast.fancybot.PBI_API_URL
import cn.rtast.fancybot.annotations.CommandDescription
import cn.rtast.fancybot.configManager
import cn.rtast.fancybot.entity.pastebin.PastebinPayload
import cn.rtast.fancybot.entity.pastebin.PastebinResponse
import cn.rtast.fancybot.enums.CommandAction
import cn.rtast.fancybot.util.Http
import cn.rtast.fancybot.util.file.insertActionRecord
import cn.rtast.fancybot.util.str.toJson
import cn.rtast.rob.entity.GroupMessage
import cn.rtast.rob.util.BaseCommand
@CommandDescription("Pastebin!")
class PastebinCommand : BaseCommand() {
override val commandNames = listOf("/pastebin", "/pb")
companion object {
fun createPastebin(content: String): String {
val pastebinPayload = PastebinPayload(content)
val response = Http.post<PastebinResponse>(
"$API_RTAST_URL/api/pastebin", pastebinPayload.toJson(),
params = mapOf("key" to configManager.apiRtastKey)
)
return "https://api.rtast.cn/-/${response.id}"
}
}
override suspend fun executeGroup(message: GroupMessage, args: List<String>) {
if (args.isEmpty()) {
message.reply("使用`/pb <内容>`或者回复一个消息`/pb`可以快速创建一个pastebin")
return
}
val content = args.joinToString(" ")
message.reply(createPastebin(content))
insertActionRecord(CommandAction.Pastebin, message.sender.userId, content)
}
}
@@ -0,0 +1,95 @@
/*
* Copyright © 2024 RTAkland
* Author: RTAkland
* Date: 2024/8/30
*/
package cn.rtast.fancybot.commands.misc
import cn.rtast.fancybot.annotations.CommandDescription
import cn.rtast.fancybot.enums.CommandAction
import cn.rtast.fancybot.util.file.insertActionRecord
import cn.rtast.fancybot.util.misc.toBufferedImage
import cn.rtast.fancybot.util.misc.toByteArray
import cn.rtast.fancybot.util.str.encodeToBase64
import cn.rtast.rob.entity.GroupMessage
import cn.rtast.rob.enums.ArrayMessageType
import cn.rtast.rob.onebot.MessageChain
import cn.rtast.rob.util.BaseCommand
import com.google.zxing.BarcodeFormat
import com.google.zxing.BinaryBitmap
import com.google.zxing.EncodeHintType
import com.google.zxing.MultiFormatReader
import com.google.zxing.client.j2se.BufferedImageLuminanceSource
import com.google.zxing.client.j2se.MatrixToImageWriter
import com.google.zxing.common.HybridBinarizer
import com.google.zxing.qrcode.QRCodeWriter
import java.awt.image.BufferedImage
import java.net.URI
@CommandDescription("生成二维码")
class GenerateQRCodeCommand : BaseCommand() {
override val commandNames = listOf("/qr")
private fun generateQRCode(content: String): String {
val qrCodeWriter = QRCodeWriter()
val hints = mapOf(EncodeHintType.CHARACTER_SET to "UTF-8")
val bitMatrix = qrCodeWriter.encode(content, BarcodeFormat.QR_CODE, 300, 300, hints)
return MatrixToImageWriter.toBufferedImage(bitMatrix).toByteArray().encodeToBase64()
}
override suspend fun executeGroup(message: GroupMessage, args: List<String>) {
if (args.isEmpty()) {
message.reply("发送`/qr <内容>`即可生成一张二维码的图片")
return
}
val content = args.joinToString(" ")
val image = this.generateQRCode(content)
val msg = MessageChain.Builder()
.addImage(image, true)
.build()
message.reply(msg)
insertActionRecord(CommandAction.GenQRCode, message.sender.userId)
}
}
@CommandDescription("解析二维码中的内容!")
class ScanQRCodeCommand : BaseCommand() {
override val commandNames = listOf("/scan")
companion object {
private val waitingList = mutableListOf<Long>()
private fun scanQRCode(image: BufferedImage): String {
val luminanceSource = BufferedImageLuminanceSource(image)
val binaryBitmap = BinaryBitmap(HybridBinarizer(luminanceSource))
val result = MultiFormatReader().decode(binaryBitmap)
return result.text
}
suspend fun callback(message: GroupMessage) {
if (message.sender() in waitingList) {
waitingList.remove(message.sender())
if (!message.message.any { it.type == ArrayMessageType.image }) {
message.reply("回复错误已取消本次操作")
return
}
val image = message.message.find { it.type == ArrayMessageType.image }?.data?.file!!
val bufferedImage = URI(image).toURL().readBytes().toBufferedImage()
val result = this.scanQRCode(bufferedImage)
message.reply("二维码扫描结果: $result")
}
}
}
override suspend fun executeGroup(message: GroupMessage, args: List<String>) {
if (message.sender() !in waitingList) {
waitingList.add(message.sender())
message.reply("继续发送一张二维码图片")
} else {
message.reply("回复错误已经取消本次操作")
}
}
}
@@ -10,22 +10,21 @@ package cn.rtast.fancybot.commands.misc
import cn.rtast.fancybot.annotations.CommandDescription import cn.rtast.fancybot.annotations.CommandDescription
import cn.rtast.rob.entity.GroupMessage import cn.rtast.rob.entity.GroupMessage
import cn.rtast.rob.enums.ArrayMessageType import cn.rtast.rob.enums.ArrayMessageType
import cn.rtast.rob.onebot.MessageChain
import cn.rtast.rob.util.BaseCommand import cn.rtast.rob.util.BaseCommand
import cn.rtast.rob.util.ob.MessageChain
import cn.rtast.rob.util.ob.OneBotListener
@CommandDescription("RUA!") @CommandDescription("RUA!")
class RUACommand : BaseCommand() { class RUACommand : BaseCommand() {
override val commandNames = listOf("/rua", "rua") override val commandNames = listOf("/rua", "rua")
override suspend fun executeGroup(listener: OneBotListener, message: GroupMessage, args: List<String>) { override suspend fun executeGroup(message: GroupMessage, args: List<String>) {
if (args.isEmpty()) { if (args.isEmpty()) {
val msg = MessageChain.Builder() val msg = MessageChain.Builder()
.addAt(message.sender.userId) .addAt(message.sender.userId)
.addText("使用rua @xxxx") .addText("使用rua @xxxx")
.addText("即可rua某人啦~") .addText("即可rua某人啦~")
.build() .build()
listener.sendGroupMessage(message.groupId, msg) message.reply(msg)
return return
} }
try { try {
@@ -38,13 +37,13 @@ class RUACommand : BaseCommand() {
.addText(" ") .addText(" ")
.addText("rua了你~") .addText("rua了你~")
.build() .build()
listener.sendGroupMessage(message.groupId, msg) message.reply(msg)
} catch (_: NullPointerException) { } catch (_: NullPointerException) {
val msg = MessageChain.Builder() val msg = MessageChain.Builder()
.addAt(message.sender.userId) .addAt(message.sender.userId)
.addText("要@才有用哦~") .addText("要@才有用哦~")
.build() .build()
listener.sendGroupMessage(message.groupId, msg) message.reply(msg)
} }
} }
} }
@@ -0,0 +1,32 @@
/*
* Copyright © 2024 RTAkland
* Author: RTAkland
* Date: 2024/10/3
*/
package cn.rtast.fancybot.commands.misc
import cn.rtast.fancybot.annotations.CommandDescription
import cn.rtast.fancybot.configManager
import cn.rtast.fancybot.entity.music.TopSong
import cn.rtast.fancybot.util.Http
import cn.rtast.rob.entity.GroupMessage
import cn.rtast.rob.enums.MusicShareType
import cn.rtast.rob.onebot.MessageChain
import cn.rtast.rob.util.BaseCommand
@CommandDescription("获取随机热榜音乐(网易云)")
class RandomMusicCommand : BaseCommand() {
override val commandNames = listOf("随机音乐", "随机歌曲")
private val ncmApiUrl = "${configManager.ncmAPI}/top/song"
override suspend fun executeGroup(message: GroupMessage, args: List<String>) {
val id = Http.get<TopSong>(ncmApiUrl).data.random().privilege.id
val msg = MessageChain.Builder()
.addMusicShare(MusicShareType.Netease, id.toString())
.build()
message.reply(msg)
}
}
@@ -0,0 +1,28 @@
/*
* Copyright © 2024 RTAkland
* Author: RTAkland
* Date: 2024/10/1
*/
package cn.rtast.fancybot.commands.misc
import cn.rtast.fancybot.annotations.CommandDescription
import cn.rtast.rob.entity.GroupMessage
import cn.rtast.rob.enums.QQFace
import cn.rtast.rob.onebot.OneBotAction
import cn.rtast.rob.util.BaseCommand
@CommandDescription("让你的消息得到回应(reaction)!")
class ReactionCommand : BaseCommand() {
override val commandNames = listOf("reaction", "回应")
companion object {
suspend fun reaction(action: OneBotAction, groupId: Long, messageId: Long, limit: Int = 30) {
(0..limit - 1).forEach { action.reaction(groupId, messageId, QQFace.entries.random().id.toString()) }
}
}
override suspend fun executeGroup(message: GroupMessage, args: List<String>) =
reaction(message.action, message.groupId, message.messageId)
}
@@ -8,12 +8,11 @@
package cn.rtast.fancybot.commands.misc package cn.rtast.fancybot.commands.misc
import cn.rtast.fancybot.annotations.CommandDescription import cn.rtast.fancybot.annotations.CommandDescription
import cn.rtast.fancybot.entity.enums.CountryList import cn.rtast.fancybot.enums.CountryList
import cn.rtast.fancybot.util.randomBooleanWithProbability import cn.rtast.fancybot.util.misc.randomBooleanWithProbability
import cn.rtast.rob.entity.GroupMessage import cn.rtast.rob.entity.GroupMessage
import cn.rtast.rob.onebot.MessageChain
import cn.rtast.rob.util.BaseCommand import cn.rtast.rob.util.BaseCommand
import cn.rtast.rob.util.ob.MessageChain
import cn.rtast.rob.util.ob.OneBotListener
@CommandDescription("人生重开模拟器") @CommandDescription("人生重开模拟器")
class RemakeCommand : BaseCommand() { class RemakeCommand : BaseCommand() {
@@ -30,7 +29,7 @@ class RemakeCommand : BaseCommand() {
"鸽子", "鼠鼠", "化石", "狗狗" "鸽子", "鼠鼠", "化石", "狗狗"
) )
override suspend fun executeGroup(listener: OneBotListener, message: GroupMessage, args: List<String>) { override suspend fun executeGroup(message: GroupMessage, args: List<String>) {
val isSuccess = randomBooleanWithProbability(0.7) val isSuccess = randomBooleanWithProbability(0.7)
val msg = MessageChain.Builder().addReply(message.messageId) val msg = MessageChain.Builder().addReply(message.messageId)
if (isSuccess) { if (isSuccess) {
@@ -40,6 +39,6 @@ class RemakeCommand : BaseCommand() {
msg.addText("重开成功! 你转生在${country.chineseName}$location, 是一个$role") msg.addText("重开成功! 你转生在${country.chineseName}$location, 是一个$role")
} else } else
msg.addText("转生失败~") msg.addText("转生失败~")
listener.sendGroupMessage(message.groupId, msg.build()) message.reply(msg.build())
} }
} }
@@ -9,20 +9,18 @@ package cn.rtast.fancybot.commands.misc
import cn.rtast.fancybot.annotations.CommandDescription import cn.rtast.fancybot.annotations.CommandDescription
import cn.rtast.rob.entity.GroupMessage import cn.rtast.rob.entity.GroupMessage
import cn.rtast.rob.onebot.MessageChain
import cn.rtast.rob.util.BaseCommand import cn.rtast.rob.util.BaseCommand
import cn.rtast.rob.util.ob.MessageChain
import cn.rtast.rob.util.ob.OneBotListener
@CommandDescription("资料卡点赞") @CommandDescription("资料卡点赞")
class LikeMeCommand : BaseCommand() { class SendLikeCommand : BaseCommand() {
override val commandNames = listOf("赞我") override val commandNames = listOf("赞我")
override suspend fun executeGroup(listener: OneBotListener, message: GroupMessage, args: List<String>) { override suspend fun executeGroup(message: GroupMessage, args: List<String>) {
listener.sendLike(message.sender.userId, 10) message.action.sendLike(message.sender.userId, 10)
val msg = MessageChain.Builder() val msg = MessageChain.Builder()
.addReply(message.messageId)
.addText("👍了你十下~~😁") .addText("👍了你十下~~😁")
.build() .build()
listener.sendGroupMessage(message.groupId, msg) message.reply(msg)
} }
} }
@@ -9,10 +9,10 @@ package cn.rtast.fancybot.commands.misc
import cn.rtast.fancybot.annotations.CommandDescription import cn.rtast.fancybot.annotations.CommandDescription
import cn.rtast.fancybot.configManager import cn.rtast.fancybot.configManager
import cn.rtast.fancybot.enums.CommandAction
import cn.rtast.fancybot.util.file.insertActionRecord
import cn.rtast.rob.entity.GroupMessage import cn.rtast.rob.entity.GroupMessage
import cn.rtast.rob.util.BaseCommand import cn.rtast.rob.util.BaseCommand
import cn.rtast.rob.util.ob.MessageChain
import cn.rtast.rob.util.ob.OneBotListener
import org.simplejavamail.api.mailer.config.TransportStrategy import org.simplejavamail.api.mailer.config.TransportStrategy
import org.simplejavamail.email.EmailBuilder import org.simplejavamail.email.EmailBuilder
import org.simplejavamail.mailer.MailerBuilder import org.simplejavamail.mailer.MailerBuilder
@@ -42,21 +42,18 @@ class SendMailCommand : BaseCommand() {
this.mailer.sendMail(email) this.mailer.sendMail(email)
} }
override suspend fun executeGroup(listener: OneBotListener, message: GroupMessage, args: List<String>) { override suspend fun executeGroup(message: GroupMessage, args: List<String>) {
if (args.isEmpty()) { if (args.isEmpty()) {
message.reply("发送`/mail <收件人地址>`即可发送测试邮件~") message.reply("发送`/mail <收件人地址>`即可发送测试邮件~")
return return
} }
if (message.sender.userId !in configManager.admins) { if (message.sender.isAdmin || message.sender.isOwner) {
val msg = MessageChain.Builder() val target = args.first()
.addReply(message.messageId) this.sendMail(target)
.addText("你不许发邮件") message.reply("正在发送邮件, 请查收~(可能会出现在垃圾桶中~)")
.build() insertActionRecord(CommandAction.SendMail, message.sender.userId, target)
listener.sendGroupMessage(message.groupId, msg) } else {
return message.reply("你不许发邮件")
} }
val target = args.first()
this.sendMail(target)
message.reply("正在发送邮件, 请查收~(可能会出现在垃圾桶中~)")
} }
} }
@@ -7,30 +7,39 @@
package cn.rtast.fancybot.commands.misc package cn.rtast.fancybot.commands.misc
import cn.rtast.fancybot.API_RTAST_URL
import cn.rtast.fancybot.annotations.CommandDescription import cn.rtast.fancybot.annotations.CommandDescription
import cn.rtast.fancybot.entity.ShortLink import cn.rtast.fancybot.configManager
import cn.rtast.fancybot.entity.shortlink.ShortLinkPayload
import cn.rtast.fancybot.entity.shortlink.ShortLinkResponse
import cn.rtast.fancybot.util.Http import cn.rtast.fancybot.util.Http
import cn.rtast.fancybot.util.str.encodeToBase64 import cn.rtast.fancybot.util.str.toJson
import cn.rtast.rob.entity.GroupMessage import cn.rtast.rob.entity.GroupMessage
import cn.rtast.rob.util.BaseCommand import cn.rtast.rob.util.BaseCommand
import cn.rtast.rob.util.ob.OneBotListener
@CommandDescription("生成短链接") @CommandDescription("生成短链接")
class ShortLinkCommand : BaseCommand() { class ShortLinkCommand : BaseCommand() {
override val commandNames = listOf("/s") override val commandNames = listOf("/s")
private val shortLinkApi = "https://api.rtast.cn"
override suspend fun executeGroup(listener: OneBotListener, message: GroupMessage, args: List<String>) { companion object {
fun String.makeShortLink(): String {
val shortLink = Http.post<ShortLinkResponse>(
"$API_RTAST_URL/api/sl",
ShortLinkPayload(this).toJson(),
params = mapOf("key" to configManager.apiRtastKey)
)
return "$API_RTAST_URL/${shortLink.id}"
}
}
override suspend fun executeGroup(message: GroupMessage, args: List<String>) {
if (args.isEmpty()) { if (args.isEmpty()) {
message.reply("发送`/s <url>`来生成短链接`不要放入一些奇怪的链接哦~") message.reply("发送`/s <url>`来生成短链接`不要放入一些奇怪的链接哦~")
return return
} }
val target = args.first().encodeToBase64() val target = args.first()
val shortLink = Http.post<ShortLink>( val shortLink = target.makeShortLink()
"$shortLinkApi/short_link", message.reply(shortLink)
params = mapOf("target" to target)
)
message.reply("$shortLinkApi/s?rnd_id=${shortLink.id}")
} }
} }
@@ -0,0 +1,54 @@
/*
* Copyright © 2024 RTAkland
* Author: RTAkland
* Date: 2024/10/7
*/
package cn.rtast.fancybot.commands.misc
import cn.rtast.fancybot.annotations.CommandDescription
import cn.rtast.fancybot.entity.tts.TTSResponse
import cn.rtast.fancybot.util.Http
import cn.rtast.fancybot.util.str.proxy
import cn.rtast.rob.entity.GroupMessage
import cn.rtast.rob.enums.AIRecordCharacter
import cn.rtast.rob.onebot.MessageChain
import cn.rtast.rob.util.BaseCommand
import okhttp3.FormBody
@CommandDescription("将文字转换成语音(TTS)!")
class TTSCommand : BaseCommand() {
override val commandNames = listOf("/tts")
private val ttsApiUrl = "https://ttsmp3.com/makemp3_new.php"
override suspend fun executeGroup(message: GroupMessage, args: List<String>) {
if (args.isEmpty()) {
message.reply("发送`/tts <文字>`就可以生成语音啦~")
return
}
val content = args.joinToString(" ").trim()
val form = FormBody.Builder()
.add("msg", content)
.add("lang", "Zhiyu")
.add("source", "ttsmp3")
.build()
val response = Http.post<TTSResponse>(ttsApiUrl, form)
val msg = MessageChain.Builder()
.addRecord(response.url.proxy)
.build()
message.reply(msg)
}
}
class AITTSCommand : BaseCommand() {
override val commandNames = listOf("/aitts")
override suspend fun executeGroup(message: GroupMessage, args: List<String>) {
val content = args.joinToString(" ").trim()
val character = if (args.size == 1) AIRecordCharacter.XiaoXin
else AIRecordCharacter.forName(args.last()) ?: AIRecordCharacter.XiaoXin
message.action.sendGroupAIRecord(message.groupId, character, content)
}
}
@@ -7,15 +7,17 @@
package cn.rtast.fancybot.commands.misc package cn.rtast.fancybot.commands.misc
import cn.rtast.fancybot.annotations.CommandDescription
import cn.rtast.rob.entity.GroupMessage import cn.rtast.rob.entity.GroupMessage
import cn.rtast.rob.onebot.MessageChain
import cn.rtast.rob.util.BaseCommand import cn.rtast.rob.util.BaseCommand
import cn.rtast.rob.util.ob.MessageChain
import cn.rtast.rob.util.ob.OneBotListener
@Deprecated("已迁移到兑换列表中")
@CommandDescription("获得十张色图!")
class TenSetuCommand : BaseCommand() { class TenSetuCommand : BaseCommand() {
override val commandNames = listOf("十张色图") override val commandNames = listOf("十张色图")
override suspend fun executeGroup(listener: OneBotListener, message: GroupMessage, args: List<String>) { override suspend fun executeGroup(message: GroupMessage, args: List<String>) {
val msg = MessageChain.Builder() val msg = MessageChain.Builder()
.addText("指令已经迁移到兑换列表中") .addText("指令已经迁移到兑换列表中")
.addNewLine() .addNewLine()
@@ -0,0 +1,89 @@
/*
* Copyright © 2024 RTAkland
* Author: RTAkland
* Date: 2024/10/11
*/
package cn.rtast.fancybot.commands.misc
import cn.rtast.fancybot.annotations.CommandDescription
import cn.rtast.rob.entity.GroupMessage
import cn.rtast.rob.util.BaseCommand
import kotlin.random.Random
@CommandDescription("今天吃什么")
class TodayEatCommand : BaseCommand() {
override val commandNames = listOf("今天吃什么")
private val foodList = listOf(
"炒饭", "火锅", "汉堡", "披萨", "寿司", "拉面", "炸鸡", "米粉",
"牛排", "烤串", "沙拉", "饺子", "意大利面", "烤鸭", "豆腐脑",
"蛋糕", "烧烤", "咖喱饭", "麻辣烫", "烤鱼", "牛肉面", "凉皮",
"西餐", "日式便当", "韩式拌饭", "生煎包", "炸酱面", "煎饼果子",
"酸辣粉", "煲仔饭", "", ""
)
override suspend fun executeGroup(message: GroupMessage, args: List<String>) {
if (KFCCommand.isThursday() && Random.nextBoolean()) {
message.reply("今天是疯! 狂! 星! 期! 四!, 所以吃肯德基!")
return
}
message.reply("今天吃${foodList.random()}吧~")
}
}
@CommandDescription("今天不吃什么")
class TodayDontEatCommand : BaseCommand() {
override val commandNames = listOf("今天不吃什么")
override suspend fun executeGroup(message: GroupMessage, args: List<String>) {
message.reply("你吃个结巴, 是吧")
}
}
@CommandDescription("今天喝什么")
class TodayDrinkCommand : BaseCommand() {
override val commandNames = listOf("今天喝什么")
private val drinkList = listOf(
"咖啡", "绿茶", "奶茶", "橙汁", "苹果汁", "可乐", "啤酒", "矿泉水",
"苏打水", "红茶", "酸梅汤", "豆浆", "椰子水", "冰沙", "葡萄汁",
"柠檬水", "奶昔", "热巧克力", "果茶", "乌龙茶", "茉莉花茶", "白开水",
"气泡水", "奶盖茶", "冰可乐", "草莓汁", "芒果汁", "蓝莓汁", "热奶茶", "蜂蜜柚子茶"
)
override suspend fun executeGroup(message: GroupMessage, args: List<String>) {
message.reply("今天喝${drinkList.random()}吧~")
}
}
@CommandDescription("今天不吃什么")
class TodayDontDrinkCommand : BaseCommand() {
override val commandNames = listOf("今天不喝什么")
override suspend fun executeGroup(message: GroupMessage, args: List<String>) {
message.reply("你喝个结巴, 是吧")
}
}
@CommandDescription("今天抽什么")
class TodaySmokeCommand : BaseCommand() {
override val commandNames = listOf("今天抽什么")
private val smokeList = listOf(
"黑利", "蓝利", "白利", "普皖", "金皖",
"宽窄", "煊赫门", "骆驼", "百乐(红酒)",
"手卷烟", "烟斗", "万宝路", "白沙3代",
"白沙2代", "中华", "南京-大观园", "金钗",
"银钗", "玉溪", "芒果", "南京-雨花石",
"金圣", "南京95", "红塔山", "轿子",
"钻石荷花", "黄金叶(天叶)", "芙蓉王",
"黄鹤楼", "红河", "长白山", "长丰",
"中南海(蓝莓爆珠)", "云烟", "真龙", "红利"
)
override suspend fun executeGroup(message: GroupMessage, args: List<String>) {
message.reply("今天抽${smokeList.random()}吧~")
}
}
@@ -0,0 +1,90 @@
/*
* Copyright © 2024 RTAkland
* Author: RTAkland
* Date: 2024/10/5
*/
package cn.rtast.fancybot.commands.misc
import cn.rtast.fancybot.annotations.CommandDescription
import cn.rtast.fancybot.util.str.decodeToString
import cn.rtast.fancybot.util.str.encodeToBase64
import cn.rtast.rob.entity.GroupMessage
import cn.rtast.rob.enums.ArrayMessageType
import cn.rtast.rob.util.BaseCommand
@CommandDescription("将人类的语言翻译成尊嘟假嘟语还能将尊嘟假嘟语翻译成人类的语言!")
class ZDJDCommand : BaseCommand() {
override val commandNames = listOf("尊嘟假嘟", "zdjd")
private val b64 = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ+/="
private val leftEye = listOf("o", "0", "O", "Ö")
private val mouse = listOf("w", "v", ".", "_")
private val rightEye = listOf("o", "0", "O", "Ö")
private val table = mutableListOf<String>()
private val separator = " "
init {
makeTable()
}
private fun makeTable() {
for (i in leftEye.indices) {
for (j in mouse.indices) {
for (k in rightEye.indices) {
table.add(leftEye[i] + mouse[j] + rightEye[k])
}
}
}
}
private fun human2zdjd(text: String): String {
val encoded = text.encodeToBase64()
val arr = mutableListOf<String>()
for (c in encoded) {
if (c != '=') {
val n = b64.indexOf(c)
arr.add(table[n])
}
}
return arr.joinToString(separator)
}
private fun zdjd2human(t: String): String {
val arr = t.split(separator)
val resultArr = mutableListOf<String>()
for (c in arr) {
if (c.isEmpty()) continue
val n = table.indexOf(c)
if (n < 0) throw IllegalArgumentException("Invalid zdjd code")
resultArr.add(b64[n].toString())
}
var base64String = resultArr.joinToString("")
val padding = resultArr.size % 4
if (padding > 0) {
base64String += "=".repeat(4 - padding)
}
return base64String.decodeToString()
}
private fun String.isZdjd(): Boolean {
return try {
zdjd2human(this)
true
} catch (_: Exception) {
false
}
}
override suspend fun executeGroup(message: GroupMessage, args: List<String>) {
val text = if (message.message.any { it.type == ArrayMessageType.image }) {
message.message.find { it.type == ArrayMessageType.image }!!.data.file!!
} else args.joinToString(" ")
if (text.isZdjd()) {
message.reply(zdjd2human(text))
} else {
message.reply(human2zdjd(text))
}
}
}
@@ -10,9 +10,8 @@ package cn.rtast.fancybot.commands.misc
import cn.rtast.fancybot.annotations.CommandDescription import cn.rtast.fancybot.annotations.CommandDescription
import cn.rtast.rob.entity.GroupMessage import cn.rtast.rob.entity.GroupMessage
import cn.rtast.rob.entity.PrivateMessage import cn.rtast.rob.entity.PrivateMessage
import cn.rtast.rob.onebot.MessageChain
import cn.rtast.rob.util.BaseCommand import cn.rtast.rob.util.BaseCommand
import cn.rtast.rob.util.ob.MessageChain
import cn.rtast.rob.util.ob.OneBotListener
val users = mutableMapOf<Long, Long>() val users = mutableMapOf<Long, Long>()
@@ -20,7 +19,7 @@ val users = mutableMapOf<Long, Long>()
class ZiBiCommand : BaseCommand() { class ZiBiCommand : BaseCommand() {
override val commandNames = listOf("/自闭", "自闭") override val commandNames = listOf("/自闭", "自闭")
override suspend fun executeGroup(listener: OneBotListener, message: GroupMessage, args: List<String>) { override suspend fun executeGroup(message: GroupMessage, args: List<String>) {
try { try {
users.remove(message.sender.userId) users.remove(message.sender.userId)
val duration = if (args.isEmpty()) 1 else args.first().toInt() val duration = if (args.isEmpty()) 1 else args.first().toInt()
@@ -47,7 +46,7 @@ class ZiBiCommand : BaseCommand() {
class UnsetZiBiCommand : BaseCommand() { class UnsetZiBiCommand : BaseCommand() {
override val commandNames = listOf("我想开了") override val commandNames = listOf("我想开了")
override suspend fun executePrivate(listener: OneBotListener, message: PrivateMessage, args: List<String>) { override suspend fun executePrivate(message: PrivateMessage, args: List<String>) {
if (!users.any { it.key == message.sender.userId }) { if (!users.any { it.key == message.sender.userId }) {
val msg = MessageChain.Builder() val msg = MessageChain.Builder()
.addText("你没有在自闭列表中~") .addText("你没有在自闭列表中~")
@@ -55,7 +54,7 @@ class UnsetZiBiCommand : BaseCommand() {
message.reply(msg) message.reply(msg)
return return
} }
listener.setGroupBan(users.getValue(message.sender.userId), message.sender.userId, 0) message.action.setGroupBan(users.getValue(message.sender.userId), message.sender.userId, 0)
val msg = MessageChain.Builder() val msg = MessageChain.Builder()
.addText("祝你天天开心不要自闭~~") .addText("祝你天天开心不要自闭~~")
.build() .build()
@@ -10,16 +10,15 @@ package cn.rtast.fancybot.commands.niuzi
import cn.rtast.fancybot.annotations.CommandDescription import cn.rtast.fancybot.annotations.CommandDescription
import cn.rtast.fancybot.niuziManager import cn.rtast.fancybot.niuziManager
import cn.rtast.rob.entity.GroupMessage import cn.rtast.rob.entity.GroupMessage
import cn.rtast.rob.onebot.MessageChain
import cn.rtast.rob.util.BaseCommand import cn.rtast.rob.util.BaseCommand
import cn.rtast.rob.util.ob.MessageChain
import cn.rtast.rob.util.ob.OneBotListener
@CommandDescription("查询自己牛子的长度") @CommandDescription("查询自己牛子的长度")
class MyNiuziCommand : BaseCommand() { class MyNiuziCommand : BaseCommand() {
override val commandNames = listOf("我的牛子", "mp") override val commandNames = listOf("我的牛子", "mp")
override suspend fun executeGroup(listener: OneBotListener, message: GroupMessage, args: List<String>) { override suspend fun executeGroup(message: GroupMessage, args: List<String>) {
val niuzi = niuziManager.getUser(message.sender.userId) val niuzi = niuziManager.getUser(message.sender.userId)
if (niuzi == null) { if (niuzi == null) {
message.reply("你还没有牛子呢, 发送`牛子签到`来领取一根专属于你的牛子吧~") message.reply("你还没有牛子呢, 发送`牛子签到`来领取一根专属于你的牛子吧~")
@@ -37,6 +36,6 @@ class MyNiuziCommand : BaseCommand() {
else -> "牛子也太长啦把天捅穿啦!!!!!!" else -> "牛子也太长啦把天捅穿啦!!!!!!"
} }
msg.addText(niuziString) msg.addText(niuziString)
listener.sendGroupMessage(message.groupId, msg.build()) message.reply(msg.build())
} }
} }
@@ -11,12 +11,11 @@ import cn.rtast.fancybot.annotations.CommandDescription
import cn.rtast.fancybot.commands import cn.rtast.fancybot.commands
import cn.rtast.fancybot.niuziBankManager import cn.rtast.fancybot.niuziBankManager
import cn.rtast.fancybot.niuziManager import cn.rtast.fancybot.niuziManager
import cn.rtast.fancybot.util.getUserName import cn.rtast.fancybot.util.misc.getUserName
import cn.rtast.rob.entity.GroupMessage import cn.rtast.rob.entity.GroupMessage
import cn.rtast.rob.enums.ArrayMessageType import cn.rtast.rob.enums.ArrayMessageType
import cn.rtast.rob.onebot.MessageChain
import cn.rtast.rob.util.BaseCommand import cn.rtast.rob.util.BaseCommand
import cn.rtast.rob.util.ob.MessageChain
import cn.rtast.rob.util.ob.OneBotListener
private suspend fun noAccount(message: GroupMessage) { private suspend fun noAccount(message: GroupMessage) {
message.reply("你还没有银行账户呢, 发送`创建账户`来创建一个账户吧~") message.reply("你还没有银行账户呢, 发送`创建账户`来创建一个账户吧~")
@@ -28,7 +27,7 @@ private const val INTEREST_RATE = 0.0054
class NiuziBankCommand : BaseCommand() { class NiuziBankCommand : BaseCommand() {
override val commandNames = listOf("牛子银行") override val commandNames = listOf("牛子银行")
override suspend fun executeGroup(listener: OneBotListener, message: GroupMessage, args: List<String>) { override suspend fun executeGroup(message: GroupMessage, args: List<String>) {
val niuziCommands = commands.filter { val niuziCommands = commands.filter {
it::class.simpleName?.startsWith("Niuzi")!! && !it::class.simpleName?.lowercase()?.contains("redeem")!! it::class.simpleName?.startsWith("Niuzi")!! && !it::class.simpleName?.lowercase()?.contains("redeem")!!
}.joinToString("\n") { it.commandNames.joinToString(",") { join -> "`$join`" } } }.joinToString("\n") { it.commandNames.joinToString(",") { join -> "`$join`" } }
@@ -49,7 +48,7 @@ class NiuziBankCommand : BaseCommand() {
class BankBalanceCommand : BaseCommand() { class BankBalanceCommand : BaseCommand() {
override val commandNames = listOf("余额查询") override val commandNames = listOf("余额查询")
override suspend fun executeGroup(listener: OneBotListener, message: GroupMessage, args: List<String>) { override suspend fun executeGroup(message: GroupMessage, args: List<String>) {
val balanceData = niuziBankManager.getUser(message.sender.userId) val balanceData = niuziBankManager.getUser(message.sender.userId)
if (balanceData == null) { if (balanceData == null) {
noAccount(message) noAccount(message)
@@ -63,12 +62,12 @@ class BankBalanceCommand : BaseCommand() {
class CreateBankAccountCommand : BaseCommand() { class CreateBankAccountCommand : BaseCommand() {
override val commandNames = listOf("创建账户") override val commandNames = listOf("创建账户")
override suspend fun executeGroup(listener: OneBotListener, message: GroupMessage, args: List<String>) { override suspend fun executeGroup(message: GroupMessage, args: List<String>) {
if (niuziBankManager.getUser(message.sender.userId) != null) { if (niuziBankManager.getUser(message.sender.userId) != null) {
message.reply("你已经有账户了!") message.reply("你已经有账户了!")
return return
} }
val userInfo = listener.getGroupMemberInfo(message.groupId, message.sender.userId) val userInfo = message.action.getGroupMemberInfo(message.groupId, message.sender.userId)
niuziBankManager.createBlankAccount(message.sender.userId, userInfo.card ?: userInfo.nickname) niuziBankManager.createBlankAccount(message.sender.userId, userInfo.card ?: userInfo.nickname)
message.reply("成功创建了一个账户!") message.reply("成功创建了一个账户!")
} }
@@ -78,7 +77,7 @@ class CreateBankAccountCommand : BaseCommand() {
class BankTransferCommand : BaseCommand() { class BankTransferCommand : BaseCommand() {
override val commandNames = listOf("银行转账") override val commandNames = listOf("银行转账")
override suspend fun executeGroup(listener: OneBotListener, message: GroupMessage, args: List<String>) { override suspend fun executeGroup(message: GroupMessage, args: List<String>) {
val currentUserBalance = niuziBankManager.getUser(message.sender.userId) val currentUserBalance = niuziBankManager.getUser(message.sender.userId)
if (currentUserBalance == null) { if (currentUserBalance == null) {
noAccount(message) noAccount(message)
@@ -94,7 +93,7 @@ class BankTransferCommand : BaseCommand() {
message.reply("你的牛子长度不够转账!") message.reply("你的牛子长度不够转账!")
return return
} }
val username = listener.getUserName(message.groupId, message.sender.userId) val username = message.action.getUserName(message.groupId, message.sender.userId)
val result = niuziBankManager.transfer(message.sender.userId, target, amount, username) val result = niuziBankManager.transfer(message.sender.userId, target, amount, username)
message.reply("转账成功! 你在银行内剩余的牛子长度为: ${result?.balance}") message.reply("转账成功! 你在银行内剩余的牛子长度为: ${result?.balance}")
} }
@@ -104,7 +103,7 @@ class BankTransferCommand : BaseCommand() {
class WithdrawCommand : BaseCommand() { class WithdrawCommand : BaseCommand() {
override val commandNames = listOf("取牛子") override val commandNames = listOf("取牛子")
override suspend fun executeGroup(listener: OneBotListener, message: GroupMessage, args: List<String>) { override suspend fun executeGroup(message: GroupMessage, args: List<String>) {
val amount = args.last().toDouble() val amount = args.last().toDouble()
val currentBalance = niuziBankManager.getUser(message.sender.userId) val currentBalance = niuziBankManager.getUser(message.sender.userId)
val niuzi = niuziManager.getUser(message.sender.userId) val niuzi = niuziManager.getUser(message.sender.userId)
@@ -124,7 +123,7 @@ class WithdrawCommand : BaseCommand() {
message.reply("取出的长度必须大于0!") message.reply("取出的长度必须大于0!")
return return
} }
val username = listener.getUserName(message.groupId, message.sender.userId) val username = message.action.getUserName(message.groupId, message.sender.userId)
val result = niuziBankManager.withdraw(message.sender.userId, amount, username) val result = niuziBankManager.withdraw(message.sender.userId, amount, username)
niuziManager.updateLength(message.sender.userId, amount + amount * INTEREST_RATE) niuziManager.updateLength(message.sender.userId, amount + amount * INTEREST_RATE)
message.reply("取牛子成功, 你现在还剩${result.balance}cm的牛子在银行内") message.reply("取牛子成功, 你现在还剩${result.balance}cm的牛子在银行内")
@@ -135,7 +134,7 @@ class WithdrawCommand : BaseCommand() {
class DepositCommand : BaseCommand() { class DepositCommand : BaseCommand() {
override val commandNames = listOf("存牛子") override val commandNames = listOf("存牛子")
override suspend fun executeGroup(listener: OneBotListener, message: GroupMessage, args: List<String>) { override suspend fun executeGroup(message: GroupMessage, args: List<String>) {
val currentBalance = niuziBankManager.getUser(message.sender.userId) val currentBalance = niuziBankManager.getUser(message.sender.userId)
val niuzi = niuziManager.getUser(message.sender.userId) val niuzi = niuziManager.getUser(message.sender.userId)
val amount = args.last().toDouble() val amount = args.last().toDouble()
@@ -155,7 +154,7 @@ class DepositCommand : BaseCommand() {
message.reply("你身上的牛子不够用啦, 没办法存进银行! >>> ${niuzi.length}") message.reply("你身上的牛子不够用啦, 没办法存进银行! >>> ${niuzi.length}")
return return
} }
val username = listener.getUserName(message.groupId, message.sender.userId) val username = message.action.getUserName(message.groupId, message.sender.userId)
val self = niuziManager.updateLength(message.sender.userId, -amount) val self = niuziManager.updateLength(message.sender.userId, -amount)
val result = niuziBankManager.deposit(message.sender.userId, amount, username) val result = niuziBankManager.deposit(message.sender.userId, amount, username)
message.reply("存入成功你身上的牛子还有${self?.length}cm 银行账户内还有: ${result.balance}cm") message.reply("存入成功你身上的牛子还有${self?.length}cm 银行账户内还有: ${result.balance}cm")
@@ -11,15 +11,14 @@ import cn.rtast.fancybot.annotations.CommandDescription
import cn.rtast.fancybot.niuziManager import cn.rtast.fancybot.niuziManager
import cn.rtast.rob.entity.GroupMessage import cn.rtast.rob.entity.GroupMessage
import cn.rtast.rob.enums.ArrayMessageType import cn.rtast.rob.enums.ArrayMessageType
import cn.rtast.rob.onebot.MessageChain
import cn.rtast.rob.util.BaseCommand import cn.rtast.rob.util.BaseCommand
import cn.rtast.rob.util.ob.MessageChain
import cn.rtast.rob.util.ob.OneBotListener
@CommandDescription("来一起激情击剑吧!") @CommandDescription("来一起激情击剑吧!")
class NiuziJiJianCommand : BaseCommand() { class NiuziJiJianCommand : BaseCommand() {
override val commandNames = listOf("击剑", "jj") override val commandNames = listOf("击剑", "jj")
override suspend fun executeGroup(listener: OneBotListener, message: GroupMessage, args: List<String>) { override suspend fun executeGroup(message: GroupMessage, args: List<String>) {
if (args.isEmpty()) { if (args.isEmpty()) {
message.reply("发送`击剑 @xxx`即可对某人进行击剑") message.reply("发送`击剑 @xxx`即可对某人进行击剑")
return return
@@ -34,7 +33,7 @@ class NiuziJiJianCommand : BaseCommand() {
.addNewLine() .addNewLine()
.addText("所以你不能和他们击剑~") .addText("所以你不能和他们击剑~")
.build() .build()
listener.sendGroupMessage(message.groupId, msg) message.reply(msg)
return return
} }
if (message.sender.userId == targetId) { if (message.sender.userId == targetId) {
@@ -69,7 +68,7 @@ class NiuziJiJianCommand : BaseCommand() {
.addNewLine() .addNewLine()
.addText("对方的牛子增加了${result.second}cm~") .addText("对方的牛子增加了${result.second}cm~")
.build() .build()
listener.sendGroupMessage(message.groupId, msg) message.reply(msg)
} }
} }
} }
@@ -0,0 +1,69 @@
/*
* Copyright © 2024 RTAkland
* Author: RTAkland
* Date: 2024/10/11
*/
package cn.rtast.fancybot.commands.niuzi
import cn.rtast.fancybot.annotations.CommandDescription
import cn.rtast.fancybot.niuziBankManager
import cn.rtast.fancybot.niuziManager
import cn.rtast.fancybot.util.misc.getUserName
import cn.rtast.rob.entity.GroupMessage
import cn.rtast.rob.enums.ArrayMessageType
import cn.rtast.rob.onebot.MessageChain
import cn.rtast.rob.util.BaseCommand
@CommandDescription("在群内设置某个成员的牛子银行余额或者现金牛子长度")
class NiuziManagerCommand : BaseCommand() {
override val commandNames = listOf("管理牛子")
override suspend fun executeGroup(message: GroupMessage, args: List<String>) {
if (args.isEmpty()) {
message.reply("此命令可以管理成员的牛子或者银行账户")
return
}
println(args)
if (message.sender.isAdmin || message.sender.isOwner) {
val target = message.message.find { it.type == ArrayMessageType.at }!!.data.qq!!.toLong()
val method = args[0]
val amount = args[1].toDouble()
when (method) {
"set", "s", "设置" -> {
val type = args[2]
when (type) {
"yh", "银行" -> {
val before = niuziBankManager.getUser(target)
niuziBankManager.deposit(
target, amount, message.action.getUserName(message.groupId, target)
)
val after = niuziBankManager.getUser(target)
val msg = MessageChain.Builder()
.addText("设置成功! 金额: $amount")
.addNewLine()
.addText("设置前余额: ${before?.balance?.toFloat()} | 设置后余额: ${after?.balance?.toFloat()}")
.build()
message.reply(msg)
}
else -> {
val before = niuziManager.getUser(target)
niuziManager.updateLength(target, amount)
val after = niuziManager.getUser(target)
val msg = MessageChain.Builder()
.addText("设置成功! 金额: $amount")
.addNewLine()
.addText("设置前长度: ${before?.length?.toFloat()} | 设置后长度: ${after?.length?.toFloat()}")
.build()
message.reply(msg)
}
}
}
}
} else {
message.reply("你没权限呢!")
}
}
}
@@ -12,13 +12,12 @@ import cn.rtast.fancybot.niuziManager
import cn.rtast.rob.entity.GroupMessage import cn.rtast.rob.entity.GroupMessage
import cn.rtast.rob.enums.ArrayMessageType import cn.rtast.rob.enums.ArrayMessageType
import cn.rtast.rob.util.BaseCommand import cn.rtast.rob.util.BaseCommand
import cn.rtast.rob.util.ob.OneBotListener
@CommandDescription("查询别人的牛子长度") @CommandDescription("查询别人的牛子长度")
class NiuziQueryCommand : BaseCommand() { class NiuziQueryCommand : BaseCommand() {
override val commandNames = listOf("牛子查询") override val commandNames = listOf("牛子查询")
override suspend fun executeGroup(listener: OneBotListener, message: GroupMessage, args: List<String>) { override suspend fun executeGroup(message: GroupMessage, args: List<String>) {
if (args.isEmpty()) { if (args.isEmpty()) {
message.reply("输入`牛子查询 @某人`即可查询他的牛子长度") message.reply("输入`牛子查询 @某人`即可查询他的牛子长度")
return return
@@ -11,15 +11,14 @@ import cn.rtast.fancybot.annotations.CommandDescription
import cn.rtast.fancybot.niuziBankManager import cn.rtast.fancybot.niuziBankManager
import cn.rtast.fancybot.niuziManager import cn.rtast.fancybot.niuziManager
import cn.rtast.rob.entity.GroupMessage import cn.rtast.rob.entity.GroupMessage
import cn.rtast.rob.onebot.MessageChain
import cn.rtast.rob.util.BaseCommand import cn.rtast.rob.util.BaseCommand
import cn.rtast.rob.util.ob.MessageChain
import cn.rtast.rob.util.ob.OneBotListener
@CommandDescription("查询所有人的牛子的排行榜") @CommandDescription("查询所有人的牛子的排行榜")
class NiuziRankCommand : BaseCommand() { class NiuziRankCommand : BaseCommand() {
override val commandNames = listOf("牛子排行榜", "牛子排行") override val commandNames = listOf("牛子排行榜", "牛子排行")
override suspend fun executeGroup(listener: OneBotListener, message: GroupMessage, args: List<String>) { override suspend fun executeGroup(message: GroupMessage, args: List<String>) {
val allNiuzi = niuziManager.getAllNiuzi().sortedBy { it.length }.reversed() val allNiuzi = niuziManager.getAllNiuzi().sortedBy { it.length }.reversed()
val msg = MessageChain.Builder().addText("牛子长度排行榜如下").addNewLine() val msg = MessageChain.Builder().addText("牛子长度排行榜如下").addNewLine()
allNiuzi.forEach { allNiuzi.forEach {
@@ -33,7 +32,7 @@ class NiuziRankCommand : BaseCommand() {
class NiuziBankRankCommand : BaseCommand() { class NiuziBankRankCommand : BaseCommand() {
override val commandNames = listOf("牛子富豪榜", "富豪榜") override val commandNames = listOf("牛子富豪榜", "富豪榜")
override suspend fun executeGroup(listener: OneBotListener, message: GroupMessage, args: List<String>) { override suspend fun executeGroup(message: GroupMessage, args: List<String>) {
val allNiuziBankAccounts = niuziBankManager.getAllAccount() val allNiuziBankAccounts = niuziBankManager.getAllAccount()
.sortedBy { it.balance }.reversed() .sortedBy { it.balance }.reversed()
val msg = MessageChain.Builder().addText("牛子银行富豪榜排行如下").addNewLine() val msg = MessageChain.Builder().addText("牛子银行富豪榜排行如下").addNewLine()
@@ -11,25 +11,23 @@ import cn.rtast.fancybot.annotations.CommandDescription
import cn.rtast.fancybot.itemManager import cn.rtast.fancybot.itemManager
import cn.rtast.fancybot.niuziManager import cn.rtast.fancybot.niuziManager
import cn.rtast.rob.entity.GroupMessage import cn.rtast.rob.entity.GroupMessage
import cn.rtast.rob.onebot.MessageChain
import cn.rtast.rob.util.BaseCommand import cn.rtast.rob.util.BaseCommand
import cn.rtast.rob.util.ob.MessageChain
import cn.rtast.rob.util.ob.OneBotListener
@CommandDescription("使用牛子长度兑换物品") @CommandDescription("使用牛子长度兑换物品")
class NiuziRedeemCommand : BaseCommand() { class NiuziRedeemCommand : BaseCommand() {
override val commandNames = listOf("/rdm") override val commandNames = listOf("/rdm")
override suspend fun executeGroup(listener: OneBotListener, message: GroupMessage, args: List<String>) { override suspend fun executeGroup(message: GroupMessage, args: List<String>) {
if (args.isEmpty()) { if (args.isEmpty()) {
val msg = MessageChain.Builder() val msg = MessageChain.Builder()
.addReply(message.messageId)
.addText("发送 /rdm <物品名称> 即可兑换哦!") .addText("发送 /rdm <物品名称> 即可兑换哦!")
.addNewLine() .addNewLine()
.addText("发送 /rdm list 即可查看所有可兑换的物品~") .addText("发送 /rdm list 即可查看所有可兑换的物品~")
.addNewLine() .addNewLine()
.addText("发送 `我的牛子` 可以查看自己的牛子长度~") .addText("发送 `我的牛子` 可以查看自己的牛子长度~")
.build() .build()
listener.sendGroupMessage(message.groupId, msg) message.reply(msg)
return return
} }
val status = niuziManager.getUser(message.sender.userId) val status = niuziManager.getUser(message.sender.userId)
@@ -47,29 +45,26 @@ class NiuziRedeemCommand : BaseCommand() {
val selectedItem = itemManager.items.find { it.itemNames.any { any -> any == item } } val selectedItem = itemManager.items.find { it.itemNames.any { any -> any == item } }
if (selectedItem == null) { if (selectedItem == null) {
val msg = MessageChain.Builder() val msg = MessageChain.Builder()
.addReply(message.messageId)
.addText("没有找到你想兑换的东西呢") .addText("没有找到你想兑换的东西呢")
.addNewLine() .addNewLine()
.addText("你可以发送 /rdm list来查看所有可兑换的物品") .addText("你可以发送 /rdm list来查看所有可兑换的物品")
.build() .build()
listener.sendGroupMessage(message.groupId, msg) message.reply(msg)
return return
} }
val selectedItemPrice = selectedItem.itemPrice val selectedItemPrice = selectedItem.itemPrice
if (selectedItemPrice > status.length) { if (selectedItemPrice > status.length) {
val msg = MessageChain.Builder() val msg = MessageChain.Builder()
.addReply(message.messageId)
.addText("你的牛子长度不够兑换这个物品呢~") .addText("你的牛子长度不够兑换这个物品呢~")
.addNewLine() .addNewLine()
.addText("你有: ${status.length}cm, 兑换需要: $selectedItemPrice cm~\"") .addText("你有: ${status.length}cm, 兑换需要: $selectedItemPrice cm~\"")
.build() .build()
listener.sendGroupMessage(message.groupId, msg) message.reply(msg)
return return
} }
val afterStatus = niuziManager.redeemItem(message.sender.userId, selectedItemPrice)?.length!! val afterStatus = niuziManager.redeemItem(message.sender.userId, selectedItemPrice)?.length!!
val action = selectedItem.redeemInGroup(listener, message, afterStatus) val action = selectedItem.redeemInGroup(message, afterStatus)
val msg = MessageChain.Builder() val msg = MessageChain.Builder()
.addReply(message.messageId)
.addText("兑换成功! 你花费了${selectedItemPrice}cm来兑换奖品, 还剩${afterStatus}cm") .addText("兑换成功! 你花费了${selectedItemPrice}cm来兑换奖品, 还剩${afterStatus}cm")
.addNewLine() .addNewLine()
.addText("-------------------") .addText("-------------------")
@@ -9,22 +9,21 @@ package cn.rtast.fancybot.commands.niuzi
import cn.rtast.fancybot.annotations.CommandDescription import cn.rtast.fancybot.annotations.CommandDescription
import cn.rtast.fancybot.niuziManager import cn.rtast.fancybot.niuziManager
import cn.rtast.fancybot.util.getUserName import cn.rtast.fancybot.util.misc.getUserName
import cn.rtast.rob.entity.GroupMessage import cn.rtast.rob.entity.GroupMessage
import cn.rtast.rob.onebot.MessageChain
import cn.rtast.rob.util.BaseCommand import cn.rtast.rob.util.BaseCommand
import cn.rtast.rob.util.ob.MessageChain
import cn.rtast.rob.util.ob.OneBotListener
@CommandDescription("牛子签到~") @CommandDescription("牛子签到~")
class NiuziSignCommand : BaseCommand() { class NiuziSignCommand : BaseCommand() {
override val commandNames = listOf("牛子签到", "签到") override val commandNames = listOf("牛子签到", "签到")
override suspend fun executeGroup(listener: OneBotListener, message: GroupMessage, args: List<String>) { override suspend fun executeGroup(message: GroupMessage, args: List<String>) {
if (niuziManager.isSigned(message.sender.userId)) { if (niuziManager.isSigned(message.sender.userId)) {
message.reply("你今天已经对你的牛子使用了签到啦,明天再来吧~") message.reply("你今天已经对你的牛子使用了签到啦,明天再来吧~")
return return
} }
val username = listener.getUserName(message.groupId, message.sender.userId) val username = message.action.getUserName(message.groupId, message.sender.userId)
val afterStatus = niuziManager.sign(message.sender.userId, username) val afterStatus = niuziManager.sign(message.sender.userId, username)
val msg = MessageChain.Builder() val msg = MessageChain.Builder()
.addReply(message.messageId) .addReply(message.messageId)
@@ -32,6 +31,6 @@ class NiuziSignCommand : BaseCommand() {
.addNewLine() .addNewLine()
.addText("你的牛子增加了${afterStatus.first}cm~ 总长度为: ${afterStatus.second?.length}") .addText("你的牛子增加了${afterStatus.first}cm~ 总长度为: ${afterStatus.second?.length}")
.build() .build()
listener.sendGroupMessage(message.groupId, msg) message.reply(msg)
} }
} }
@@ -12,13 +12,12 @@ import cn.rtast.fancybot.niuziManager
import cn.rtast.rob.entity.GroupMessage import cn.rtast.rob.entity.GroupMessage
import cn.rtast.rob.enums.ArrayMessageType import cn.rtast.rob.enums.ArrayMessageType
import cn.rtast.rob.util.BaseCommand import cn.rtast.rob.util.BaseCommand
import cn.rtast.rob.util.ob.OneBotListener
@CommandDescription("将自己的牛子长度转账给别人") @CommandDescription("将自己的牛子长度转账给别人")
class NiuziTransferCommand : BaseCommand() { class NiuziTransferCommand : BaseCommand() {
override val commandNames = listOf("牛子转账", "zz") override val commandNames = listOf("牛子转账", "zz")
override suspend fun executeGroup(listener: OneBotListener, message: GroupMessage, args: List<String>) { override suspend fun executeGroup(message: GroupMessage, args: List<String>) {
if (args.isEmpty()) { if (args.isEmpty()) {
message.reply("发送`牛子转账 @某人 <长度>` 即可把自己的牛子长度转账给TA") message.reply("发送`牛子转账 @某人 <长度>` 即可把自己的牛子长度转账给TA")
return return
@@ -8,14 +8,18 @@
package cn.rtast.fancybot.commands.parse package cn.rtast.fancybot.commands.parse
import cn.rtast.fancybot.annotations.CommandDescription import cn.rtast.fancybot.annotations.CommandDescription
import cn.rtast.fancybot.util.makeGif import cn.rtast.fancybot.commands.misc.ShortLinkCommand.Companion.makeShortLink
import cn.rtast.fancybot.util.Logger
import cn.rtast.fancybot.util.file.getFileType
import cn.rtast.fancybot.util.misc.ImageBed
import cn.rtast.fancybot.util.misc.makeGif
import cn.rtast.fancybot.util.misc.toByteArray
import cn.rtast.fancybot.util.str.encodeToBase64 import cn.rtast.fancybot.util.str.encodeToBase64
import cn.rtast.fancybot.util.toByteArray import cn.rtast.rob.entity.GetMessage
import cn.rtast.rob.entity.GroupMessage import cn.rtast.rob.entity.GroupMessage
import cn.rtast.rob.enums.ArrayMessageType import cn.rtast.rob.enums.ArrayMessageType
import cn.rtast.rob.onebot.MessageChain
import cn.rtast.rob.util.BaseCommand import cn.rtast.rob.util.BaseCommand
import cn.rtast.rob.util.ob.MessageChain
import cn.rtast.rob.util.ob.OneBotListener
import com.madgag.gif.fmsware.GifDecoder import com.madgag.gif.fmsware.GifDecoder
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@@ -33,6 +37,7 @@ class AsciiArtCommand : BaseCommand() {
companion object { companion object {
private const val FONT_SIZE = 12 private const val FONT_SIZE = 12
private val font = Font("Monospaced", Font.PLAIN, FONT_SIZE) private val font = Font("Monospaced", Font.PLAIN, FONT_SIZE)
private val logger = Logger.getLogger<AsciiArtCommand>()
val waitingList = mutableListOf<Long>() val waitingList = mutableListOf<Long>()
private fun BufferedImage.convertToAscii(): String { private fun BufferedImage.convertToAscii(): String {
@@ -86,48 +91,94 @@ class AsciiArtCommand : BaseCommand() {
return img return img
} }
suspend fun generateAsciiArt(url: String): MessageChain {
val gifStream = withContext(Dispatchers.IO) { URI(url).toURL().openStream() }
val decoder = GifDecoder()
try {
decoder.read(gifStream)
if (decoder.frameCount == 0) {
val bufferedImage = withContext(Dispatchers.IO) { ImageIO.read(gifStream) }
val imageBytes = bufferedImage.convertToAscii()
.saveAsciiArtToImage(bufferedImage.width, bufferedImage.height)
.toByteArray()
val imageBedUrl = ImageBed.upload(imageBytes, imageBytes.getFileType()).makeShortLink()
val msg = MessageChain.Builder()
.addImage(imageBytes.encodeToBase64(), true)
.addText(imageBedUrl)
.build()
return msg
} else {
logger.info("制作GIF Ascii art中, 总帧数: ${decoder.frameCount}")
val frames = (0 until decoder.frameCount).map { decoder.getFrame(it) }
val asciiFrames = mutableListOf<BufferedImage>()
val width = frames.first().width
val height = frames.first().height
frames.forEachIndexed { index, item ->
asciiFrames.add(item.convertToAscii().saveAsciiArtToImage(width, height))
logger.info("帧:${index}处理完成")
}
logger.info("合成GIF中...")
val gifBytes = decoder.makeGif(asciiFrames)
val imageBedUrl = ImageBed.upload(gifBytes, gifBytes.getFileType()).makeShortLink()
logger.info("合并完成")
val gifBase64 = gifBytes.encodeToBase64()
logger.info("处理后的图片大小: ${(gifBytes.size / 1024 / 1024).toInt()}MB")
val msg = MessageChain.Builder()
.addImage(gifBase64, true)
.addText(imageBedUrl)
.build()
return msg
}
} catch (_: OutOfMemoryError) {
return MessageChain.Builder().addText("GIF处理失败: 内存溢出").build()
logger.info("GIF处理失败: 内存溢出")
} catch (e: Exception) {
return MessageChain.Builder().addText("处理GIF失败: ${e.message}").build()
logger.info("gif处理失败: ${e.message}")
}
}
suspend fun callback(message: GroupMessage) { suspend fun callback(message: GroupMessage) {
if (message.sender.userId !in waitingList) return if (message.sender.userId !in waitingList) return
waitingList.removeIf { it == message.sender.userId } waitingList.removeIf { it == message.sender.userId }
if (message.message.any { it.type == ArrayMessageType.image } if (message.message.any { it.type == ArrayMessageType.image }
|| message.message.any { it.type == ArrayMessageType.mface }) { || message.message.any { it.type == ArrayMessageType.mface }) {
val url = if (message.message.any { it.type == ArrayMessageType.image }) val url = this.getImageUrl(message)
message.message.find { it.type == ArrayMessageType.image }!!.data.file!! val msg = this.generateAsciiArt(url)
else message.message.find { it.type == ArrayMessageType.mface }!!.data.url!! message.reply(msg)
val gifStream = withContext(Dispatchers.IO) { URI(url).toURL().openStream() }
val decoder = GifDecoder()
try {
decoder.read(gifStream)
if (decoder.frameCount == 0) {
val bufferedImage = withContext(Dispatchers.IO) { ImageIO.read(URI(url).toURL()) }
val imageBase64 = bufferedImage.convertToAscii()
.saveAsciiArtToImage(bufferedImage.width, bufferedImage.height)
.toByteArray().encodeToBase64()
val msg = MessageChain.Builder().addImage(imageBase64, true).build()
message.reply(msg)
} else {
println("Making gif ascii art, frames: ${decoder.frameCount}")
val frames = (0 until decoder.frameCount).map { decoder.getFrame(it) }
val asciiFrames = mutableListOf<BufferedImage>()
val width = frames.first().width
val height = frames.first().height
frames.forEach { asciiFrames.add(it.convertToAscii().saveAsciiArtToImage(width, height)) }
val gifBytes = decoder.makeGif(asciiFrames)
val gifBase64 = gifBytes.encodeToBase64()
message.reply(MessageChain.Builder().addImage(gifBase64, true).build())
}
} catch (_: Exception) {
message.reply("处理GIF失败")
}
} else { } else {
message.reply("回复错误已取消本次操作") message.reply("回复错误已取消本次操作")
} }
} }
fun getImageUrl(groupMessage: GroupMessage): String {
return getImageUrlFromUnified(toUnifiedMessage(groupMessage))
}
fun getImageUrl(getMessageData: GetMessage.Message): String {
return getImageUrlFromUnified(toUnifiedMessage(getMessageData))
}
private fun getImageUrlFromUnified(messages: List<UnifiedMessage>): String {
return if (messages.any { it.type == ArrayMessageType.image })
messages.find { it.type == ArrayMessageType.image }!!.file!!
else messages.find { it.type == ArrayMessageType.mface }!!.url!!
}
private fun toUnifiedMessage(groupMessage: GroupMessage): List<UnifiedMessage> {
return groupMessage.message.map { UnifiedMessage(it.type, it.data.file, it.data.url) }
}
private fun toUnifiedMessage(getMessageData: GetMessage.Message): List<UnifiedMessage> {
return getMessageData.message.map { UnifiedMessage(it.type, it.data.file, it.data.url) }
}
private data class UnifiedMessage(val type: ArrayMessageType, val file: String?, val url: String?)
} }
override suspend fun executeGroup(listener: OneBotListener, message: GroupMessage, args: List<String>) { override suspend fun executeGroup(message: GroupMessage, args: List<String>) {
if (message.sender.userId !in waitingList) { if (message.sender.userId !in waitingList) {
message.reply("请继续发送一张图片如果输入错误则取消本次操作") message.reply("请继续发送一张图片, 如果输入错误则取消本次操作, 如果GIF帧数过多可能会需要很长时间(也可能处理失败)")
waitingList.add(message.sender.userId) waitingList.add(message.sender.userId)
return return
} else { } else {
@@ -1,201 +0,0 @@
/*
* Copyright © 2024 RTAkland
* Author: RTAkland
* Date: 2024/9/6
*/
package cn.rtast.fancybot.commands.parse
import cn.rtast.fancybot.entity.bili.*
import cn.rtast.fancybot.util.Http
import cn.rtast.fancybot.util.Resources
import cn.rtast.fancybot.util.drawCustomImage
import cn.rtast.fancybot.util.str.encodeToBase64
import cn.rtast.fancybot.util.str.formatNumber
import cn.rtast.fancybot.util.str.fromJson
import cn.rtast.fancybot.util.str.setTruncate
import cn.rtast.fancybot.util.toByteArray
import cn.rtast.rob.entity.GroupMessage
import cn.rtast.rob.enums.ArrayMessageType
import cn.rtast.rob.util.ob.MessageChain
import cn.rtast.rob.util.ob.OneBotListener
import okhttp3.OkHttpClient
import okhttp3.Request
import java.awt.Color
import java.awt.Font
import java.awt.RenderingHints
import java.awt.image.BufferedImage
import java.net.URI
import javax.imageio.ImageIO
object BVParseCommand {
private const val VIDEO_STAT_URL = "https://api.bilibili.com/x/web-interface/view"
private const val USER_STAT_URL = "https://api.bilibili.com/x/relation/stat"
private const val SHORT_URL_API_URL = "https://api.bilibili.com/x/share/click"
private const val VIEW_COUNT_URL = "https://api.bilibili.com/x/player/online/total"
private const val CANVAS_WIDTH = 1000
private const val CANVAS_HEIGHT = 600
private val backgroundColor = Color(43, 43, 43)
private val upBarColor = Color(102, 102, 102)
private val textColor = Color.WHITE
private val titleFont = Font("Serif", Font.ITALIC, 40)
private val numberFont = Font("Serif", Font.ITALIC, 25)
private val twoTwoLogo = ImageIO.read(Resources.loadFromResources("bili/22-coin.png"))
private val likeIcon = ImageIO.read(Resources.loadFromResources("bili/like.png"))
private val coinIcon = ImageIO.read(Resources.loadFromResources("bili/coin.png"))
private val viewIcon = ImageIO.read(Resources.loadFromResources("bili/view.png"))
private val shareIcon = ImageIO.read(Resources.loadFromResources("bili/share.png"))
private val favoriteIcon = ImageIO.read(Resources.loadFromResources("bili/favorite.png"))
private val replyIcon = ImageIO.read(Resources.loadFromResources("bili/reply.png"))
private val tempOkHttpClient = OkHttpClient()
private fun generateShortUrl(bvid: String, oid: Long): String {
val result = Http.post<ShortUrl>(
SHORT_URL_API_URL, mapOf(
"buvid" to bvid,
"build" to 6114514,
"platform" to "unix",
"share_channel" to "COPY",
"share_mode" to 2,
"share_id" to "main.ugc-video-detail.0.0.pv",
"oid" to oid
)
)
return result.data.content.split(" ").last()
}
private fun getViewCount(bvid: String, cid: Long): String {
return Http.get<ViewCount>(
VIEW_COUNT_URL, mapOf(
"bvid" to bvid,
"cid" to cid
)
).data.total
}
private fun getVideoStat(bvid: String): VideoStat {
return Http.get<VideoStat>(VIDEO_STAT_URL, mapOf("bvid" to bvid))
}
private fun createResponseImage(
title: String,
author: String,
authorFace: String,
picUrl: String,
reply: String,
view: String,
coin: String,
share: String,
like: String,
favorite: String,
fans: Int,
viewCount: String,
): String {
val coverImage = ImageIO.read(URI(picUrl).toURL())
val faceImage = ImageIO.read(URI(authorFace).toURL())
val canvas = BufferedImage(CANVAS_WIDTH, CANVAS_HEIGHT, BufferedImage.TYPE_INT_RGB)
val g2d = canvas.createGraphics()
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON)
g2d.color = backgroundColor
g2d.fillRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT)
// draw rect
g2d.color = upBarColor
g2d.fillRect(0, 0, CANVAS_WIDTH, 120)
g2d.color = textColor
// draw numbers
g2d.font = numberFont
g2d.drawString(like, 110, 200)
g2d.drawString(coin, 110, 290)
g2d.drawString(view, 110, 380)
g2d.drawString(favorite, 300, 380)
g2d.drawString(reply, 300, 290)
g2d.drawString(share, 300, 200)
// draw video title
val truncatedText = setTruncate(title, g2d)
g2d.font = titleFont
g2d.drawString(truncatedText, 20, 70)
// draw author name
g2d.drawString(author, 40, 580)
// draw icons
g2d.drawCustomImage(favoriteIcon, 230, 340, 50.0, 50.0, false)
g2d.drawCustomImage(replyIcon, 230, 250, 50.0, 50.0, false)
g2d.drawCustomImage(shareIcon, 230, 160, 50.0, 50.0, false)
g2d.drawCustomImage(viewIcon, 40, 340, 50.0, 50.0, false)
g2d.drawCustomImage(coinIcon, 40, 250, 50.0, 50.0, false)
g2d.drawCustomImage(likeIcon, 40, 160, 50.0, 50.0, false)
// draw 22 logo
g2d.drawCustomImage(twoTwoLogo, 800, 450, 90.0, 160.0, false)
// draw fans count
g2d.font = numberFont
g2d.drawString(fans.formatNumber() + "粉丝", 120, 520)
// draw viewing count
g2d.drawString("${viewCount}人正在观看", 40, 460)
// draw author face
g2d.drawCustomImage(faceImage, 40, 480, 60.0, 60.0, true)
// draw cover image
g2d.drawCustomImage(coverImage, 430, 140, 600.0, 300.0, true)
g2d.dispose()
return canvas.toByteArray().encodeToBase64()
}
fun getShortUrlBVID(shortUrl: String): String {
val request = Request.Builder().url(shortUrl.split(" ").last()).build()
val redirectedUrl = tempOkHttpClient.newCall(request).execute()
redirectedUrl.use {
return it.request.url.toString().split("/")[4].split("?").first()
}
}
suspend fun parse(listener: OneBotListener, message: GroupMessage) {
// parse bilibili video with a link, bvid or b23.tv link
val bvid = if (message.rawMessage.startsWith("BV")) {
message.rawMessage
} else if (message.rawMessage.split(" ").last().startsWith("https://b23.tv/")) {
val shortUrl = message.rawMessage.split(" ").last()
BVParseCommand.getShortUrlBVID(shortUrl)
} else if (message.message.find { it.type == ArrayMessageType.json } != null) {
val card = message.message.find { it.type == ArrayMessageType.json }!!
.data.data!!.toString().fromJson<CardShare>()
val shortUrl = if (card.meta.detail == null) {
if (!card.meta.news?.tag!!.contains("哔哩哔哩")) return
card.meta.news.jumpUrl
} else {
if (!card.meta.detail.title.contains("哔哩哔哩")) return
card.meta.detail.qqDocUrl
}
BVParseCommand.getShortUrlBVID(shortUrl)
} else {
message.rawMessage.split("?").first()
.split("/")
.last { it.isNotEmpty() && it.isNotBlank() }
}
val videoInfo = this.getVideoStat(bvid)
val viewCount = this.getViewCount(bvid, videoInfo.data.cid)
val shortUrl = this.generateShortUrl(bvid, videoInfo.data.aid)
val fans = Http.get<UserStat>(USER_STAT_URL, mapOf("vmid" to videoInfo.data.owner.mid)).data.follower
val authorFace = videoInfo.data.owner.face
val title = videoInfo.data.title
val author = videoInfo.data.owner.name
val coverPicUrl = videoInfo.data.pic
val view = videoInfo.data.stat.view.formatNumber()
val share = videoInfo.data.stat.share.formatNumber()
val like = videoInfo.data.stat.like.formatNumber()
val favorite = videoInfo.data.stat.favorite.formatNumber()
val coin = videoInfo.data.stat.coin.formatNumber()
val reply = videoInfo.data.stat.reply.formatNumber()
val image = this.createResponseImage(
title, author, authorFace,
coverPicUrl, reply, view, coin,
share, like, favorite, fans, viewCount
)
val msg = MessageChain.Builder()
.addReply(message.messageId)
.addImage(image, true)
.addText(shortUrl)
.build()
listener.sendGroupMessage(message.groupId, msg)
}
}
@@ -0,0 +1,291 @@
/*
* Copyright © 2024 RTAkland
* Author: RTAkland
* Date: 2024/9/6
*/
package cn.rtast.fancybot.commands.parse
import cn.rtast.fancybot.entity.bili.*
import cn.rtast.fancybot.util.Http
import cn.rtast.fancybot.util.misc.Resources
import cn.rtast.fancybot.util.misc.drawCustomImage
import cn.rtast.fancybot.util.misc.drawString
import cn.rtast.fancybot.util.misc.toBufferedImage
import cn.rtast.fancybot.util.misc.toByteArray
import cn.rtast.fancybot.util.misc.toURL
import cn.rtast.fancybot.util.str.encodeToBase64
import cn.rtast.fancybot.util.str.formatNumber
import cn.rtast.fancybot.util.str.fromJson
import cn.rtast.fancybot.util.str.setTruncate
import cn.rtast.rob.entity.GroupMessage
import cn.rtast.rob.entity.text
import cn.rtast.rob.enums.ArrayMessageType
import cn.rtast.rob.onebot.MessageChain
import okhttp3.OkHttpClient
import okhttp3.Request
import java.awt.Color
import java.awt.Font
import java.awt.Image
import java.awt.RenderingHints
import java.awt.image.BufferedImage
import java.math.BigInteger
import java.net.URI
import javax.imageio.ImageIO
object BiliVideoParseCommand {
private val avRegex = Regex("av(\\d+)", RegexOption.IGNORE_CASE)
private val bvRegex = Regex("BV[\\dA-Za-z]{10}")
private val shortUrlRegex = Regex("https://b23\\.tv/\\w{6,8}")
private val backgroundColor = Color(43, 43, 43)
private val upBarColor = Color(102, 102, 102)
private val textColor = Color.WHITE
private val titleFont = Font("Serif", Font.ITALIC, 40)
private val numberFont = Font("Serif", Font.ITALIC, 25)
private val twoTwoLogo = ImageIO.read(Resources.loadFromResources("bili/22-coin.png"))
private val likeIcon = ImageIO.read(Resources.loadFromResources("bili/like.png"))
private val coinIcon = ImageIO.read(Resources.loadFromResources("bili/coin.png"))
private val viewIcon = ImageIO.read(Resources.loadFromResources("bili/view.png"))
private val shareIcon = ImageIO.read(Resources.loadFromResources("bili/share.png"))
private val favoriteIcon = ImageIO.read(Resources.loadFromResources("bili/favorite.png"))
private val replyIcon = ImageIO.read(Resources.loadFromResources("bili/reply.png"))
private const val VIDEO_STAT_URL = "https://api.bilibili.com/x/web-interface/view"
private const val USER_STAT_URL = "https://api.bilibili.com/x/relation/stat"
private const val SHORT_URL_API_URL = "https://api.bilibili.com/x/share/click"
private const val VIEW_COUNT_URL = "https://api.bilibili.com/x/player/online/total"
private const val CANVAS_WIDTH = 1000
private const val CANVAS_HEIGHT = 600
private const val BASE = 58
private const val DATA = "FcwAPNKTMug3GV5Lj7EJnHpWsx4tb8haYeviqBz6rkCy12mUSDQX9RdoZf"
private val XOR_CODE = BigInteger.valueOf(23442827791579L)
private val MAX_AID = BigInteger.ONE.shiftLeft(51)
private val tempOkHttpClient = OkHttpClient()
private fun generateShortUrl(bvid: String, oid: Long): String {
val result = Http.post<ShortUrl>(
SHORT_URL_API_URL, mapOf(
"buvid" to bvid,
"build" to 6114514,
"platform" to "unix",
"share_channel" to "COPY",
"share_mode" to 2,
"share_id" to "main.ugc-video-detail.0.0.pv",
"oid" to oid
)
)
return result.data.content.split(" ").last()
}
private fun getViewCount(bvid: String, cid: Long): String {
return Http.get<ViewCount>(
VIEW_COUNT_URL, mapOf(
"bvid" to bvid,
"cid" to cid
)
).data.total
}
private fun getVideoStat(bvid: String): VideoStat {
return Http.get<VideoStat>(VIDEO_STAT_URL, mapOf("bvid" to bvid))
}
private fun createResponseImage(
title: String,
author: String,
authorFace: String,
picUrl: String,
reply: String,
view: String,
coin: String,
share: String,
like: String,
favorite: String,
fans: Int,
viewCount: String,
): String {
val coverImage = ImageIO.read(URI(picUrl).toURL())
val faceImage = ImageIO.read(URI(authorFace).toURL())
val canvas = BufferedImage(CANVAS_WIDTH, CANVAS_HEIGHT, BufferedImage.TYPE_INT_RGB)
val g2d = canvas.createGraphics()
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON)
g2d.color = backgroundColor
g2d.fillRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT)
// draw rect
g2d.color = upBarColor
g2d.fillRect(0, 0, CANVAS_WIDTH, 120)
g2d.color = textColor
// draw numbers
g2d.font = numberFont
g2d.drawString(like, 110, 200)
g2d.drawString(coin, 110, 290)
g2d.drawString(view, 110, 380)
g2d.drawString(favorite, 300, 380)
g2d.drawString(reply, 300, 290)
g2d.drawString(share, 300, 200)
// draw video title
val truncatedText = setTruncate(title, g2d)
g2d.font = titleFont
g2d.drawString(truncatedText, 20, 70)
// draw author name
g2d.drawString(author, 40, 580)
// draw icons
g2d.drawCustomImage(favoriteIcon, 230, 340, 50.0, 50.0, false)
g2d.drawCustomImage(replyIcon, 230, 250, 50.0, 50.0, false)
g2d.drawCustomImage(shareIcon, 230, 160, 50.0, 50.0, false)
g2d.drawCustomImage(viewIcon, 40, 340, 50.0, 50.0, false)
g2d.drawCustomImage(coinIcon, 40, 250, 50.0, 50.0, false)
g2d.drawCustomImage(likeIcon, 40, 160, 50.0, 50.0, false)
// draw 22 logo
g2d.drawCustomImage(twoTwoLogo, 800, 450, 90.0, 160.0, false)
// draw fans count
g2d.font = numberFont
g2d.drawString(fans.formatNumber() + "粉丝", 120, 520)
// draw viewing count
g2d.drawString("${viewCount}人正在观看", 40, 460)
// draw author face
g2d.drawCustomImage(faceImage, 40, 480, 60.0, 60.0, true)
// draw cover image
g2d.drawCustomImage(coverImage, 430, 140, 600.0, 300.0, true)
g2d.dispose()
return canvas.toByteArray().encodeToBase64()
}
private fun getShortUrlBVID(shortUrl: String): String {
val request = Request.Builder().url(shortUrl.split(" ").last()).build()
val redirectedUrl = tempOkHttpClient.newCall(request).execute()
redirectedUrl.use {
return it.request.url.toString().split("/")[4].split("?").first()
}
}
private fun String.extractAv(): Long = avRegex.find(this)!!.groupValues[1].toLong()
private fun String.extractBv(): String = bvRegex.find(this)!!.value
private fun swap(array: CharArray, i: Int, j: Int) {
val temp = array[i]
array[i] = array[j]
array[j] = temp
}
private fun Long.avToBv(): String {
val aid = BigInteger.valueOf(this)
val bytes = charArrayOf('B', 'V', '1', '0', '0', '0', '0', '0', '0', '0', '0', '0')
var bvIndex = bytes.size - 1
var tmp = MAX_AID.or(aid).xor(XOR_CODE)
while (tmp > BigInteger.ZERO) {
bytes[bvIndex] = DATA[tmp.mod(BigInteger.valueOf(BASE.toLong())).toInt()]
tmp = tmp.divide(BigInteger.valueOf(BASE.toLong()))
bvIndex--
}
swap(bytes, 3, 9)
swap(bytes, 4, 7)
return String(bytes)
}
suspend fun parse(message: GroupMessage) {
if (!message.text.contains("bilibili.com") && !message.text.contains("b23.tv")) return
try {
val bvid = if (bvRegex.containsMatchIn(message.rawMessage)) {
message.rawMessage.extractBv()
} else if (avRegex.containsMatchIn(message.rawMessage)) {
message.rawMessage.extractAv().avToBv()
} else if (shortUrlRegex.containsMatchIn(message.rawMessage)) {
val shortUrl = shortUrlRegex.find(message.rawMessage)!!.value
getShortUrlBVID(shortUrl)
} else if (bvRegex.containsMatchIn(message.rawMessage)) {
bvRegex.find(message.rawMessage)!!.value
} else if (message.message.find { it.type == ArrayMessageType.json } != null) {
val card = message.message.find { it.type == ArrayMessageType.json }!!
.data.data!!.toString().fromJson<CardShare>()
val shortUrl = if (card.meta.detail == null) {
if (!card.meta.news?.tag!!.contains("哔哩哔哩")) return
card.meta.news.jumpUrl
} else {
if (!card.meta.detail.title.contains("哔哩哔哩")) return
card.meta.detail.qqDocUrl
}
getShortUrlBVID(shortUrl)
} else {
return
}
val videoInfo = this.getVideoStat(bvid)
val viewCount = this.getViewCount(bvid, videoInfo.data.cid)
val shortUrl = this.generateShortUrl(bvid, videoInfo.data.aid)
val fans = Http.get<UserStat>(USER_STAT_URL, mapOf("vmid" to videoInfo.data.owner.mid)).data.follower
val authorFace = videoInfo.data.owner.face
val title = videoInfo.data.title
val author = videoInfo.data.owner.name
val coverPicUrl = videoInfo.data.pic
val view = videoInfo.data.stat.view.formatNumber()
val share = videoInfo.data.stat.share.formatNumber()
val like = videoInfo.data.stat.like.formatNumber()
val favorite = videoInfo.data.stat.favorite.formatNumber()
val coin = videoInfo.data.stat.coin.formatNumber()
val reply = videoInfo.data.stat.reply.formatNumber()
val image = this.createResponseImage(
title, author, authorFace,
coverPicUrl, reply, view, coin,
share, like, favorite, fans, viewCount
)
val msg = MessageChain.Builder()
.addImage(image, true)
.addText(shortUrl)
.build()
message.reply(msg)
} catch (_: Exception) {
}
}
}
object BiliUserParseCommand {
private val idRegex = Regex("https://space\\.bilibili\\.com/(\\d+)")
private const val USER_INFO_API_URL = "https://api.bilibili.com/x/web-interface/card"
private val font = Font("Serif", Font.BOLD, 24)
private val signFont = Font("Serif", Font.BOLD, 18)
private const val IMAGE_WIDTH = 500
private const val IMAGE_HEIGHT = 300
private fun getUserInfo(userId: String): BiliUserInfo {
val result = Http.get<UserInfo>(USER_INFO_API_URL, mapOf("mid" to userId)).data.card
return BiliUserInfo(result.name, result.levelInfo.currentLevel, result.face, result.fans, result.sign)
}
private fun getUserFace(url: String) = url.toURL().readBytes().toBufferedImage()
private fun createUserImage(userInfo: BiliUserInfo): String {
val image = BufferedImage(IMAGE_WIDTH, IMAGE_HEIGHT, BufferedImage.TYPE_INT_ARGB)
val g2d = image.createGraphics()
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON)
g2d.color = Color.WHITE
g2d.fillRect(0, 0, IMAGE_WIDTH, IMAGE_HEIGHT)
g2d.font = font
g2d.color = Color.BLACK
g2d.drawString("用户名: ${userInfo.name}", 150, 50)
g2d.drawString("粉丝数: ${userInfo.fans}", 150, 100)
g2d.drawString("等级: ${userInfo.level}", 150, 150)
g2d.font = signFont
g2d.drawString(setTruncate("签名: ${userInfo.sign}", g2d), 40, 180, 100)
val faceImage = getUserFace(userInfo.avatar)
val resizedFaceImage = faceImage.getScaledInstance(100, 100, Image.SCALE_SMOOTH)
g2d.drawImage(resizedFaceImage, 20, 20, null)
g2d.dispose()
return image.toByteArray().encodeToBase64()
}
suspend fun parse(message: GroupMessage) {
val match = idRegex.find(message.rawMessage)
if (match != null) {
val userId = match.groupValues[1]
val userInfo = this.getUserInfo(userId)
val userCardImage = this.createUserImage(userInfo)
val msg = MessageChain.Builder()
.addImage(userCardImage, true)
.build()
message.reply(msg)
}
}
}
@@ -18,7 +18,6 @@ object CalculateCommand {
return try { return try {
engine.eval(input) engine.eval(input)
} catch (_: ScriptException) { } catch (_: ScriptException) {
"Invalid expression"
return null return null
}.toString() }.toString()
} }
@@ -0,0 +1,107 @@
/*
* Copyright © 2024 RTAkland
* Author: RTAkland
* Date: 2024/10/13
*/
package cn.rtast.fancybot.commands.parse
import cn.rtast.fancybot.commands.misc.ShortLinkCommand.Companion.makeShortLink
import cn.rtast.fancybot.entity.douyin.DouyinVideo
import cn.rtast.fancybot.util.Http
import cn.rtast.fancybot.util.Logger
import cn.rtast.fancybot.util.misc.drawCustomImage
import cn.rtast.fancybot.util.misc.toBufferedImage
import cn.rtast.fancybot.util.misc.toByteArray
import cn.rtast.fancybot.util.misc.toURL
import cn.rtast.fancybot.util.str.encodeToBase64
import cn.rtast.fancybot.util.str.formatNumber
import cn.rtast.fancybot.util.str.proxy
import cn.rtast.rob.entity.GroupMessage
import cn.rtast.rob.onebot.MessageChain
import okhttp3.OkHttpClient
import okhttp3.Request
import java.awt.Color
import java.awt.Font
import java.awt.RenderingHints
import java.awt.geom.RoundRectangle2D
import java.awt.image.BufferedImage
object DouyinVideoParseCommand {
private val tempHttpClient = OkHttpClient.Builder().followRedirects(true).followSslRedirects(true).build()
private val shortLinkRegex = Regex("https://v\\.douyin\\.com/[a-zA-Z0-9]+/")
private val singleVideoRegex = Regex("https://www\\.douyin\\.com/video/([0-9]+)")
private val douyinApiUrl = "https://douyin.wtf/api/douyin/web/fetch_one_video".proxy
private val logger = Logger.getLogger<DouyinVideoParseCommand>()
private const val IMAGE_WIDTH = 1200
private const val IMAGE_HEIGHT = 800
private fun extractVideoId(origin: String) = singleVideoRegex.find(origin)?.groupValues?.get(1)!!
private fun createDouyinVideoCard(video: DouyinVideo): String {
val card = BufferedImage(IMAGE_WIDTH, IMAGE_HEIGHT, BufferedImage.TYPE_INT_RGB)
val g2d = card.createGraphics()
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON)
g2d.color = Color(245, 245, 245)
g2d.fillRect(0, 0, IMAGE_WIDTH, IMAGE_HEIGHT)
g2d.color = Color(230, 230, 230)
g2d.fill(
RoundRectangle2D.Float(20f, 20f, (IMAGE_WIDTH - 40).toFloat(), (IMAGE_HEIGHT - 40).toFloat(), 50f, 50f)
)
g2d.color = Color.BLACK
g2d.font = Font("Serif", Font.BOLD, 48)
g2d.drawString(video.data.awemeDetail.author.nickname, 60, 100)
g2d.font = Font("Serif", Font.PLAIN, 32)
g2d.drawString(video.data.awemeDetail.desc, 60, 160)
val avatarImage = video.data.awemeDetail.author.avatarThumb.urlList
.first().toURL().readBytes().toBufferedImage()
g2d.drawImage(avatarImage, 60, 180, 160, 160, null)
val cover = video.data.awemeDetail.video.coverOriginalScale
g2d.drawCustomImage(
cover.urlList.first().toURL().readBytes().toBufferedImage(),
450, 170, cover.width.toDouble(), cover.height.toDouble()
)
g2d.color = Color.DARK_GRAY
g2d.font = Font("Serif", Font.PLAIN, 28)
g2d.drawString("粉丝: ${video.data.awemeDetail.author.followerCount.formatNumber()}", 60, 400)
g2d.drawString("点赞: ${video.data.awemeDetail.statistics.diggCount.formatNumber()}", 60, 440)
g2d.drawString("播放: ${video.data.awemeDetail.statistics.playCount.formatNumber()}", 60, 480)
g2d.drawString("评论: ${video.data.awemeDetail.statistics.commentCount.formatNumber()}", 60, 520)
g2d.drawString("收藏: ${video.data.awemeDetail.statistics.collectCount.formatNumber()}", 60, 560)
g2d.drawString("分享: ${video.data.awemeDetail.statistics.shareCount.formatNumber()}", 60, 600)
g2d.dispose()
return card.toByteArray().encodeToBase64()
}
suspend fun parse(message: GroupMessage) {
try {
val id = if (shortLinkRegex.find(message.rawMessage) != null) {
val shortUrl = shortLinkRegex.find(message.rawMessage)?.value ?: ""
val request = Request.Builder().url(shortUrl).build()
val finalUrl = tempHttpClient.newCall(request).execute().also { it.close() }
extractVideoId(finalUrl.request.url.toString())
} else if (singleVideoRegex.find(message.rawMessage) != null) extractVideoId(message.rawMessage) else ""
if (id.isNotBlank()) {
val response = Http.get<DouyinVideo>(
douyinApiUrl,
mapOf("aweme_id" to id)
)
val videoPlayUrlShortUrl = response.data.awemeDetail.video.bitRate
.first().playAddr.urlList.first().makeShortLink()
val shareUrlShortUrl = response.data.awemeDetail.shareUrl.makeShortLink()
val image = this.createDouyinVideoCard(response)
val msg = MessageChain.Builder()
.addImage(image, true)
.addText("视频地址: $shareUrlShortUrl")
.addNewLine()
.addText("下载地址: $videoPlayUrlShortUrl")
.build()
message.reply(msg)
}
} catch (e: Exception) {
logger.warn("抖音视频解析失败: ${e.message}")
}
}
}
@@ -10,16 +10,15 @@ package cn.rtast.fancybot.commands.parse
import cn.rtast.fancybot.configManager import cn.rtast.fancybot.configManager
import cn.rtast.fancybot.entity.github.RepoInfo import cn.rtast.fancybot.entity.github.RepoInfo
import cn.rtast.fancybot.util.Http import cn.rtast.fancybot.util.Http
import cn.rtast.fancybot.util.Resources import cn.rtast.fancybot.util.misc.Resources
import cn.rtast.fancybot.util.drawCustomImage import cn.rtast.fancybot.util.misc.drawCustomImage
import cn.rtast.fancybot.util.drawString import cn.rtast.fancybot.util.misc.drawString
import cn.rtast.fancybot.util.misc.toByteArray
import cn.rtast.fancybot.util.str.encodeToBase64 import cn.rtast.fancybot.util.str.encodeToBase64
import cn.rtast.fancybot.util.str.formatNumberEnglish import cn.rtast.fancybot.util.str.formatNumberEnglish
import cn.rtast.fancybot.util.str.setTruncate import cn.rtast.fancybot.util.str.setTruncate
import cn.rtast.fancybot.util.toByteArray
import cn.rtast.rob.entity.GroupMessage import cn.rtast.rob.entity.GroupMessage
import cn.rtast.rob.util.ob.MessageChain import cn.rtast.rob.onebot.MessageChain
import cn.rtast.rob.util.ob.OneBotListener
import java.awt.Color import java.awt.Color
import java.awt.Font import java.awt.Font
import java.awt.RenderingHints import java.awt.RenderingHints
@@ -42,6 +41,7 @@ object GitHubParseCommand {
private val descriptionCustomFont = Font("Serif", Font.PLAIN, 40).deriveFont(Font.BOLD or Font.ITALIC) private val descriptionCustomFont = Font("Serif", Font.PLAIN, 40).deriveFont(Font.BOLD or Font.ITALIC)
private val backgroundColor = Color.WHITE private val backgroundColor = Color.WHITE
private val textColor = Color(60, 60, 60) private val textColor = Color(60, 60, 60)
private val githubRegex = Regex("""github\.com/([^/]+)/([^/]+)""")
private val languageColors = mapOf( private val languageColors = mapOf(
"Kotlin" to Color(169, 123, 255), "Kotlin" to Color(169, 123, 255),
@@ -52,7 +52,7 @@ object GitHubParseCommand {
"Go" to Color(0, 173, 216), "Go" to Color(0, 173, 216),
"Ruby" to Color(112, 21, 22), "Ruby" to Color(112, 21, 22),
"PHP" to Color(79, 93, 149), "PHP" to Color(79, 93, 149),
"TypeScript" to Color(43, 116, 137), "TypeScript" to Color(0x3178c6),
"Swift" to Color(240, 81, 56), "Swift" to Color(240, 81, 56),
"SCSS" to Color(198, 83, 140), "SCSS" to Color(198, 83, 140),
"Visual Basic 6.0" to Color(44, 99, 83), "Visual Basic 6.0" to Color(44, 99, 83),
@@ -63,17 +63,72 @@ object GitHubParseCommand {
"Shell" to Color(137, 224, 81), "Shell" to Color(137, 224, 81),
"CMake" to Color(218, 52, 52), "CMake" to Color(218, 52, 52),
"HTML" to Color(227, 76, 38), "HTML" to Color(227, 76, 38),
"C#" to Color(23, 134, 0),
"F#" to Color(184, 69, 252),
"Scala" to Color(194, 45, 64),
"CSS" to Color(86, 61, 124),
"XML" to Color(0, 96, 172),
"Dart" to Color(0, 180, 171),
"Objective-C" to Color(67, 142, 255),
"OCaml" to Color(239, 122, 8),
"Jupyter Notebook" to Color(218, 91, 11),
"Haskell" to Color(239, 122, 8),
"GDScript" to Color(0x355570),
"Elm" to Color(0x60b5cc),
"Dockerfile" to Color(0x384d54),
"C" to Color(0x555555),
"Batchfile" to Color(0xC1, 0xF7, 0x65),
"AngelScript" to Color(0xC7d7dc),
"Vue" to Color(0x41b883),
"SQF" to Color(0x3f3f3f),
"R" to Color(0x198ce7),
"Assembly" to Color(0x6e4c13),
"M" to Color(0x9a6700),
"HCL" to Color(0x844fba),
"Scilab" to Color(0xca0f21),
"TeX" to Color(0x3d6117),
"Makefile" to Color(0x427819),
"Svelte" to Color(0xff3e00),
"Stylus" to Color(0xff6347),
"EJS" to Color(0xa91e50),
"PLpgSQL" to Color(0x336790),
"Markdown" to Color(0x083fa1),
"Lua" to Color(0x000080),
"JSON" to Color(0x292929),
"YAML" to Color(0xcb171e),
"HLSL" to Color(0xaace60),
"GLSL" to Color(0x5686a5),
"Jinja" to Color(0xa52a22),
"Zig" to Color(0xec915c),
"Perl" to Color(0x0298c3),
"Vim Script" to Color(0x199f4b),
"Tcl" to Color(0xe4cc98),
"Roff" to Color(0xecdebe),
"CoffeeScript" to Color(0x244776),
"Matlab" to Color(0xe16737),
"Pascal" to Color(0xE3F171),
"Fortran" to Color(0x4d41b1),
"Prolog" to Color(0x74283c),
"Erlang" to Color(0xB83998),
"D" to Color(0xba595e),
"ActionScript" to Color(0x882B0F),
"Mathematica" to Color(0xdd1100),
"Nim" to Color(0xffc200),
"COBOL" to Color(0x9a6700),
"Cuda" to Color(0x3a4e3a),
"SAS" to Color(0xb34936),
"Visual Basic .NET" to Color(0x945db7),
"Less" to Color(0x1d365d),
"Unknown" to Color(211, 211, 211) "Unknown" to Color(211, 211, 211)
) )
private fun getLanguageColor(language: String): Color { private fun getLanguageColor(language: String?): Color {
return languageColors[language] ?: languageColors["Unknown"]!! return languageColors[language] ?: languageColors["Unknown"]!!
} }
private fun getRepoStat(username: String, repo: String): RepoInfo { private fun getRepoStat(username: String, repo: String): RepoInfo {
return Http.get<RepoInfo>( return Http.get<RepoInfo>(
"$REPO_INFO_API/$username/$repo", "$REPO_INFO_API/$username/$repo", headers = mapOf("Authorization" to "Bearer ${configManager.githubKey}")
headers = mapOf("Authorization" to "Bearer ${configManager.githubKey}")
) )
} }
@@ -110,24 +165,26 @@ object GitHubParseCommand {
g2d.drawString(repoStat.description ?: "暂无描述~", 80, 400, 800) g2d.drawString(repoStat.description ?: "暂无描述~", 80, 400, 800)
val avatarImage = ImageIO.read(URI(repoStat.owner.avatarUrl).toURL()) val avatarImage = ImageIO.read(URI(repoStat.owner.avatarUrl).toURL())
g2d.drawCustomImage(avatarImage, 1200, 160, 300.0, 300.0, true) g2d.drawCustomImage(avatarImage, 1200, 160, 300.0, 300.0, true)
g2d.color = this.getLanguageColor(repoStat.language)
g2d.drawString(repoStat.language ?: "[无语言]", 30, CANVAS_HEIGHT - 50)
g2d.dispose() g2d.dispose()
return canvas.toByteArray().encodeToBase64() return canvas.toByteArray().encodeToBase64()
} }
suspend fun parse(listener: OneBotListener, message: GroupMessage) { suspend fun parse(message: GroupMessage) {
val path = message.rawMessage val matchedResult = githubRegex.find(message.rawMessage)
.replace(".git", "") if (message.rawMessage.contains("github.com") && matchedResult != null) {
.replace("https://github.com/", "") val (user, repo) = matchedResult.destructured
.replace("git@github.com:", "") val repoStat = this.getRepoStat(user, repo)
.split("/") val image = this.createImage(repoStat)
val user = path.first() val msg = MessageChain.Builder().addImage(image, true).build()
val repo = path.last() message.reply(msg)
}
}
fun creatRepoImage(user: String, repo: String): String {
val repoStat = this.getRepoStat(user, repo) val repoStat = this.getRepoStat(user, repo)
val image = this.createImage(repoStat) val image = this.createImage(repoStat)
val msg = MessageChain.Builder() return image
.addReply(message.messageId)
.addImage(image, true)
.build()
listener.sendGroupMessage(message.groupId, msg)
} }
} }
@@ -7,24 +7,52 @@
package cn.rtast.fancybot.commands.parse package cn.rtast.fancybot.commands.parse
import cn.rtast.fancybot.commands.misc.ShortLinkCommand.Companion.makeShortLink
import cn.rtast.fancybot.configManager
import cn.rtast.rob.entity.GetMessage import cn.rtast.rob.entity.GetMessage
import cn.rtast.rob.entity.GroupMessage import cn.rtast.rob.entity.GroupMessage
import cn.rtast.rob.enums.ArrayMessageType import cn.rtast.rob.enums.ArrayMessageType
import cn.rtast.rob.util.ob.MessageChain import cn.rtast.rob.onebot.MessageChain
import cn.rtast.rob.onebot.toNode
object ImageURLCommand { object ImageURLCommand {
suspend fun callback(groupMessage: GroupMessage, message: GetMessage.Data) {
val msg = MessageChain.Builder() fun getImageUrl(message: GetMessage.Message): List<String> {
.addReply(message.messageId) try {
.addText("哈哈哈哈图片来咯~") val images = mutableListOf<String>()
.addNewLine() message.message.forEach {
val image = message.message.find { it.type == ArrayMessageType.image } if (it.type == ArrayMessageType.image) images.add(it.data.file!!)
if (image == null) { if (it.type == ArrayMessageType.mface) images.add(it.data.url!!)
val gif = message.message.find { it.type == ArrayMessageType.mface }!! }
msg.addText(gif.data.url!!) return images
} else { } catch (_: NullPointerException) {
msg.addText(image.data.file!!) return emptyList()
}
}
suspend fun callback(groupMessage: GroupMessage, message: GetMessage.Message) {
val images = getImageUrl(message)
if (images.isEmpty()) {
groupMessage.reply("这个消息里没有图片呢!")
} else {
if (images.size == 1) {
val msg = MessageChain.Builder()
.addText(images.first().makeShortLink())
.addNewLine(2)
.addText(images.first())
.build()
groupMessage.reply(msg)
} else {
val messages = mutableListOf<MessageChain>()
images.forEach {
val msg = MessageChain.Builder()
.addText(it)
.addNewLine(2)
.build()
messages.add(msg)
}
groupMessage.reply(messages.toNode(configManager.selfId))
}
} }
groupMessage.reply(msg.build())
} }
} }
@@ -8,13 +8,13 @@
package cn.rtast.fancybot.commands.parse package cn.rtast.fancybot.commands.parse
import cn.rtast.fancybot.annotations.CommandDescription import cn.rtast.fancybot.annotations.CommandDescription
import cn.rtast.fancybot.util.makeGif import cn.rtast.fancybot.util.misc.makeGif
import cn.rtast.fancybot.util.str.encodeToBase64 import cn.rtast.fancybot.util.str.encodeToBase64
import cn.rtast.rob.entity.GetMessage
import cn.rtast.rob.entity.GroupMessage import cn.rtast.rob.entity.GroupMessage
import cn.rtast.rob.enums.ArrayMessageType import cn.rtast.rob.enums.ArrayMessageType
import cn.rtast.rob.onebot.MessageChain
import cn.rtast.rob.util.BaseCommand import cn.rtast.rob.util.BaseCommand
import cn.rtast.rob.util.ob.MessageChain
import cn.rtast.rob.util.ob.OneBotListener
import com.madgag.gif.fmsware.GifDecoder import com.madgag.gif.fmsware.GifDecoder
import java.net.URI import java.net.URI
@@ -34,6 +34,20 @@ class ReverseGIFCommand : BaseCommand() {
return gifBytes.encodeToBase64() return gifBytes.encodeToBase64()
} }
suspend fun reverse(message: GroupMessage, getMsg: GetMessage.Message) {
val imageObject = getMsg.message.find { it.type == ArrayMessageType.image }
if (imageObject == null) {
message.reply("这个消息中没有图片呢")
} else {
val url = imageObject.data.file!!
val image = this.reverseGif(url)
val msg = MessageChain.Builder()
.addImage(image, true)
.build()
message.reply(msg)
}
}
suspend fun callback(message: GroupMessage) { suspend fun callback(message: GroupMessage) {
if (message.sender.userId !in waitingList) return if (message.sender.userId !in waitingList) return
waitingList.removeIf { it == message.sender.userId } waitingList.removeIf { it == message.sender.userId }
@@ -48,7 +62,7 @@ class ReverseGIFCommand : BaseCommand() {
} }
} }
override suspend fun executeGroup(listener: OneBotListener, message: GroupMessage, args: List<String>) { override suspend fun executeGroup(message: GroupMessage, args: List<String>) {
if (message.sender.userId !in waitingList) { if (message.sender.userId !in waitingList) {
message.reply("发送一张动图来继续操作") message.reply("发送一张动图来继续操作")
waitingList.add(message.sender.userId) waitingList.add(message.sender.userId)
@@ -0,0 +1,85 @@
/*
* Copyright © 2024 RTAkland
* Author: RTAkland
* Date: 2024/10/15
*/
package cn.rtast.fancybot.commands.parse
import cn.rtast.fancybot.configManager
import cn.rtast.fancybot.entity.yt.YoutubeVideoResponse
import cn.rtast.fancybot.util.Http
import cn.rtast.fancybot.util.misc.toBufferedImage
import cn.rtast.fancybot.util.misc.toByteArray
import cn.rtast.fancybot.util.misc.toURL
import cn.rtast.fancybot.util.str.encodeToBase64
import cn.rtast.fancybot.util.str.formatNumber
import cn.rtast.fancybot.util.str.proxy
import cn.rtast.rob.entity.GroupMessage
import cn.rtast.rob.onebot.MessageChain
import java.awt.Color
import java.awt.Font
import java.awt.RenderingHints
import java.awt.image.BufferedImage
object YoutubeVideoParseCommand {
private const val YOUTUBE_API_URL = "https://proxy.rtast.cn/https/www.googleapis.com/youtube/v3/videos"
private const val IMAGE_WIDTH = 800
private const val IMAGE_HEIGHT = 450
private val dataApiKey = configManager.youtubeDataApiKey
private val youtubeVideoIdRegex =
Regex("(?:https?://)?(?:www\\.)?(?:youtube\\.com/(?:watch\\?v=|embed/|v/|.+\\?v=)|youtu\\.be/)([\\w\\-]{11})")
private fun createVideoCard(videoInfo: YoutubeVideoResponse): String {
val image = BufferedImage(IMAGE_WIDTH, IMAGE_HEIGHT, BufferedImage.TYPE_INT_ARGB)
val g2d = image.createGraphics()
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON)
g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON)
g2d.color = Color(48, 48, 48)
g2d.fillRect(0, 0, IMAGE_WIDTH, IMAGE_HEIGHT)
g2d.color = Color(255, 255, 255)
g2d.font = Font("Serif", Font.BOLD, 24)
val title = videoInfo.items.first().snippet.title
g2d.drawString(title, 20, 40)
g2d.font = Font("Serif", Font.PLAIN, 18)
val channelTitle = videoInfo.items.first().snippet.channelTitle
g2d.drawString(channelTitle, 20, 70)
val thumbnailImage = videoInfo.items.first().snippet.thumbnails.standard.url
.proxy.toURL().readBytes().toBufferedImage()
val scaledWidth = 300
val scaledHeight = (thumbnailImage.height * (scaledWidth / thumbnailImage.width.toDouble())).toInt()
g2d.drawImage(thumbnailImage, 20, 100, scaledWidth, scaledHeight, null)
g2d.color = Color(200, 200, 200)
g2d.font = Font("Serif", Font.PLAIN, 16)
g2d.drawString("观看次数: ${videoInfo.items.first().statistics.viewCount.toInt().formatNumber()}", 340, 120)
g2d.drawString("点赞次数: ${videoInfo.items.first().statistics.likeCount.toInt().formatNumber()}", 340, 150)
g2d.drawString("收藏次数: ${videoInfo.items.first().statistics.favoriteCount.toInt().formatNumber()}", 340, 180)
g2d.drawString("评论数: ${videoInfo.items.first().statistics.commentCount.toInt().formatNumber()}", 340, 210)
g2d.dispose()
return image.toByteArray().encodeToBase64()
}
private fun getVideoInfo(videoId: String): YoutubeVideoResponse {
val response = Http.get<YoutubeVideoResponse>(
YOUTUBE_API_URL,
mapOf(
"id" to videoId,
"key" to dataApiKey,
"part" to "snippet,statistics"
)
)
return response
}
suspend fun parse(message: GroupMessage) {
val matchResult = youtubeVideoIdRegex.find(message.rawMessage)
if (matchResult != null) {
val videoId = matchResult.groupValues[1]
val videoInfo = this.getVideoInfo(videoId)
val image = createVideoCard(videoInfo)
val msg = MessageChain.Builder().addImage(image, true).build()
message.reply(msg)
}
}
}
@@ -0,0 +1,62 @@
/*
* Copyright © 2024 RTAkland
* Author: RTAkland
* Date: 2024/10/13
*/
package cn.rtast.fancybot.commands.reply
import cn.rtast.fancybot.commands.misc.ShortLinkCommand.Companion.makeShortLink
import cn.rtast.fancybot.commands.parse.ImageURLCommand
import cn.rtast.fancybot.configManager
import cn.rtast.fancybot.util.file.getFileType
import cn.rtast.fancybot.util.misc.ImageBed
import cn.rtast.fancybot.util.misc.toURL
import cn.rtast.rob.entity.GetMessage
import cn.rtast.rob.entity.GroupMessage
import cn.rtast.rob.onebot.MessageChain
import cn.rtast.rob.onebot.toNode
import okio.IOException
object ImageBedCommand {
suspend fun execute(getMsg: GetMessage.Message, message: GroupMessage) {
val imagesUrl = ImageURLCommand.getImageUrl(getMsg)
if (imagesUrl.isEmpty()) {
message.reply("这个消息里没有图片呢!")
} else {
try {
if (imagesUrl.size == 1) {
val imageByteArray = imagesUrl.first().toURL().readBytes()
val imageFileType = imageByteArray.getFileType()
val imageBedUrl = ImageBed.upload(imageByteArray, imageFileType)
val msg = MessageChain.Builder()
.addText(imageBedUrl.makeShortLink())
.addNewLine(2)
.addText(imageBedUrl)
.build()
message.reply(msg)
} else {
val messages = mutableListOf<MessageChain>()
imagesUrl.forEach {
val imageByteArray = it.toURL().readBytes()
val imageFileType = imageByteArray.getFileType()
val imageBedUrl = ImageBed.upload(imageByteArray, imageFileType)
val msg = MessageChain.Builder()
.addText(imageBedUrl.makeShortLink())
.addNewLine(2)
.addText(imageBedUrl)
.build()
messages.add(msg)
}
message.reply(messages.toNode(configManager.selfId))
}
} catch (_: IOException) {
message.reply("上传失败: 图片已过期")
} catch (e: Exception) {
e.printStackTrace()
message.reply("上传失败: ${e.message}")
}
}
}
}
@@ -0,0 +1,31 @@
/*
* Copyright © 2024 RTAkland
* Author: RTAkland
* Date: 2024/10/31
*/
package cn.rtast.fancybot.commands.reply
import cn.rtast.fancybot.util.misc.invertColor
import cn.rtast.fancybot.util.misc.toBufferedImage
import cn.rtast.fancybot.util.misc.toByteArray
import cn.rtast.fancybot.util.misc.toURL
import cn.rtast.fancybot.util.str.encodeToBase64
import cn.rtast.rob.entity.GetMessage
import cn.rtast.rob.entity.GroupMessage
import cn.rtast.rob.entity.images
import cn.rtast.rob.onebot.MessageChain
object InvertImageCommand {
suspend fun invert(message: GroupMessage, getMsg: GetMessage.Message) {
val msg = MessageChain.Builder()
getMsg.images.forEach {
val img = it.file.toURL().readBytes().toBufferedImage()
.invertColor().toByteArray().encodeToBase64()
msg.addImage(img, true)
}
message.reply(msg.build())
}
}
@@ -0,0 +1,33 @@
/*
* Copyright © 2024 RTAkland
* Author: RTAkland
* Date: 2024/10/31
*/
package cn.rtast.fancybot.commands.reply
import cn.rtast.fancybot.util.misc.randomColor
import cn.rtast.fancybot.util.misc.toBufferedImage
import cn.rtast.fancybot.util.misc.toByteArray
import cn.rtast.fancybot.util.misc.toURL
import cn.rtast.fancybot.util.str.encodeToBase64
import cn.rtast.rob.entity.GetMessage
import cn.rtast.rob.entity.GroupMessage
import cn.rtast.rob.entity.images
import cn.rtast.rob.onebot.MessageChain
object RandomRGBCommand {
suspend fun random(message: GroupMessage, getMsg: GetMessage.Message) {
val msg = MessageChain.Builder()
getMsg.images.forEach {
msg.addImage(
it.file.toURL().readBytes().toBufferedImage()
.randomColor().toByteArray().encodeToBase64(),
true
)
}
message.reply(msg.build())
}
}
@@ -0,0 +1,37 @@
/*
* Copyright © 2024 RTAkland
* Author: RTAkland
* Date: 2024/10/27
*/
package cn.rtast.fancybot.commands.reply
import cn.rtast.fancybot.commands.misc.ShortLinkCommand.Companion.makeShortLink
import cn.rtast.fancybot.commands.parse.AsciiArtCommand
import cn.rtast.fancybot.util.misc.ImageBed
import cn.rtast.fancybot.util.misc.makeGif
import cn.rtast.fancybot.util.misc.toURL
import cn.rtast.fancybot.util.str.encodeToBase64
import cn.rtast.rob.entity.GetMessage
import cn.rtast.rob.entity.GroupMessage
import cn.rtast.rob.onebot.MessageChain
import com.madgag.gif.fmsware.GifDecoder
object SpeedUpGIFCommand {
suspend fun speedUp(message: GroupMessage, getMsg: GetMessage.Message, multiply: Float) {
val gifUrl = AsciiArtCommand.getImageUrl(getMsg)
val gifStream = gifUrl.toURL().openStream()
val decoder = GifDecoder()
decoder.read(gifStream)
val frames = (0 until decoder.frameCount).map { decoder.getFrame(it) }
val gifBase64 = decoder.makeGif(frames, multiply)
val imgBedUrl = ImageBed.upload(gifBase64)
val shortLink = imgBedUrl.makeShortLink()
val msg = MessageChain.Builder()
.addImage(gifBase64.encodeToBase64(), true)
.addText(shortLink)
.build()
message.reply(msg)
}
}
@@ -0,0 +1,19 @@
/*
* Copyright © 2024 RTAkland
* Author: RTAkland
* Date: 2024/10/2
*/
package cn.rtast.fancybot.db
import org.jetbrains.exposed.sql.Table
object ActionTable : Table("action") {
val id = integer("id").autoIncrement()
val action = varchar("action", 255)
val timestamp = long("timestamp")
val triggerId = long("trigger_id")
val result = text("result")
override val primaryKey = PrimaryKey(id)
}
@@ -0,0 +1,24 @@
/*
* Copyright © 2024 RTAkland
* Author: RTAkland
* Date: 2024/10/11
*/
package cn.rtast.fancybot.db
import org.jetbrains.exposed.sql.Table
object BlackListTable : Table("blacklist") {
val id = integer("id").autoIncrement()
val groupId = long("group_id")
val operator = long("operator")
val timestamp = long("timestamp")
override val primaryKey = PrimaryKey(id)
}
data class BlackListEntity(
val groupId: Long,
val operator: Long,
val timestamp: Long,
)
@@ -5,7 +5,7 @@
*/ */
package cn.rtast.fancybot.entity.db package cn.rtast.fancybot.db
import org.jetbrains.exposed.sql.Table import org.jetbrains.exposed.sql.Table
@@ -0,0 +1,63 @@
/*
* Copyright © 2024 RTAkland
* Author: RTAkland
* Date: 2024/10/11
*/
package cn.rtast.fancybot.db
import cn.rtast.fancybot.db.MinecraftAccessTokenTable.timestamp
import cn.rtast.fancybot.db.MinecraftAccessTokenTable.token
import cn.rtast.fancybot.db.MinecraftAccessTokenTable.userId
import cn.rtast.fancybot.util.suspendedTransaction
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
import org.jetbrains.exposed.sql.Table
import org.jetbrains.exposed.sql.deleteWhere
import org.jetbrains.exposed.sql.insert
import org.jetbrains.exposed.sql.selectAll
import java.time.Instant
object MinecraftAccessTokenTable : Table("mc_access_token") {
val id = integer("id").autoIncrement()
val token = text("token")
val userId = long("user_id")
val timestamp = long("timestamp")
override val primaryKey = PrimaryKey(id)
}
data class MCAccessTokenEntity(
val token: String,
val userId: Long,
val timestamp: Long,
)
suspend fun insertNewAccessToken(accessToken: String, id: Long) {
suspendedTransaction {
MinecraftAccessTokenTable.insert {
it[token] = accessToken
it[userId] = id
it[timestamp] = Instant.now().epochSecond
}
}
}
suspend fun getAccessTokenById(id: Long): MCAccessTokenEntity? {
return suspendedTransaction {
MinecraftAccessTokenTable.selectAll()
.where { userId eq id }
.map {
MCAccessTokenEntity(
it[token],
it[userId],
it[timestamp]
)
}.singleOrNull()
}
}
suspend fun deleteAccessTokenById(id: Long) {
suspendedTransaction {
MinecraftAccessTokenTable.deleteWhere { userId eq id }
}
}
@@ -5,7 +5,7 @@
*/ */
package cn.rtast.fancybot.entity.db package cn.rtast.fancybot.db
import org.jetbrains.exposed.sql.Table import org.jetbrains.exposed.sql.Table
@@ -24,5 +24,5 @@ data class NiuziBankAccount(
val balance: Double, val balance: Double,
val interestRate: Double, val interestRate: Double,
val timestamp: Long, val timestamp: Long,
val nickname: String val nickname: String,
) )
@@ -5,7 +5,7 @@
*/ */
package cn.rtast.fancybot.entity.db package cn.rtast.fancybot.db
import org.jetbrains.exposed.sql.Table import org.jetbrains.exposed.sql.Table
@@ -0,0 +1,29 @@
/*
* Copyright © 2024 RTAkland
* Author: RTAkland
* Date: 2024/10/13
*/
package cn.rtast.fancybot.db
import org.jetbrains.exposed.sql.Table
object RCONTable : Table("rcon") {
val id = integer("id").autoIncrement()
val host = varchar("host", 50)
val port = integer("port")
val userId = long("user_id")
val password = varchar("password", 128)
val name = varchar("name", 50)
override val primaryKey = PrimaryKey(id)
}
data class RCONEntity(
val host: String,
val port: Int,
val userId: Long,
val name: String,
val password: String,
)
@@ -7,8 +7,9 @@
package cn.rtast.fancybot.entity package cn.rtast.fancybot.entity
import cn.rtast.fancybot.entity.enums.ImageType import cn.rtast.fancybot.enums.ImageBedType
import cn.rtast.fancybot.entity.enums.WSType import cn.rtast.fancybot.enums.ImageType
import cn.rtast.fancybot.enums.WSType
data class Config( data class Config(
val ncmAPI: String, val ncmAPI: String,
@@ -33,6 +34,22 @@ data class Config(
val llamaUrl: String, val llamaUrl: String,
val llamaModel: String, val llamaModel: String,
val qqMusicApiUrl: String, val qqMusicApiUrl: String,
val startUpNoticeUser: Long, val noticeUser: Long,
val selfId: Long val selfId: Long,
val tianXingApiKey: String,
val githubUser: String,
val githubImageRepo: String,
val imageBedType: ImageBedType,
val cloudflareAccountId: String,
val cloudflareR2AccessKeyId: String,
val cloudflareR2SecretKey: String,
val cloudflareR2BucketName: String,
val cloudflareR2PublicUrl: String,
val apiSpaceKey: String,
val apiRtastKey: String,
val azureAppClientId: String,
val youtubeDataApiKey: String,
val naiLongApiUrl: String,
val osuClientSecret: String,
val osuClientId: Int
) )
@@ -13,5 +13,5 @@ data class Hitokoto(
val uuid: String, val uuid: String,
@SerializedName("hitokoto") @SerializedName("hitokoto")
val sentence: String, val sentence: String,
val from: String val from: String,
) )
@@ -0,0 +1,15 @@
/*
* Copyright © 2024 RTAkland
* Author: RTAkland
* Date: 2024/10/23
*/
package cn.rtast.fancybot.entity
data class IPGeo(
val city: String,
val region: String,
val country: String,
val org: String,
)
@@ -1,12 +0,0 @@
/*
* Copyright © 2024 RTAkland
* Author: RTAkland
* Date: 2024/9/22
*/
package cn.rtast.fancybot.entity
data class ShortLink(
val id: String
)
@@ -0,0 +1,19 @@
/*
* Copyright © 2024 RTAkland
* Author: RTAkland
* Date: 2024/10/10
*/
package cn.rtast.fancybot.entity.apispace
data class IdiomExplainResponse(
val data: List<Data>,
) {
data class Data(
val name: String,
val sound: String,
val explanation: String,
val provenance: String,
)
}
@@ -10,7 +10,7 @@ package cn.rtast.fancybot.entity.bgm
import com.google.gson.annotations.SerializedName import com.google.gson.annotations.SerializedName
data class BGMSearch( data class BGMSearch(
val list: List<Result> val list: List<Result>,
) { ) {
data class Result( data class Result(
val url: String, val url: String,
@@ -21,6 +21,6 @@ data class BGMSearch(
) )
data class Images( data class Images(
val large: String val large: String,
) )
} }
@@ -0,0 +1,16 @@
/*
* Copyright © 2024 RTAkland
* Author: RTAkland
* Date: 2024/10/12
*/
package cn.rtast.fancybot.entity.bili
data class BiliUserInfo(
val name: String,
val level: Int,
val avatar: String,
val fans: Int,
val sign: String,
)
@@ -15,7 +15,7 @@ data class CardShare(
data class Meta( data class Meta(
@SerializedName("detail_1") @SerializedName("detail_1")
val detail: Detail?, val detail: Detail?,
val news: News? val news: News?,
) )
data class Detail( data class Detail(
@@ -26,6 +26,6 @@ data class CardShare(
data class News( data class News(
val tag: String, val tag: String,
val jumpUrl: String val jumpUrl: String,
) )
} }
Loaded 100 of 324 files, more files were not shown because too many files have changed in this diff. Show more