Author SHA1 Message Date
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
RTAkland bf9e6dd311 chore: bump rob to 1.11.8 2024-10-01 09:53:10 +08:00
RTAkland 7372724274 feat: add r18 setu api 2024-09-29 21:39:20 +08:00
RTAkland 44e3e093d1 chore: chore: chore: chore: chore: chore: chore: chore: chore: chore: chore: chore 2024-09-29 14:15:16 +08:00
RTAkland e2b6ffff38 chore: move ten setu to redeem list 2024-09-28 20:53:49 +08:00
RTAkland 042cef3340 chore: bump ronebot to 1.11.6 2024-09-28 18:03:25 +08:00
RTAkland 32c822c3a1 chore: improve rdm command 2024-09-28 16:31:55 +08:00
RTAkland 9aab28334e chore: add command description, and add command aliases 2024-09-28 14:48:11 +08:00
RTAkland 120fc38084 feat: add poke reply 2024-09-28 12:37:18 +08:00
RTAkland bc04c53517 chore: improve some command 2024-09-27 23:31:01 +08:00
RTAkland e040819e61 chore: update rob version 2024-09-27 19:36:56 +08:00
RTAkland 9762d8d566 chore: chore: chore: chore: chore: chore: chore 2024-09-27 15:22:24 +08:00
RTAkland cc30d8e1c0 chore: format code 2024-09-26 22:55:10 +08:00
RTAkland 8a31dce4d4 chore: use reply 2024-09-26 22:11:28 +08:00
RTAkland 4a2575e1c5 chore: remove command aliases 2024-09-26 22:09:04 +08:00
RTAkland 1953897642 chore: rename classes 2024-09-26 21:59:14 +08:00
RTAkland a51be7292f chore: chore 2024-09-26 21:58:17 +08:00
RTAkland 0016fa578d chore: improve niuzi bank command 2024-09-26 21:56:44 +08:00
RTAkland a6537628d5 fix: fix zibi not work 2024-09-26 21:56:16 +08:00
RTAkland ef7e3bf1ad fix: fix db bug 2024-09-26 19:16:16 +08:00
RTAkland 45a6cbece3 fix: fix no member null 2024-09-26 18:57:31 +08:00
RTAkland 3c147c9ad6 feat: add niuzi rank, and niuzi bank rank 2024-09-26 18:43:24 +08:00
RTAkland 8110ed6cd4 chore: lowercase match 2024-09-26 18:13:09 +08:00
RTAkland 862670183a feat: support query single command description 2024-09-26 17:51:01 +08:00
RTAkland 53a1807d9d chore: remove command alias 2024-09-26 17:43:13 +08:00
RTAkland 2d7d81c825 chore: rename 2024-09-26 17:42:49 +08:00
RTAkland 478c0ce4fd chore: functional programming 2024-09-26 17:41:11 +08:00
RTAkland e832b1cb3b feat: add gif ascii art generate 2024-09-26 17:38:28 +08:00
RTAkland a3058780be feat: add weekday select 2024-09-26 15:42:47 +08:00
RTAkland f332031961 feat: add gpbt command 2024-09-26 15:26:07 +08:00
RTAkland 04021a8fff fix: fix incorrect word 2024-09-26 15:06:53 +08:00
RTAkland 06d0aeb529 chore: improve help command 2024-09-26 14:01:26 +08:00
RTAkland 917d4f0d26 fix: fix mface and image ascii art generator 2024-09-26 12:33:09 +08:00
RTAkland 1b5a6c627d fix: mcping desc color code 2024-09-26 05:52:02 +08:00
RTAkland 6e38ff1b04 chore: change command names 2024-09-26 05:47:31 +08:00
RTAkland 7a6e00d4d2 chore: use kotlin's reflection 2024-09-26 05:44:47 +08:00
RTAkland cbfaf0d8aa ci: update cloc 2024-09-26 05:32:32 +08:00
RTAkland 9f0133de8a ci: add cloc 2024-09-26 05:29:35 +08:00
RTAkland 0442c0ef6a ci: add cloc 2024-09-26 05:27:03 +08:00
RTAkland 660e5bcbe8 fix: fix help command description 2024-09-26 05:21:42 +08:00
RTAkland 8dc311fb90 feat: add annotation to add command description 2024-09-26 05:20:10 +08:00
RTAkland 0a28d05bfe refactor: mcping command 2024-09-26 00:30:25 +08:00
RTAkland 9a9c767e17 chore: help sort name 2024-09-25 23:16:58 +08:00
RTAkland 1c89d54636 feat: add qq music api search 2024-09-25 23:04:49 +08:00
RTAkland ecd50ffea6 chore: add exception catch 2024-09-25 18:23:27 +08:00
RTAkland 19e980be86 chore: format code and add qodana 2024-09-25 18:18:28 +08:00
RTAkland 4d2be92081 chore: rename func 2024-09-25 18:13:03 +08:00
RTAkland 3e65d6ef3e chore: update ronebot version 2024-09-25 18:06:31 +08:00
RTAkland a2117f04a4 chore: update ronebot version 2024-09-25 16:14:57 +08:00
RTAkland a91a5f475d chore: chore 2024-09-25 16:01:15 +08:00
RTAkland 4a9483bf12 fix: fix transfer 2024-09-25 15:07:57 +08:00
RTAkland ae5f60df41 chore: format code 2024-09-25 14:48:59 +08:00
RTAkland ddad36b28d chore: update lib 2024-09-25 12:51:34 +08:00
RTAkland 7e2d2d7cdb feat: add llama command 2024-09-24 20:51:20 +08:00
RTAkland 82f4b3c542 chore: move obj 2024-09-24 20:21:30 +08:00
RTAkland 98f4f5d10f feat: add dm search 2024-09-24 19:11:04 +08:00
RTAkland 7688f538fb fix: fix withdraw 2024-09-24 15:46:22 +08:00
RTAkland 765a1a60e6 feat: add niuzi bank commands 2024-09-24 15:41:24 +08:00
RTAkland e51c7ecf04 chore: remove genshin command 2024-09-24 14:56:51 +08:00
RTAkland 57b454e7d0 feat: add failback 2024-09-24 14:55:09 +08:00
RTAkland 8fc6dd6443 feat: merge func 2024-09-24 14:46:06 +08:00
RTAkland 02578a8901 feat: add genshin command 2024-09-24 14:30:29 +08:00
RTAkland 0c19984e60 chore: format code 2024-09-24 13:35:26 +08:00
RTAkland e8b847a7ac feat: add niuzi transfer and niuzi query command 2024-09-24 13:30:08 +08:00
295 changed files with 6177 additions and 1384 deletions

No files matched your search

+19
View File
@@ -0,0 +1,19 @@
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/)来查看所有的命令
> ROneBot项目地址: [Gitlab](https://repo.rtast.cn/RTAkland/ronebot) [Github](https://github.com/RTAkland/ROneBot)
# 构建
由于项目使用的依赖过多项目构建出的产物在28Mb左右并且项目开启了
由于项目使用的依赖过多项目构建出的产物在40+Mb左右并且项目开启了
Github Action来自动化部署机器人, github Action的作用仅用作判断
构建是否成功以及自动部署机器人, 并不带有上传构建产物的功能(占用的空间太多)
所以你需要自行构建出机器人的jar包来运行
@@ -33,10 +34,23 @@ $ ./gradlew build clean
> 产物在`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地址
# 开源
+25 -14
View File
@@ -4,6 +4,7 @@ import org.jetbrains.kotlin.gradle.dsl.JvmTarget
plugins {
alias(libs.plugins.kotlin)
alias(libs.plugins.shadow)
id("application")
}
val fancyBotVersion: String by project
@@ -14,8 +15,12 @@ version = fancyBotVersion
repositories {
mavenCentral()
mavenLocal()
maven("https://jitpack.io")
maven("https://repo.opencollab.dev/main/")
maven("https://oss.sonatype.org/content/repositories/snapshots")
maven("https://repo.rtast.cn/api/v4/projects/33/packages/maven")
maven("https://repo.rtast.cn/api/v4/projects/19/packages/maven")
maven("https://repo.rtast.cn/api/v4/projects/3/packages/maven")
}
tasks.compileKotlin {
@@ -29,16 +34,26 @@ tasks.compileJava {
dependencies {
implementation(libs.rOneBot)
implementation(libs.ronebot)
implementation(libs.okhttp)
implementation(libs.exposedCore)
implementation(libs.exposedJDBC)
implementation(libs.sqliteJDBC)
implementation(libs.motdPinger)
implementation(libs.exposed.core)
implementation(libs.exposed.jdbc)
implementation(libs.exposed.dao)
implementation(libs.sqlite.jdbc)
implementation(libs.mysql.jdbc)
implementation(libs.postgressql.jdbc)
implementation(libs.motd.pinger)
implementation(libs.animated.gif.lib)
implementation(libs.simple.java.mail)
implementation(libs.jsoup)
implementation(libs.nashorn.core)
implementation(libs.zxing.core)
implementation(libs.zxing.javase)
implementation(libs.logback.classic)
implementation(libs.slf4j.api)
implementation(libs.aws.s3.sdk)
implementation(libs.rconlib)
implementation(libs.mcprotocollib)
}
tasks.build {
@@ -49,15 +64,6 @@ tasks.jar {
enabled = false
}
tasks.shadowJar {
manifest {
attributes(
"Main-Class" to "cn.rtast.fancybot.FancyBotKt",
"Manifest-Version" to "1.0"
)
}
}
tasks.register("deployBot") {
val apiKey = System.getenv("MCSM_API_KEY")
val apiUrl = System.getenv("MCSM_API_URL")
@@ -65,3 +71,8 @@ tasks.register("deployBot") {
val instanceId = System.getenv("MCSM_INSTANCE_ID")
MCSMDeploy(apiUrl, apiKey, daemonId, instanceId).deploy(file("build/libs/FancyBot-1.0.0-all.jar"))
}
application {
mainClass = "cn.rtast.fancybot.FancyBotKt"
applicationName = "FancyBot"
}
+27 -9
View File
@@ -1,27 +1,45 @@
[versions]
kotlinVersion = "2.0.10"
ronebotVersion = "1.10.0"
kotlinVersion = "2.0.20"
ronebotVersion = "2.0.2-SNAPSHOT"
shadowVersion = "8.3.0"
okhttpVersion = "5.0.0-alpha.14"
exposedVersion = "0.53.0"
sqliteJDBCVersion = "3.46.1.0"
motdPingerVersion = "1.0.1"
motdPingerVersion = "1.1.4"
animatedGifLibVersion = "1.4"
simpleJavaMailVersion = "8.11.3"
simpleJavaMailVersion = "8.12.2"
jsoupVersion = "1.18.1"
nashornVersion = "15.4"
zxingVersion = "3.5.3"
logbackClassicVersion = "1.5.8"
slf4jVersion = "2.0.16"
awsS3SDKVersion = "2.28.18"
rconLibVersion = "1.1.0"
mysqlConnectorVersion = "9.1.0"
postgresqlVersion = "42.7.4"
mcprotolobVersion = "1.21-SNAPSHOT"
[libraries]
rOneBot = { group = "cn.rtast", name = "ROneBot", version.ref = "ronebotVersion" }
motdPinger = { group = "cn.rtast", name = "MotdPinger", version.ref = "motdPingerVersion" }
ronebot = { group = "cn.rtast", name = "ROneBot", version.ref = "ronebotVersion" }
motd-pinger = { group = "cn.rtast", name = "MotdPinger", version.ref = "motdPingerVersion" }
okhttp = { group = "com.squareup.okhttp3", name = "okhttp", version.ref = "okhttpVersion" }
exposedCore = { group = "org.jetbrains.exposed", name = "exposed-core", version.ref = "exposedVersion" }
exposedJDBC = { group = "org.jetbrains.exposed", name = "exposed-jdbc", version.ref = "exposedVersion" }
sqliteJDBC = { group = "org.xerial", name = "sqlite-jdbc", version.ref = "sqliteJDBCVersion" }
exposed-core = { group = "org.jetbrains.exposed", name = "exposed-core", version.ref = "exposedVersion" }
exposed-jdbc = { group = "org.jetbrains.exposed", name = "exposed-jdbc", version.ref = "exposedVersion" }
exposed-dao = { group = "org.jetbrains.exposed", name = "exposed-dao", version.ref = "exposedVersion" }
sqlite-jdbc = { group = "org.xerial", name = "sqlite-jdbc", version.ref = "sqliteJDBCVersion" }
mysql-jdbc = { group = "com.mysql", name = "mysql-connector-j", version.ref = "mysqlConnectorVersion" }
postgressql-jdbc = { group = "org.postgresql", name = "postgresql", version.ref = "postgresqlVersion" }
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" }
jsoup = { group = "org.jsoup", name = "jsoup", version.ref = "jsoupVersion" }
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" }
logback-classic = { group = "ch.qos.logback", name = "logback-classic", version.ref = "logbackClassicVersion" }
slf4j-api = { group = "org.slf4j", name = "slf4j-api", version.ref = "slf4jVersion" }
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" }
[bundles]
+31
View File
@@ -0,0 +1,31 @@
#-------------------------------------------------------------------------------#
# Qodana analysis is configured by qodana.yaml file #
# https://www.jetbrains.com/help/qodana/qodana-yaml.html #
#-------------------------------------------------------------------------------#
version: "1.0"
#Specify inspection profile for code analysis
profile:
name: qodana.starter
#Enable inspections
#include:
# - name: <SomeEnabledInspectionId>
#Disable inspections
#exclude:
# - name: <SomeDisabledInspectionId>
# paths:
# - <path/where/not/run/inspection>
projectJDK: 17 #(Applied in CI/CD pipeline)
#Execute shell command before Qodana execution (Applied in CI/CD pipeline)
#bootstrap: sh ./prepare-qodana.sh
#Install IDE plugins before Qodana execution (Applied in CI/CD pipeline)
#plugins:
# - id: <plugin.id> #(plugin id can be found at https://plugins.jetbrains.com)
#Specify Qodana linter for analysis (Applied in CI/CD pipeline)
linter: jetbrains/qodana-jvm:latest
+104 -3
View File
@@ -4,14 +4,39 @@
* Date: 2024/8/27
*/
@file:Suppress("DEPRECATION")
package cn.rtast.fancybot
import cn.rtast.fancybot.commands.AboutCommand
import cn.rtast.fancybot.commands.EchoCommand
import cn.rtast.fancybot.commands.HelpCommand
import cn.rtast.fancybot.commands.ShuffleEchoCommand
import cn.rtast.fancybot.commands.StatusCommand
import cn.rtast.fancybot.commands.SupportMeCommand
import cn.rtast.fancybot.commands.lookup.*
import cn.rtast.fancybot.commands.misc.*
import cn.rtast.fancybot.commands.misc.JrrpCommand
import cn.rtast.fancybot.commands.misc.ShortLinkCommand
import cn.rtast.fancybot.commands.niuzi.*
import cn.rtast.fancybot.commands.niuzi.NiuziRedeemCommand
import cn.rtast.fancybot.commands.parse.AsciiArtCommand
import cn.rtast.fancybot.commands.parse.ReverseGIFCommand
import cn.rtast.fancybot.entity.Config
import cn.rtast.fancybot.entity.enums.ImageType
import cn.rtast.fancybot.entity.enums.WSType
import cn.rtast.fancybot.enums.DatabaseType
import cn.rtast.fancybot.enums.ImageBedType
import cn.rtast.fancybot.enums.ImageType
import cn.rtast.fancybot.enums.WSType
import cn.rtast.fancybot.items.*
import cn.rtast.fancybot.util.file.BlackListManager
import cn.rtast.fancybot.util.file.ConfigManager
import cn.rtast.fancybot.util.file.NiuziBankManager
import cn.rtast.fancybot.util.file.NiuziManager
import cn.rtast.fancybot.util.file.RCONManager
import cn.rtast.fancybot.util.item.ItemManager
import com.google.gson.Gson
import com.google.gson.GsonBuilder
import java.time.Instant
val gson: Gson = GsonBuilder()
.setPrettyPrinting()
@@ -19,8 +44,19 @@ val gson: Gson = GsonBuilder()
.create()
const val ROOT_PATH = "./data"
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(
database = Config.Database(
DatabaseType.SQLite,
"127.0.0.1",
3306,
"root",
"114514",
"fancybot"
),
ncmAPI = "https://ncm.rtast.cn",
wsAddress = "ws://127.0.0.1",
wsType = WSType.Client,
@@ -32,11 +68,76 @@ val DEFAULT_CONFIG = Config(
imageType = ImageType.PNG,
openAIAPIHost = "https://api.moonshot.cn",
openAIAPIKey = "114514",
openAIModel = "moonshot-v1-8k",
smtpHost = "127.0.0.1",
smtpPort = 25,
smtpUser = "114514",
smtpPassword = "1919810",
smtpFromAddress = "fancybot@repo.rtast.cn",
admins = listOf(3458671395L),
enableAntiRevoke = true
enableAntiRevoke = true,
llamaUrl = "http://127.0.0.1",
llamaModel = "llama3.1",
qqMusicApiUrl = "http://127.0.0.1:3200",
noticeUser = 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"
)
val configManager = ConfigManager()
val itemManager = ItemManager()
val niuziManager = NiuziManager()
val niuziBankManager = NiuziBankManager()
val blackListManager = BlackListManager()
val rconManager = RCONManager()
val START_UP_TIME = Instant.now().epochSecond
val items = listOf(
HeisiItem(), BaisiItem(),
SetuItem(), NiuziItem(),
TenSetuItem(), TenSetuR18Item()
)
val tasks = mapOf(
10000000L to suspend {
configManager.admins.forEach {
instance.action.sendLike(it, 1)
}
}
)
val commands = listOf(
EchoCommand(), JrrpCommand(), NiuziRedeemCommand(), HitokotoCommand(),
KFCCommand(), GenerateQRCodeCommand(), AntiRevokeCommand(), MCPingCommand(),
WeatherCommand(), CigaretteCommand(), RemakeCommand(), PixivCommand(),
RUACommand(), NslookupCommand(), CompilerCommand(), AICommand(),
GithubUserCommand(), AboutCommand(), MusicPlayUrlCommand(), DomainPriceCommand(),
SendMailCommand(), ZiBiCommand(), UnsetZiBiCommand(), WikipediaCommand(),
AsciiArtCommand(), StatusCommand(), JueCommand(), ShortLinkCommand(),
ShotOtherCommand(), ReverseGIFCommand(), HelpCommand(), MusicCommand(),
SendLikeCommand(), NiuziTransferCommand(), NiuziSignCommand(), NiuziJiJianCommand(),
NiuziQueryCommand(), MyNiuziCommand(), NiuziBankCommand(), WithdrawCommand(),
BankTransferCommand(), CreateBankAccountCommand(), BankBalanceCommand(), DepositCommand(),
AnimeSearchCommand(), LlamaCommand(), QQMusicCommand(), BullShitGenerateCommand(),
NiuziRankCommand(), NiuziBankRankCommand(), ShotSelfCommand(), TenSetuCommand(),
ReactionCommand(), MCSkinCommand(), GithubLatestCommitCommand(), RandomMusicCommand(),
ZDJDCommand(), ScanQRCodeCommand(), TTSCommand(), SlimeChunkHelperCommand(),
SupportMeCommand(), TheCatCommand(), TheHistoryOfTodayCommand(), MCVersionCommand(),
MinecraftWikiCommand(), GaoKaoDaysRemainCommand(), IdiomExplainCommand(), NiuziManagerCommand(),
TodayEatCommand(), TodayDrinkCommand(), MCLoginCommand(), RCONCommand(),
GravatarCommand(), PastebinCommand(), ShuffleEchoCommand(), TodayDontEatCommand(),
TodayDontDrinkCommand(), MCBotCommand(), HTTPCatCommand(), HTTPDogCommand()
)
+104 -125
View File
@@ -7,59 +7,42 @@
package cn.rtast.fancybot
import cn.rtast.fancybot.commands.AboutCommand
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.misc.*
import cn.rtast.fancybot.commands.parse.AsciiArtCommand
import cn.rtast.fancybot.commands.parse.BVParseCommand
import cn.rtast.fancybot.commands.parse.CalculateCommand
import cn.rtast.fancybot.commands.parse.GitHubParseCommand
import cn.rtast.fancybot.commands.parse.ImageURLCommand
import cn.rtast.fancybot.commands.parse.ReverseGIFCommand
import cn.rtast.fancybot.commands.record.JiJianCommand
import cn.rtast.fancybot.commands.record.JrrpCommand
import cn.rtast.fancybot.commands.record.MyNiuziCommand
import cn.rtast.fancybot.commands.record.MyPointCommand
import cn.rtast.fancybot.commands.record.NiuziSignCommand
import cn.rtast.fancybot.commands.record.RedeemCommand
import cn.rtast.fancybot.commands.record.ShortLinkCommand
import cn.rtast.fancybot.commands.record.SignCommand
import cn.rtast.fancybot.entity.bili.CardShare
import cn.rtast.fancybot.entity.enums.WSType
import cn.rtast.fancybot.items.BaisiItem
import cn.rtast.fancybot.items.HeisiItem
import cn.rtast.fancybot.items.SetuItem
import cn.rtast.fancybot.util.file.ConfigManager
import cn.rtast.fancybot.util.file.NiuziManager
import cn.rtast.fancybot.util.file.SignManager
import cn.rtast.fancybot.util.initDatabase
import cn.rtast.fancybot.util.item.ItemManager
import cn.rtast.fancybot.util.str.fromJson
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.reply.ImageBedCommand
import cn.rtast.fancybot.enums.WSType
import cn.rtast.fancybot.util.*
import cn.rtast.fancybot.util.misc.convertToDate
import cn.rtast.fancybot.util.misc.initBackgroundTasks
import cn.rtast.fancybot.util.misc.initCommand
import cn.rtast.fancybot.util.misc.initFilesDir
import cn.rtast.fancybot.util.misc.initItems
import cn.rtast.fancybot.util.misc.initSetuIndex
import cn.rtast.rob.ROneBotFactory
import cn.rtast.rob.entity.AddFriendRequest
import cn.rtast.rob.entity.FileEvent
import cn.rtast.rob.entity.GetMessage
import cn.rtast.rob.entity.GroupMessage
import cn.rtast.rob.entity.GroupRevokeMessage
import cn.rtast.rob.entity.*
import cn.rtast.rob.entity.lagrange.FileEvent
import cn.rtast.rob.entity.lagrange.PokeEvent
import cn.rtast.rob.enums.ArrayMessageType
import cn.rtast.rob.enums.QQFace
import cn.rtast.rob.util.ob.MessageChain
import cn.rtast.rob.util.ob.OneBotListener
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import java.io.File
import java.net.URI
import java.time.Instant
class FancyBot : OneBotListener {
private val logger = Logger.getLogger<FancyBot>()
private val coroutineScope = CoroutineScope(Dispatchers.IO)
override suspend fun onWebsocketOpenEvent() {
this.broadcastMessage("FancyBot启动成功了呢~")
instance.action.sendPrivateMessage(configManager.noticeUser, "FancyBot启动成~")
}
override suspend fun onGroupMessage(message: GroupMessage, json: String) {
@@ -68,22 +51,72 @@ class FancyBot : OneBotListener {
val msg = message.rawMessage
val groupId = message.groupId
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.rawMessage.toList().any { it in arrayListOf('*', '-', '/', '+', '=') }) {
if (message.message.any { it.type == ArrayMessageType.face && it.data.id.toString() == "419" }) {
message.reply("你发牛魔的火车呢, 我直接就是打断")
}
if (message.text.contains("原神")) {
(0..10).forEach { _ -> message.reaction(QQFace.entries.random()) }
message.reply("你原神牛魔呢")
delay(1500L)
message.reply("但是话又说回来.....启动!!")
}
GitHubParseCommand.parse(message)
ReverseGIFCommand.callback(message)
AsciiArtCommand.callback(message)
ScanQRCodeCommand.callback(message)
BiliVideoParseCommand.parse(this, message)
BiliUserParseCommand.parse(this, message)
DouyinVideoParseCommand.parse(message)
YoutubeVideoParseCommand.parse(message)
if (message.text.toList().any { it in arrayListOf('*', '-', '/', '+', '=') }) {
val calculateResult = CalculateCommand.parse(message.rawMessage)
calculateResult?.let { message.reply(calculateResult) }
}
ReverseGIFCommand.callback(message)
AsciiArtCommand.callback(message)
if (message.message.any { it.type == ArrayMessageType.reply }) { // Image url parse
if (message.message.any { it.type == ArrayMessageType.reply }) {
val command = message.message.reversed().find { it.type == ArrayMessageType.text }!!.data.text!!
val replyId = message.message.find { it.type == ArrayMessageType.reply }!!.data.id!!
val getMsg = instance.action.getMessage(replyId.toString().toLong())
val plainTextContent = getMsg.message
.filter { it.type == ArrayMessageType.text }
.joinToString { it.data.text!! }
if (command.contains("倒放") || command.contains("/df")) {
// 使用回复消息的方式对一个gif倒放
ReverseGIFCommand.reverse(message, getMsg)
}
if (command.contains("/sl") || command.contains("/short")) {
// 使用回复消息的方式快速对一个url消息创建一个短链接
message.reply(plainTextContent.trim().makeShortLink())
}
if (command.contains("/pb") || command.contains("/pastebin")) {
// 使用回复消息的方式快速将创建一个pastebin
val pastebinUrl = PastebinCommand.createPastebin(plainTextContent)
message.reply(pastebinUrl)
}
if (command.contains("/ascii") || command.contains("/asc")) {
// 使用回复消息的方式直接对一个图片进行生成Ascii art的操作
val url = AsciiArtCommand.getImageUrl(getMsg)
val image = AsciiArtCommand.generateAsciiArt(url)
message.reply(image)
}
if (command.contains("图来") || command.contains("图链")) {
val getMsg = this.getMessage(replyId.toString().toLong())
// 获取一个或多个图片的链接, 并且生成一个或多个短链接
ImageURLCommand.callback(message, getMsg)
}
if (command.contains("reaction")) {
// 使用reaction刷屏回应一条消息
ReactionCommand.reaction(message)
}
if (command.contains("图床")) {
// 将一个图片上传到图床
ImageBedCommand.execute(getMsg, message)
}
}
coroutineScope.launch {
@@ -91,47 +124,13 @@ class FancyBot : OneBotListener {
if (it.type == ArrayMessageType.image) {
val filename = it.data.file!!.split("=").last() + ".png"
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())
}
}
}
}
}
if (message.rawMessage.startsWith("https://github.com/") || message.rawMessage.startsWith("git@github.com:")) {
GitHubParseCommand.parse(this, message)
}
if (message.rawMessage.startsWith("BV") ||
message.rawMessage.startsWith("https://www.bilibili.com") ||
message.rawMessage.contains("https://b23.tv/") ||
message.message.find { it.type == ArrayMessageType.json } != null
) {
// 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() }
}
BVParseCommand.parse(this, bvid, message)
}
}
override suspend fun onGroupMessageRevoke(message: GroupRevokeMessage) {
@@ -141,7 +140,7 @@ class FancyBot : OneBotListener {
.addNewLine()
.addText("使用/revoke ${message.messageId} 来获取被撤回的消息")
.build()
this.sendGroupMessage(message.groupId, msg)
instance.action.sendGroupMessage(message.groupId, msg)
}
override suspend fun onWebsocketErrorEvent(ex: Exception) {
@@ -153,66 +152,43 @@ class FancyBot : OneBotListener {
}
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) {
val msg = MessageChain.Builder()
.addText("有人退群了")
.addText("有人坐飞船离开了本星系~")
.addNewLine()
.addText("QQ: $userId | 操作者: ${if (operator == 0L) "主动退出" else operator}")
.addNewLine()
.addText("下次再见吧~~~")
.build()
this.sendGroupMessage(groupId, msg)
instance.action.sendGroupMessage(groupId, msg)
}
override suspend fun onGroupPoke(event: PokeEvent) {
if (event.targetId == configManager.selfId) {
val msg = MessageChain.Builder()
.addAt(event.userId)
.addText("${event.action.first()}牛魔呢")
.build()
instance.action.sendGroupMessage(event.groupId!!, msg)
}
}
val configManager = ConfigManager()
val itemManager = ItemManager()
val signManager = SignManager()
val niuziManager = NiuziManager()
val items = listOf(
HeisiItem(),
BaisiItem(),
SetuItem()
override suspend fun onBeKicked(groupId: Long, operator: Long, time: Long) {
blackListManager.insertGroup(groupId, operator, time)
instance.action.sendPrivateMessage(
configManager.noticeUser,
"被: ${groupId}踢出! 操作人: $operator 时间: ${time.convertToDate()} 已将其拉入黑名单!"
)
val commands = listOf(
EchoCommand(), JrrpCommand(),
MusicCommand(), SignCommand(),
RedeemCommand(), MyPointCommand(),
HitokotoCommand(), FKXQSCommand(),
QRCodeCommand(), AntiRevokeCommand(),
MCPingCommand(), HelpCommand(),
WeatherCommand(), CigaretteCommand(),
RemakeCommand(), PixivCommand(),
RUACommand(), NslookupCommand(),
NiuziSignCommand(), MyNiuziCommand(),
JiJianCommand(), LikeMeCommand(),
CompilerCommand(), AICommand(),
GithubUserCommand(), AboutCommand(),
MusicPlayUrlCommand(), DomainPriceCommand(),
SendMailCommand(), ZiBiCommand(),
UnsetZiBiCommand(), WikipediaCommand(),
AsciiArtCommand(), StatusCommand(),
JueCommand(), ShortLinkCommand(),
TenSetuCommand(), ShotSelfCommand(),
ShotOtherCommand(), ReverseGIFCommand()
)
val START_UP_TIME = Instant.now().epochSecond
fun initFilesDir() {
File("./files/images").also { it.mkdirs() }
}
}
suspend fun main() {
val fancyBot = FancyBot()
val workType = configManager.wsType
val accessToken = configManager.accessToken
val rob = if (workType == WSType.Client) {
val instance = if (workType == WSType.Client) {
val address = configManager.wsAddress
ROneBotFactory.createClient(address, accessToken, fancyBot)
.also { it.addListeningGroups(*configManager.listeningGroups.toLongArray()) }
@@ -221,9 +197,12 @@ suspend fun main() {
ROneBotFactory.createServer(port, accessToken, fancyBot)
.also { it.addListeningGroups(*configManager.listeningGroups.toLongArray()) }
}
suspend fun main() {
initDatabase()
initFilesDir()
val commandManager = rob.commandManager
commands.forEach { commandManager.register(it) }
items.forEach { itemManager.register(it) }
initCommand()
initSetuIndex()
initItems()
initBackgroundTasks(instance)
}
@@ -0,0 +1,12 @@
/*
* Copyright © 2024 RTAkland
* Author: RTAkland
* Date: 2024/9/26
*/
package cn.rtast.fancybot.annotations
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME)
annotation class CommandDescription(val description: String)
@@ -7,18 +7,18 @@
package cn.rtast.fancybot.commands
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 AboutCommand : BaseCommand() {
override val commandNames = listOf("/about", "/关于")
override suspend fun executeGroup(listener: OneBotListener, message: GroupMessage, args: List<String>) {
val msg = MessageChain.Builder()
.addAt(message.sender.userId)
.addNewLine()
.addText("Bot名称: FancyBot")
.addNewLine()
.addText("作者: RTAkland")
@@ -27,6 +27,6 @@ class AboutCommand : BaseCommand() {
.addNewLine()
.addText("项目地址: https://repo.rtast.cn/RTAkland/FancyBot")
.build()
listener.sendGroupMessage(message.groupId, msg)
message.reply(msg)
}
}
@@ -7,14 +7,37 @@
package cn.rtast.fancybot.commands
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.util.BaseCommand
import cn.rtast.rob.util.ob.OneBotListener
@CommandDescription("Echo")
class EchoCommand : BaseCommand() {
override val commandNames = listOf("/echo")
override suspend fun executeGroup(listener: OneBotListener, 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(listener: OneBotListener, 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")
}
}
}
@@ -7,24 +7,53 @@
package cn.rtast.fancybot.commands
import cn.rtast.fancybot.annotations.CommandDescription
import cn.rtast.fancybot.commands
import cn.rtast.fancybot.configManager
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.NodeMessageChain
import cn.rtast.rob.util.ob.OneBotListener
import kotlin.reflect.full.findAnnotation
import kotlin.reflect.full.hasAnnotation
@CommandDescription("帮助")
class HelpCommand : BaseCommand() {
override val commandNames = listOf("/help", "/帮助")
override suspend fun executeGroup(listener: OneBotListener, message: GroupMessage, args: List<String>) {
val allCommands = commands.joinToString("\n") {
"[${it.javaClass.name.split(".").last().replace("Command", "")}] 命令:${it.commandNames.joinToString(",")}"
}
if (args.isEmpty()) {
val node = NodeMessageChain.Builder()
val msg = MessageChain.Builder()
.addAt(message.sender.userId)
commands.sortedBy { it::class.simpleName }.toMutableList().forEach {
val description = if (!it::class.hasAnnotation<CommandDescription>()) "暂无描述"
else it::class.findAnnotation<CommandDescription>()?.description!!
val commandName = it::class.simpleName?.replace("Command", "")!!
val commandNames = it.commandNames.joinToString(",")
msg.addText("[$commandName] [$description] 命令: $commandNames").addNewLine()
}
msg.addText("共计${commands.size}条命令")
node.addMessageChain(msg.build(), configManager.selfId)
message.reply(node.build())
} else {
val commandName = args.first().replace("/", "")
val matchedCommand = commands.find {
it.commandNames.map { map -> map.replace("/", "").lowercase() }.contains(commandName)
}
if (matchedCommand == null) {
message.reply("未查询到该命令")
return
}
val description = matchedCommand::class.findAnnotation<CommandDescription>()?.description ?: "暂无描述"
val msg = MessageChain.Builder()
.addText("类名: ${matchedCommand::class.simpleName}")
.addNewLine()
.addText(allCommands)
.addText("指令: ${matchedCommand.commandNames.joinToString(",")}")
.addNewLine()
.addText("描述: $description")
.build()
listener.sendGroupMessage(message.groupId, msg)
message.reply(msg)
}
}
}
@@ -8,12 +8,16 @@
package cn.rtast.fancybot.commands
import cn.rtast.fancybot.START_UP_TIME
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.util.BaseCommand
import cn.rtast.rob.util.ob.MessageChain
import cn.rtast.rob.util.ob.OneBotListener
import java.time.Instant
@CommandDescription("状态")
class StatusCommand : BaseCommand() {
override val commandNames = listOf("/status")
@@ -42,5 +46,6 @@ class StatusCommand : BaseCommand() {
.addText("Java版本: $javaVersion / Kotlin版本: $kotlinVersion")
.build()
message.reply(msg)
insertActionRecord(CommandAction.Status, message.sender.userId)
}
}
@@ -0,0 +1,40 @@
/*
* 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.util.BaseCommand
import cn.rtast.rob.util.ob.MessageChain
import cn.rtast.rob.util.ob.OneBotListener
import java.net.URI
@CommandDescription("给我打钱!")
class SupportMeCommand : BaseCommand() {
override val commandNames = listOf("打钱", "给我打钱", "给满子打钱")
override suspend fun executeGroup(
listener: OneBotListener,
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)
}
}
@@ -7,31 +7,39 @@
package cn.rtast.fancybot.commands.lookup
import cn.rtast.fancybot.annotations.CommandDescription
import cn.rtast.fancybot.configManager
import cn.rtast.fancybot.entity.gpt.ChatCompletionsPayload
import cn.rtast.fancybot.entity.gpt.ChatCompletionsResponse
import cn.rtast.fancybot.entity.gpt.LlamaResponse
import cn.rtast.fancybot.entity.gpt.ModelList
import cn.rtast.fancybot.enums.CommandAction
import cn.rtast.fancybot.instance
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
import cn.rtast.rob.util.ob.MessageChain
import cn.rtast.rob.util.ob.NodeMessageChain
import cn.rtast.rob.util.ob.OneBotListener
@CommandDescription("问AI(GPT)")
class AICommand : BaseCommand() {
override val commandNames = listOf("/ai", "问AI")
override val commandNames = listOf("/ai")
private val openAIModel = configManager.openAIModel
override suspend fun executeGroup(listener: OneBotListener, message: GroupMessage, args: List<String>) {
if (args.isEmpty()) {
val msg = MessageChain.Builder()
.addAt(message.sender.userId)
.addText("发送`/ai [模型] <问题>`即可询问AI哦~")
.addNewLine()
.addText("不指定模型默认为`moonshot-v1-8k`")
.addText("不指定模型默认为从配置文件中读取 >>>${openAIModel}")
.addNewLine()
.addText("发送`/ai list`可以获取可用的模型列表~")
.build()
listener.sendGroupMessage(message.groupId, msg)
message.reply(msg)
return
}
@@ -42,27 +50,54 @@ class AICommand : BaseCommand() {
)
val modelsString = models.data.joinToString(", ") { it.id }
val msg = MessageChain.Builder()
.addReply(message.messageId)
.addText("可用的模型列表如下: ")
.addNewLine()
.addText(modelsString)
.build()
listener.sendGroupMessage(message.groupId, msg)
message.reply(msg)
return
}
val model = if (args.size == 1) "moonshot-v1-8k" else args.first()
val model = if (args.size == 1) openAIModel else args.first()
val content = if (args.size == 1) args.joinToString(" ") else args.drop(1).joinToString(" ")
val messages = ChatCompletionsPayload(model, listOf(ChatCompletionsPayload.Message(content)))
val response = Http.post<ChatCompletionsResponse>(
"${configManager.openAIAPIHost}/v1/chat/completions",
messages.toJson(),
"${configManager.openAIAPIHost}/v1/chat/completions", messages.toJson(),
mapOf("Authorization" to "Bearer ${configManager.openAIAPIKey}")
)
val nodeMsg = NodeMessageChain.Builder()
val msg = MessageChain.Builder()
.addReply(message.messageId)
.addText(response.choices.first().message.content)
.build()
listener.sendGroupMessage(message.groupId, msg)
nodeMsg.addMessageChain(msg, configManager.selfId)
instance.action.sendGroupForwardMsg(message.groupId, nodeMsg.build())
insertActionRecord(CommandAction.AI, message.sender.userId, "$content-$model-GPT")
}
}
@CommandDescription("问AI(LLAMA)")
class LlamaCommand : BaseCommand() {
override val commandNames = listOf("/llama")
private val llamaURL = configManager.llamaUrl
private val llamaModel = configManager.llamaModel
override suspend fun executeGroup(listener: OneBotListener, message: GroupMessage, args: List<String>) {
if (args.isEmpty()) {
message.reply("发送`/llama <问题>`即可使用llama模型来回复")
return
}
val prompt = args.joinToString(" ")
val payload = ChatCompletionsPayload(llamaModel, listOf(ChatCompletionsPayload.Message(prompt)))
val response = Http.post<LlamaResponse>("$llamaURL/api/chat", payload.toJson())
val nodeMsg = NodeMessageChain.Builder()
val msg = MessageChain.Builder()
.addText("AI回复如下:")
.addNewLine()
.addText(response.message.content)
.build()
nodeMsg.addMessageChain(msg, configManager.selfId)
instance.action.sendGroupForwardMsg(message.groupId, nodeMsg.build())
insertActionRecord(CommandAction.AI, message.sender.userId, "$prompt-LLAMA")
}
}
@@ -0,0 +1,67 @@
/*
* Copyright © 2024 RTAkland
* Author: RTAkland
* Date: 2024/9/24
*/
package cn.rtast.fancybot.commands.lookup
import cn.rtast.fancybot.annotations.CommandDescription
import cn.rtast.fancybot.configManager
import cn.rtast.fancybot.entity.bgm.BGMSearch
import cn.rtast.fancybot.instance
import cn.rtast.fancybot.util.Http
import cn.rtast.fancybot.util.str.encodeToBase64
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.NodeMessageChain
import cn.rtast.rob.util.ob.OneBotListener
import okhttp3.OkHttpClient
import okhttp3.Request
@CommandDescription("搜索动漫(Bangumi.tv)")
class AnimeSearchCommand : BaseCommand() {
override val commandNames = listOf("/dm", "动漫")
private val client = OkHttpClient()
companion object {
private const val API_URL = "https://api.bgm.tv/search/subject/#{}?type=2&responseGroup=small"
}
private fun followRedirect(url: String): ByteArray {
val request = Request.Builder().url(url).build()
client.newCall(request).execute().use { response ->
return response.body.bytes()
}
}
override suspend fun executeGroup(listener: OneBotListener, message: GroupMessage, args: List<String>) {
if (args.isEmpty()) {
message.reply("发送`/dm <关键词>`即可搜索番剧啦~")
return
}
try {
val keyword = args.joinToString(" ")
val response = Http.get<BGMSearch>(API_URL.replace("#{}", keyword)).list
val nodeMsg = NodeMessageChain.Builder()
response.asSequence().take(5).forEach {
val imageBase64 = this.followRedirect(it.images.large).encodeToBase64()
val tempMsg = MessageChain.Builder()
.addImage(imageBase64, true)
.addText("${it.name} | ${it.nameCN}")
.addNewLine()
.addText(it.url)
.build()
nodeMsg.addMessageChain(tempMsg, configManager.selfId)
}
val footerNode = MessageChain.Builder().addText("数据来源: Bangumi").build()
nodeMsg.addMessageChain(footerNode, configManager.selfId)
instance.action.sendGroupForwardMsg(message.groupId, nodeMsg.build())
} catch (_: Exception) {
message.reply("没有搜索到结果呢~")
}
}
}
@@ -7,32 +7,32 @@
package cn.rtast.fancybot.commands.lookup
import cn.rtast.fancybot.annotations.CommandDescription
import cn.rtast.fancybot.configManager
import cn.rtast.fancybot.entity.cigarette.Cigarette
import cn.rtast.fancybot.util.Http
import cn.rtast.fancybot.util.str.encodeToBase64
import cn.rtast.rob.entity.GroupMessage
import cn.rtast.rob.segment.Node
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
@CommandDescription("查询香烟价格")
class CigaretteCommand : BaseCommand() {
override val commandNames = listOf("/香烟", "/tobacco", "/cigarette")
override val commandNames = listOf("/tobacco")
private val apiUrl = "https://www.yanyue.cn/api/rc/product/yanlist"
override suspend fun executeGroup(listener: OneBotListener, message: GroupMessage, args: List<String>) {
if (args.isEmpty()) {
val msg = MessageChain.Builder()
.addAt(message.sender.userId)
.addNewLine()
.addText("发送/tobacco <名称> [数量] 来搜索结果哦~")
.addNewLine()
.addText("最多只能显示出10条结果哦, 不指定数量的话默认是3条~")
.build()
listener.sendGroupMessage(message.groupId, msg)
message.reply(msg)
return
}
val limit = if (args.size == 1) 3 else if (args.last().toInt() > 10) 3 else args.last().toInt()
@@ -42,8 +42,7 @@ class CigaretteCommand : BaseCommand() {
val headerMsg = MessageChain.Builder()
.addText("搜索到${result.totalCount}条结果, 仅展示前 $limit 条结果哦~")
.build()
val headerNode = Node(Node.Data("", message.userId.toString(), headerMsg.finalArrayMsgList))
nodeMsg.addNode(headerNode)
nodeMsg.addMessageChain(headerMsg, message.sender.userId)
result.productList.asSequence().take(limit).forEach {
val imageBase64 = URI(it.cover).toURL().readBytes().encodeToBase64()
val tempMsg = MessageChain.Builder()
@@ -55,14 +54,13 @@ class CigaretteCommand : BaseCommand() {
.addText("参考价: 单盒: ${it.packPrice}元 | 整条: ${it.barPrice}")
.addNewLine()
.build()
val tempNode = Node(Node.Data("", message.userId.toString(), tempMsg.finalArrayMsgList))
nodeMsg.addNode(tempNode)
nodeMsg.addMessageChain(tempMsg, configManager.selfId)
}
val footerMsg = MessageChain.Builder()
.addText("吸烟有害健康, 尽早戒烟有益健康。")
.build()
val footerNode = Node(Node.Data("", message.userId.toString(), footerMsg.finalArrayMsgList))
nodeMsg.addNode(footerNode)
listener.sendGroupForwardMsg(message.groupId, nodeMsg.build())
nodeMsg.addMessageChain(footerMsg, configManager.selfId)
message.reply(nodeMsg.build())
message.sender.poke()
}
}
@@ -7,12 +7,13 @@
package cn.rtast.fancybot.commands.lookup
import cn.rtast.fancybot.annotations.CommandDescription
import cn.rtast.fancybot.entity.domain.PricePayload
import cn.rtast.fancybot.entity.domain.PriceResponse
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.toJson
import cn.rtast.fancybot.util.toByteArray
import cn.rtast.rob.entity.GroupMessage
import cn.rtast.rob.util.BaseCommand
import cn.rtast.rob.util.ob.MessageChain
@@ -21,8 +22,9 @@ import java.awt.Color
import java.awt.Font
import java.awt.image.BufferedImage
@CommandDescription("查询域名价格")
class DomainPriceCommand : BaseCommand() {
override val commandNames = listOf("/domain")
override val commandNames = listOf("/domain", "/dp")
private val canvasWidth = 400
private val canvasHeight = 300
@@ -67,7 +69,7 @@ class DomainPriceCommand : BaseCommand() {
g2d.color = textColor
g2d.drawString("域名$domain${if (available) "可以注册" else "已被注册"}", 50, 110)
g2d.drawString("${if (premium) "是" else "不是"}白金域名", 50, 140)
g2d.drawString("首年注册价格: ${domainPrice.first}", 50, 170)
g2d.drawString("首年注册价格: ${domainPrice.first}", 50, 170)
g2d.drawString("续费价格: ${domainPrice.second}", 50, 200)
g2d.drawString("数据来源: 腾讯云", 50, 230)
g2d.dispose()
@@ -78,10 +80,9 @@ class DomainPriceCommand : BaseCommand() {
override suspend fun executeGroup(listener: OneBotListener, message: GroupMessage, args: List<String>) {
if (args.isEmpty()) {
val msg = MessageChain.Builder()
.addAt(message.sender.userId)
.addText("发送`/domain <域名>`即可查询对应的注册和续费价格~")
.build()
listener.sendGroupMessage(message.groupId, msg)
message.reply(msg)
return
}
val suffix = args.first()
@@ -101,10 +102,7 @@ class DomainPriceCommand : BaseCommand() {
} else {
this.createDomainPriceCard(suffix, response.realPrice to response.price)
}
val msg = MessageChain.Builder()
.addAt(message.sender.userId)
.addImage(cardImage, true)
.build()
listener.sendGroupMessage(message.groupId, msg)
val msg = MessageChain.Builder().addImage(cardImage, true).build()
message.reply(msg)
}
}
@@ -0,0 +1,58 @@
/*
* 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.util.BaseCommand
import cn.rtast.rob.util.ob.MessageChain
import cn.rtast.rob.util.ob.OneBotListener
@CommandDescription("查询Github上的仓库的最新一次的提交信息")
class GithubLatestCommitCommand : BaseCommand() {
override val commandNames = listOf(".commit")
private val userInfoUrl = "https://api.github.com/repos"
override suspend fun executeGroup(listener: OneBotListener, 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("查询失败~")
}
}
}
@@ -7,14 +7,15 @@
package cn.rtast.fancybot.commands.lookup
import cn.rtast.fancybot.annotations.CommandDescription
import cn.rtast.fancybot.configManager
import cn.rtast.fancybot.entity.github.UserInfo
import cn.rtast.fancybot.util.Http
import cn.rtast.fancybot.util.drawCustomImage
import cn.rtast.fancybot.util.drawString
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.str.encodeToBase64
import cn.rtast.fancybot.util.toBufferedImage
import cn.rtast.fancybot.util.toByteArray
import cn.rtast.rob.entity.GroupMessage
import cn.rtast.rob.util.BaseCommand
import cn.rtast.rob.util.ob.MessageChain
@@ -24,8 +25,9 @@ import java.awt.Font
import java.awt.image.BufferedImage
import java.net.URI
@CommandDescription("获取Github用户信息")
class GithubUserCommand : BaseCommand() {
override val commandNames = listOf("/gh", "/github", "gh")
override val commandNames = listOf("/gh")
private val userInfoUrl = "https://api.github.com/users"
private val backgroundColor = Color(255, 255, 240)
@@ -67,11 +69,8 @@ class GithubUserCommand : BaseCommand() {
override suspend fun executeGroup(listener: OneBotListener, message: GroupMessage, args: List<String>) {
if (args.isEmpty()) {
val msg = MessageChain.Builder()
.addAt(message.sender.userId)
.addText("发送`gh <用户名>`即可查询用户的信息~")
.build()
listener.sendGroupMessage(message.groupId, msg)
val msg = MessageChain.Builder().addText("发送`gh <用户名>`即可查询用户的信息~").build()
message.reply(msg)
return
}
val username = args.first()
@@ -80,10 +79,7 @@ class GithubUserCommand : BaseCommand() {
headers = mapOf("Authorization" to configManager.githubKey)
)
val image = this.createUserInfoCard(userInfo)
val msg = MessageChain.Builder()
.addReply(message.messageId)
.addImage(image, true)
.build()
listener.sendGroupMessage(message.groupId, msg)
val msg = MessageChain.Builder().addImage(image, true).build()
message.reply(msg)
}
}
@@ -0,0 +1,34 @@
/*
* 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.util.BaseCommand
import cn.rtast.rob.util.ob.MessageChain
import cn.rtast.rob.util.ob.OneBotListener
@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(listener: OneBotListener, 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,51 @@
/*
* 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.util.BaseCommand
import cn.rtast.rob.util.ob.MessageChain
import cn.rtast.rob.util.ob.OneBotListener
@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(listener: OneBotListener, 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,66 +0,0 @@
/*
* Copyright © 2024 RTAkland
* Author: RTAkland
* Date: 2024/9/3
*/
package cn.rtast.fancybot.commands.lookup
import cn.rtast.fancybot.entity.MCPing
import cn.rtast.fancybot.util.str.fromJson
import cn.rtast.motdpinger.MotdPinger
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
class MCPingCommand : BaseCommand() {
override val commandNames = listOf("/mcping", "/mcPing")
private val pingInstance = MotdPinger()
override suspend fun executeGroup(listener: OneBotListener, message: GroupMessage, args: List<String>) {
if (args.isEmpty()) {
val msg = MessageChain.Builder()
.addAt(message.sender.userId)
.addNewLine()
.addText("使用方法: /mcping <host>:[port]")
.addNewLine()
.addText("仅限Java版服务器哦~")
.build()
listener.sendGroupMessage(message.groupId, msg)
return
}
val parts = args.first().split(":")
val host: String
val port: Int
if (parts.size == 2) {
host = parts[0]
port = parts[1].toIntOrNull() ?: 25565
} else {
host = args.first()
port = 25565
}
try {
val rawResponse = pingInstance.pingServer(host, port)
val jsonResponse = rawResponse?.fromJson<MCPing>()!!
val msg = MessageChain.Builder().addAt(message.sender.userId).addNewLine()
if (jsonResponse.favicon != null) msg.addImage(jsonResponse.favicon, true)
msg.addText("服务器地址: $host:$port")
msg.addNewLine()
msg.addText("协议版本号: ${jsonResponse.version.protocol}/${jsonResponse.version.name}")
msg.addNewLine()
msg.addText("玩家: ${jsonResponse.players.online}/${jsonResponse.players.max}")
listener.sendGroupMessage(message.groupId, msg.build())
} catch (_: Exception) {
val msg = MessageChain.Builder()
.addAt(message.sender.userId)
.addText("ping失败~")
.addNewLine()
.addText("检查一下地址或者端口是否正确吧~")
.build()
listener.sendGroupMessage(message.groupId, msg)
}
}
}
@@ -7,10 +7,12 @@
package cn.rtast.fancybot.commands.lookup
import cn.rtast.fancybot.annotations.CommandDescription
import cn.rtast.fancybot.configManager
import cn.rtast.fancybot.entity.music.Search
import cn.rtast.fancybot.entity.music.SearchedResult
import cn.rtast.fancybot.entity.music.SongUrl
import cn.rtast.fancybot.entity.music.qq.QQMSearch
import cn.rtast.fancybot.util.Http
import cn.rtast.rob.entity.GroupMessage
import cn.rtast.rob.enums.MusicShareType
@@ -19,15 +21,18 @@ import cn.rtast.rob.util.ob.MessageChain
import cn.rtast.rob.util.ob.OneBotListener
import java.time.Instant
@CommandDescription("网易云音乐点歌")
class MusicCommand : BaseCommand() {
override val commandNames = listOf("/music", "/点歌", "点歌")
override val commandNames = listOf("点歌", "music")
private val searchedResult = mutableMapOf<Long, List<SearchedResult>>()
companion object {
private val MUSIC_API_URL = configManager.ncmAPI
fun searchSong(keyword: String): Search {
return Http.get<Search>(
"${configManager.ncmAPI}/search",
"$MUSIC_API_URL/search",
mapOf("keywords" to keyword),
)
}
@@ -35,11 +40,7 @@ class MusicCommand : BaseCommand() {
private suspend fun legacyMusicCommand(listener: OneBotListener, message: GroupMessage, args: List<String>) {
if (args.isEmpty()) {
val msg = MessageChain.Builder()
.addAt(message.sender.userId)
.addText("发送`/点歌`或者`/music` <关键词> 来搜索音乐~")
.build()
listener.sendGroupMessage(message.groupId, msg)
message.reply("发送`/点歌` <关键词> 来搜索音乐~")
return
}
searchedResult.forEach { (key, results) ->
@@ -62,13 +63,9 @@ class MusicCommand : BaseCommand() {
val msg = MessageChain.Builder()
.addMusicShare(MusicShareType.Netease, finalResult.id.toString())
.build()
listener.sendGroupMessage(message.groupId, msg)
message.reply(msg)
} catch (_: Exception) {
val msg = MessageChain.Builder()
.addAt(message.sender.userId)
.addText("输入有误请重新搜索本次搜索结果已清除")
.build()
listener.sendGroupMessage(message.groupId, msg)
message.reply("输入有误请重新搜索本次搜索结果已清除")
}
searchedResult.remove(message.sender.userId)
}
@@ -76,11 +73,7 @@ class MusicCommand : BaseCommand() {
else -> {
if (searchedResult.containsKey(message.sender.userId)) {
searchedResult.remove(message.sender.userId)
val msg = MessageChain.Builder()
.addAt(message.sender.userId)
.addText("你上次的搜索结果还没用呢, 已经帮你把上次的搜索结果清除掉啦~")
.build()
listener.sendGroupMessage(message.groupId, msg)
message.reply("你上次的搜索结果还没用呢, 已经帮你把上次的搜索结果清除掉啦~")
}
val keyword = args.joinToString(" ")
val result = searchSong(keyword)
@@ -95,11 +88,7 @@ class MusicCommand : BaseCommand() {
stringResult.append("| $index | 《${searchedResult.name}》-- ${searchedResult.artists}\n")
}
stringResult.append("输入/music p <序号> 播放\n你有30秒的时间进行操作")
val msg = MessageChain.Builder()
.addAt(message.sender.userId)
.addText(stringResult.toString())
.build()
listener.sendGroupMessage(message.groupId, msg)
message.reply(stringResult.toString())
}
}
}
@@ -111,29 +100,32 @@ class MusicCommand : BaseCommand() {
}
if (args.isEmpty()) {
val msg = MessageChain.Builder()
.addAt(message.sender.userId)
.addReply(message.messageId)
.addText("发送`点歌 <歌名>`即可点第一首搜索到的歌曲~")
.addNewLine()
.addText("发送`点歌 <歌名> [l|legacy]`即可使用旧版点歌系统")
.build()
listener.sendGroupMessage(message.groupId, msg)
message.reply(msg)
return
}
val keyword = args.joinToString(" ")
val result = searchSong(keyword).result.songs.asSequence().first().id
val result = searchSong(keyword).result.songs.first().id
val msg = MessageChain.Builder()
.addMusicShare(MusicShareType.Netease, result.toString())
.build()
listener.sendGroupMessage(message.groupId, msg)
message.reply(msg)
}
}
@CommandDescription("获取网易云音乐链接")
class MusicPlayUrlCommand : BaseCommand() {
override val commandNames = listOf("/lj", "lj", "song", "/song")
override val commandNames = listOf("lj")
private val musicApiUrl = configManager.ncmAPI
private fun getPlayUrl(id: String): String {
return Http.get<SongUrl>("${configManager.ncmAPI}/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>) {
@@ -149,6 +141,27 @@ class MusicPlayUrlCommand : BaseCommand() {
.addReply(message.messageId)
.addText(url)
.build()
listener.sendGroupMessage(message.groupId, msg)
message.reply(msg)
}
}
@CommandDescription("QQ音乐点歌")
class QQMusicCommand : BaseCommand() {
override val commandNames = listOf("qm", "/qm")
private val qqMusicApiUrl = configManager.qqMusicApiUrl
override suspend fun executeGroup(listener: OneBotListener, message: GroupMessage, args: List<String>) {
if (args.isEmpty()) {
message.reply("发送`/qm <关键词>`即可搜索qq音乐的音乐哦")
return
}
val keyword = args.joinToString(" ")
val response = Http.get<QQMSearch>(
"$qqMusicApiUrl/getSearchByKey",
mapOf("key" to keyword)
).response.data.song.list.first().songId
val msg = MessageChain.Builder().addMusicShare(MusicShareType.QQ, response.toString()).build()
message.reply(msg)
}
}
@@ -7,21 +7,97 @@
package cn.rtast.fancybot.commands.lookup
import cn.rtast.fancybot.annotations.CommandDescription
import cn.rtast.fancybot.configManager
import cn.rtast.fancybot.entity.IPGeo
import cn.rtast.fancybot.util.Http
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 cn.rtast.rob.util.ob.asNode
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.net.InetAddress
import java.net.UnknownHostException
import java.util.Hashtable
import javax.naming.directory.InitialDirContext
@CommandDescription("解析域名获取IP地址")
class NslookupCommand : BaseCommand() {
override val commandNames = listOf("/nslookup", "/ns")
override val commandNames = listOf("/ns")
companion object {
private const val MC_SRV_PREFIX = "_minecraft._tcp"
private const val IP_INFO_API = "https://ipinfo.io/###/json"
private val env = Hashtable<String, String>().also {
it["java.naming.factory.initial"] = "com.sun.jndi.dns.DnsContextFactory"
}
private val dirContext = InitialDirContext(env)
fun srv(host: String): MutableList<String> {
val srvDomain = "$MC_SRV_PREFIX.$host"
val attrs = dirContext.getAttributes(srvDomain, arrayOf("SRV"))
val srvRecords = mutableListOf<String>()
val srvAttr = attrs.get("SRV")
if (srvAttr != null) {
for (i in 0 until srvAttr.size()) {
val srvRecord = srvAttr.get(i) as String
srvRecords.add(srvRecord)
}
}
return srvRecords
}
}
private fun geo(ip: String) = Http.get<IPGeo>(IP_INFO_API.replace("###", ip))
override suspend fun executeGroup(listener: OneBotListener, message: GroupMessage, args: List<String>) {
val host = args.first()
val nsType = if (args.size == 3) args[1] else "common"
val srvType = if (args.size == 3) args.last() else "mc"
try {
if (nsType == "srv") {
when (srvType) {
"mc" -> {
val srvRecords = srv(host)
val messages = mutableListOf<MessageChain>()
srvRecords.forEach {
val srv = it.split(" ")
val priority = srv.first().toInt()
val weight = srv[1].toInt()
val port = srv[2].toInt()
val host = srv.last()
val ip = InetAddress.getByName(host).hostAddress
val geo = this.geo(ip)
val msg = MessageChain.Builder()
.addReply(message.messageId)
.addText("域名: ${args.first()}解析后的IP地址为: ")
.addText(InetAddress.getByName(args.first()).hostAddress)
.addText("解析后的地址: $host:$port")
.addNewLine()
.addText("权重: $weight | 优先级: $priority")
.addNewLine()
.addText("$host -> $ip")
.addNewLine()
.addText("国家: ${geo.country} 地区: ${geo.region} 城市: ${geo.city} | 所属组织: ${geo.org}")
.build()
listener.sendGroupMessage(message.groupId, msg)
messages.add(msg)
}
message.reply(messages.asNode(configManager.selfId))
}
}
} else {
val ip = withContext(Dispatchers.IO) { InetAddress.getByName(host) }.hostAddress
val geo = this.geo(ip)
val msg = MessageChain.Builder()
.addText("$host -> $ip")
.addNewLine()
.addText("国家: ${geo.country} 地区: ${geo.region} 城市: ${geo.city} | 所属组织: ${geo.org}")
.build()
message.reply(msg)
}
} catch (_: UnknownHostException) {
message.reply("未知的主机名: $host")
} catch (e: Exception) {
message.reply("未知错误: ${e.message}")
}
}
}
@@ -7,17 +7,21 @@
package cn.rtast.fancybot.commands.lookup
import cn.rtast.fancybot.annotations.CommandDescription
import cn.rtast.fancybot.configManager
import cn.rtast.fancybot.entity.pixiv.Ranking
import cn.rtast.fancybot.util.Http
import cn.rtast.fancybot.util.str.encodeToBase64
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.NodeMessageChain
import cn.rtast.rob.util.ob.OneBotListener
import java.net.URI
@CommandDescription("获取Pixiv上的图片")
class PixivCommand : BaseCommand() {
override val commandNames = listOf("/pixiv", "/p")
override val commandNames = listOf("/p")
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"
@@ -25,30 +29,39 @@ class PixivCommand : BaseCommand() {
override suspend fun executeGroup(listener: OneBotListener, message: GroupMessage, args: List<String>) {
if (args.isEmpty()) {
val msg = MessageChain.Builder()
.addAt(message.sender.userId)
.addText("发送/pixiv <id> | rank 来进行操作哦~")
.addText("发送`/pixiv <id> | rank` 来进行操作哦~")
.build()
listener.sendGroupMessage(message.groupId, msg)
message.reply(msg)
return
}
when (val keyword = args.first()) {
"rank", "排名", "r" -> {
val msg = MessageChain.Builder()
.addAt(message.sender.userId)
.addText("今日Pixiv Ranking~")
.addNewLine()
val node = NodeMessageChain.Builder()
.addMessageChain(
MessageChain.Builder().addText("今日Pixiv Ranking~").build(),
message.sender.userId
)
val result = Http.get<Ranking>(pixivRankingURL)
result.contents.asSequence().take(5).forEach {
try {
val msg = MessageChain.Builder()
val imageBase64 = URI(it.getOriginUrl()).toURL().readBytes().encodeToBase64()
msg.addText(it.title)
.addImage(imageBase64, true)
msg.addImage(imageBase64, true)
.addText("标题: ${it.title}")
.addNewLine()
.addText("用户: ${it.userName}(${it.userId}) | 作品ID: ${it.illustId}")
.addNewLine()
.addText("日期: ${it.date}")
node.addMessageChain(msg.build(), configManager.selfId)
} catch (_: Exception) {
}
}
listener.sendGroupMessage(message.groupId, msg.build())
node.addMessageChain(
MessageChain.Builder()
.addText("图片来源: Pixiv")
.build(), configManager.selfId
)
message.reply(node.build())
return
}
@@ -56,19 +69,14 @@ class PixivCommand : BaseCommand() {
try {
val id = keyword.toLong()
val imageBase64 = URI("$imageProxyURL/$id.png").toURL().readBytes().encodeToBase64()
println(imageBase64)
val msg = MessageChain.Builder()
.addAt(message.sender.userId)
.addText("来咯~ 你要的图片~~~")
.addImage(imageBase64, true)
.addText("图片来源: Pixiv")
.build()
listener.sendGroupMessage(message.groupId, msg)
message.reply(msg)
} catch (_: Exception) {
val msg = MessageChain.Builder()
.addAt(message.sender.userId)
.addText("输入错误~检查一下有没有输错吧~")
.build()
listener.sendGroupMessage(message.groupId, msg)
val msg = MessageChain.Builder().addText("输入错误~检查一下有没有输错吧~").build()
message.reply(msg)
}
}
}
@@ -1,26 +0,0 @@
/*
* Copyright © 2024 RTAkland
* Author: RTAkland
* Date: 2024/8/30
*/
package cn.rtast.fancybot.commands.lookup
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
class QRCodeCommand : BaseCommand() {
override val commandNames = listOf("/qrcode", "/qr", "/二维码")
override suspend fun executeGroup(listener: OneBotListener, message: GroupMessage, args: List<String>) {
val content = args.joinToString(" ")
val msg = MessageChain.Builder()
.addAt(message.sender.userId)
.addImage("https://api.rtast.cn/api/generate_qr?data=$content")
.build()
listener.sendGroupMessage(message.groupId, msg)
}
}
@@ -7,52 +7,48 @@
package cn.rtast.fancybot.commands.lookup
import cn.rtast.fancybot.annotations.CommandDescription
import cn.rtast.fancybot.instance
import cn.rtast.rob.entity.GroupMessage
import cn.rtast.rob.enums.ArrayMessageType
import cn.rtast.rob.segment.Node
import cn.rtast.rob.segment.PlainText
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(targetName: String, targetId: String, times: Int = 10): NodeMessageChain {
private fun generateNodeMessage(targetId: String, times: Int = 10): NodeMessageChain {
val nodeMsg = NodeMessageChain.Builder()
repeat(times) {
val node = Node(
Node.Data(
targetName,
targetId,
listOf(PlainText(PlainText.Data("我是傻逼")))
)
)
nodeMsg.addNode(node)
val msg = MessageChain.Builder().addText("我是傻逼").build()
nodeMsg.addMessageChain(msg, targetId.toLong())
}
return nodeMsg.build()
}
@CommandDescription("骂自己(好爽)")
class ShotSelfCommand : BaseCommand() {
override val commandNames = listOf("骂我")
override suspend fun executeGroup(listener: OneBotListener, message: GroupMessage, args: List<String>) {
listener.sendGroupForwardMsg(
message.groupId,
generateNodeMessage(message.sender.nickname, message.sender.userId.toString())
)
instance.action.sendGroupForwardMsg(message.groupId, generateNodeMessage(message.sender.userId.toString()))
}
}
@CommandDescription("骂别人(更爽了")
class ShotOtherCommand : BaseCommand() {
override val commandNames = listOf("")
override suspend fun executeGroup(listener: OneBotListener, message: GroupMessage, args: List<String>) {
if (args.isEmpty()) {
message.reply("发送`骂 @某人`可以骂他~")
instance.action.sendGroupForwardMsg(
message.groupId, generateNodeMessage(message.sender.userId.toString(), 20)
)
return
}
var times = if (args.size == 1) 10 else if (args.last() == "") 10 else args.last().toInt()
if (times >= 200) times = 10
val target = message.message.find { it.type == ArrayMessageType.at }?.data!!
listener.sendGroupForwardMsg(message.groupId, generateNodeMessage(target.name!!, target.qq!!, times))
instance.action.sendGroupForwardMsg(message.groupId, generateNodeMessage(target.qq!!, times))
}
}
@@ -0,0 +1,35 @@
/*
* 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.util.BaseCommand
import cn.rtast.rob.util.ob.MessageChain
import cn.rtast.rob.util.ob.OneBotListener
@CommandDescription("随机猫猫图")
class TheCatCommand : BaseCommand() {
override val commandNames = listOf("随机猫咪", "随机猫猫")
private val theCatApiUrl = "https://api.thecatapi.com/v1/images/search"
override suspend fun executeGroup(listener: OneBotListener, 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,52 @@
/*
* 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.util.BaseCommand
import cn.rtast.rob.util.ob.MessageChain
import cn.rtast.rob.util.ob.OneBotListener
import cn.rtast.rob.util.ob.asNode
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(listener: OneBotListener, 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.asNode(configManager.selfId))
}
}
@@ -7,16 +7,17 @@
package cn.rtast.fancybot.commands.lookup
import cn.rtast.fancybot.annotations.CommandDescription
import cn.rtast.fancybot.configManager
import cn.rtast.fancybot.entity.weather.Geo
import cn.rtast.fancybot.entity.weather.Weather
import cn.rtast.fancybot.util.Http
import cn.rtast.fancybot.util.Resources
import cn.rtast.fancybot.util.drawCenteredText
import cn.rtast.fancybot.util.drawCustomImage
import cn.rtast.fancybot.util.misc.Resources
import cn.rtast.fancybot.util.misc.drawCenteredText
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.toBufferedImage
import cn.rtast.fancybot.util.toByteArray
import cn.rtast.rob.entity.GroupMessage
import cn.rtast.rob.util.BaseCommand
import cn.rtast.rob.util.ob.MessageChain
@@ -25,8 +26,9 @@ import java.awt.Color
import java.awt.Font
import java.awt.image.BufferedImage
@CommandDescription("查询天气")
class WeatherCommand : BaseCommand() {
override val commandNames = listOf("/weather", "/天气", "天气")
override val commandNames = listOf("天气")
private val canvasWidth = 800
private val canvasHeight = 600
@@ -62,7 +64,7 @@ class WeatherCommand : BaseCommand() {
.addAt(message.sender.userId)
.addText("发送/weather <地区名>即可查看实时天气哦~")
.build()
listener.sendGroupMessage(message.groupId, msg)
message.reply(msg)
return
}
try {
@@ -79,13 +81,13 @@ class WeatherCommand : BaseCommand() {
.addReply(message.messageId)
.addImage(this.createWeatherCard(weather, lookupLocation), true)
.build()
listener.sendGroupMessage(message.groupId, msg)
message.reply(msg)
} catch (_: NullPointerException) {
val msg = MessageChain.Builder()
.addAt(message.sender.userId)
.addText("没有查询到该城市的天气信息哦~")
.build()
listener.sendGroupMessage(message.groupId, msg)
message.reply(msg)
}
}
}
@@ -7,24 +7,30 @@
package cn.rtast.fancybot.commands.lookup
import cn.rtast.fancybot.annotations.CommandDescription
import cn.rtast.fancybot.entity.wiki.PageInfoResponse
import cn.rtast.fancybot.entity.wiki.WikipediaResponse
import cn.rtast.fancybot.enums.CommandAction
import cn.rtast.fancybot.util.Http
import cn.rtast.fancybot.util.file.insertActionRecord
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 org.jsoup.Jsoup
@CommandDescription("查询wiki百科")
class WikipediaCommand : BaseCommand() {
override val commandNames = listOf("/wiki", "/wk")
private val wikipediaAPI = "https://proxy.rtast.cn/https/zh.wikipedia.org/w/api.php"
private fun String.extractPlainTextFromHtml(): String {
companion object {
fun String.extractPlainTextFromHtml(): String {
val doc = Jsoup.parse(this)
return doc.text()
}
}
override suspend fun executeGroup(listener: OneBotListener, message: GroupMessage, args: List<String>) {
if (args.isEmpty()) {
@@ -67,6 +73,8 @@ class WikipediaCommand : BaseCommand() {
.addText("没有查询到指定的wiki页面呢 >>> $title")
.build()
message.reply(msg)
} finally {
insertActionRecord(CommandAction.Wiki, message.sender.userId, title)
}
}
}
@@ -7,24 +7,32 @@
package cn.rtast.fancybot.commands.misc
import cn.rtast.fancybot.configManager
import cn.rtast.fancybot.annotations.CommandDescription
import cn.rtast.fancybot.enums.CommandAction
import cn.rtast.fancybot.instance
import cn.rtast.fancybot.util.file.insertActionRecord
import cn.rtast.rob.entity.ArrayMessage
import cn.rtast.rob.entity.GroupMessage
import cn.rtast.rob.enums.ArrayMessageType
import cn.rtast.rob.util.BaseCommand
import cn.rtast.rob.util.ob.OneBotListener
@CommandDescription("防撤回(获取消息)")
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>) {
if (message.sender.userId !in configManager.admins) message.reply("你不许用防撤回")
if (message.sender.isAdmin || message.sender.isOwner) {
val messageId = args.first().toLong()
val getMsg = listener.getMessage(messageId)
val getMsg = instance.action.getMessage(messageId)
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.addAll(getMsg.message)
listener.sendGroupMessage(message.groupId, msgList)
instance.action.sendGroupMessage(message.groupId, msgList)
insertActionRecord(CommandAction.AntiRevoke, message.sender.userId, getMsg.messageId.toString())
} else {
message.reply("你不许用防撤回")
}
}
}
@@ -0,0 +1,72 @@
/*
* Copyright © 2024 RTAkland
* Author: RTAkland
* Date: 2024/9/26
*/
package cn.rtast.fancybot.commands.misc
import cn.rtast.fancybot.annotations.CommandDescription
import cn.rtast.fancybot.configManager
import cn.rtast.fancybot.entity.bullshit.BullShit
import cn.rtast.fancybot.util.misc.Resources
import cn.rtast.fancybot.util.str.fromJson
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.NodeMessageChain
import cn.rtast.rob.util.ob.OneBotListener
import kotlin.random.Random
@CommandDescription("狗屁不通文章生成器")
class BullShitGenerateCommand : BaseCommand() {
override val commandNames = listOf("/gpbt", "狗屁不通")
private val bullShitData = String(Resources.loadFromResourcesAsBytes("misc/bull_shit.json")!!)
.fromJson<BullShit>()
private fun <T> shuffleList(list: List<T>): Iterator<T> {
val pool = mutableListOf<T>().apply { addAll(list) }.toMutableList()
repeat(1) { pool.addAll(list) }
return generateSequence {
pool.shuffle()
pool
}.flatten().iterator()
}
private fun getFamousQuote(nextQuote: Iterator<String>, beforeList: List<String>, afterList: List<String>): String {
var quote = nextQuote.next()
quote = quote.replace("a", beforeList.random())
quote = quote.replace("b", afterList.random())
return quote
}
private fun newParagraph(): String {
return ".\n\n "
}
override suspend fun executeGroup(listener: OneBotListener, message: GroupMessage, args: List<String>) {
val topic = args.joinToString(" ")
val nextFamousQuote = shuffleList(bullShitData.famous)
val nextBosh = shuffleList(bullShitData.bosh)
val output = StringBuilder()
while (output.length < 6000) {
val branch = Random.nextInt(100)
if (branch < 5) {
output.append(newParagraph())
} else if (branch < 20) {
output.append(getFamousQuote(nextFamousQuote, bullShitData.before, bullShitData.after))
} else {
output.append(nextBosh.next())
}
}
val finalText = output.toString().replace("x", topic)
val node = NodeMessageChain.Builder()
.addMessageChain(MessageChain.Builder().addText(finalText).build(), configManager.selfId)
.build()
message.reply(node)
message.sender.poke()
}
}
@@ -7,42 +7,48 @@
package cn.rtast.fancybot.commands.misc
import cn.rtast.fancybot.annotations.CommandDescription
import cn.rtast.fancybot.entity.compiler.GLOTPayload
import cn.rtast.fancybot.entity.compiler.GLOTResponse
import cn.rtast.fancybot.entity.compiler.KCSPayload
import cn.rtast.fancybot.entity.compiler.KCSResponse
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
import cn.rtast.rob.util.ob.MessageChain
import cn.rtast.rob.util.ob.OneBotListener
@CommandDescription("执行不同语言的代码")
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 glotCompilerServer = "https://glot.io/run"
override suspend fun executeGroup(listener: OneBotListener, message: GroupMessage, args: List<String>) {
if (args.isEmpty()) {
val msg = MessageChain.Builder()
.addAt(message.sender.userId)
.addText("使用`/exec` <语言> <代码> 即可执行~")
.build()
listener.sendGroupMessage(message.groupId, msg)
message.reply("使用`/exec` <语言> <代码> 即可执行~")
return
}
val language = args.first()
var language = args.first().lowercase()
val code = args.drop(1).joinToString(" ")
val response = when (language) {
"kotlin", "Kotlin", "kt", "Kt", "KT" -> {
"kotlin", "kt" -> {
Http.post<KCSResponse>(
kotlinCompilerServer, jsonBody = KCSPayload(listOf(KCSPayload.File(code))).toJson()
kotlinCompilerServer,
jsonBody = KCSPayload(listOf(KCSPayload.File(code))).toJson()
).text
}
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>(
"$glotCompilerServer/$language",
params = mapOf("version" to "latest"),
@@ -50,12 +56,18 @@ class CompilerCommand : BaseCommand() {
).stdout
}
}
try {
val msg = MessageChain.Builder()
.addAt(message.sender.userId)
.addReply(message.messageId)
.addText("执行结果如下:")
.addNewLine()
.addText(response.replace("<outStream>", "").replace("</outStream>", ""))
.build()
listener.sendGroupMessage(message.groupId, msg)
message.reply(msg)
} catch (e: Exception) {
message.reply("执行错误: ${e.message}")
} finally {
insertActionRecord(CommandAction.Compiler, message.sender.userId)
}
}
}
@@ -1,28 +0,0 @@
/*
* Copyright © 2024 RTAkland
* Author: RTAkland
* Date: 2024/8/30
*/
package cn.rtast.fancybot.commands.misc
import cn.rtast.fancybot.entity.FKXQS
import cn.rtast.fancybot.util.Http
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
class FKXQSCommand : BaseCommand() {
override val commandNames = listOf("/fkxqs", "/fk", "/疯狂星期四")
override suspend fun executeGroup(listener: OneBotListener, message: GroupMessage, args: List<String>) {
val response = Http.get<FKXQS>("https://api.shadiao.pro/kfc").data.text
val msg = MessageChain.Builder()
.addAt(message.sender.userId)
.addText(response)
.build()
listener.sendGroupMessage(message.groupId, msg)
}
}
@@ -0,0 +1,28 @@
/*
* 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 cn.rtast.rob.util.ob.OneBotListener
import java.time.LocalDate
import java.time.temporal.ChronoUnit
@CommandDescription("高考倒计时")
class GaoKaoDaysRemainCommand : BaseCommand() {
override val commandNames = listOf("高考倒计时")
override suspend fun executeGroup(listener: OneBotListener, 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,35 @@
/*
* 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.util.BaseCommand
import cn.rtast.rob.util.ob.MessageChain
import cn.rtast.rob.util.ob.OneBotListener
@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(listener: OneBotListener, 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,35 @@
/*
* 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.util.BaseCommand
import cn.rtast.rob.util.ob.MessageChain
import cn.rtast.rob.util.ob.OneBotListener
@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(listener: OneBotListener, 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)
}
}
@@ -7,6 +7,7 @@
package cn.rtast.fancybot.commands.misc
import cn.rtast.fancybot.annotations.CommandDescription
import cn.rtast.fancybot.entity.Hitokoto
import cn.rtast.fancybot.util.Http
import cn.rtast.rob.entity.GroupMessage
@@ -14,8 +15,9 @@ import cn.rtast.rob.util.BaseCommand
import cn.rtast.rob.util.ob.MessageChain
import cn.rtast.rob.util.ob.OneBotListener
@CommandDescription("一言")
class HitokotoCommand : BaseCommand() {
override val commandNames = listOf("/hitokoto", "/1", "一言", "1")
override val commandNames = listOf("/1")
override suspend fun executeGroup(listener: OneBotListener, message: GroupMessage, args: List<String>) {
val response = Http.get<Hitokoto>("https://v1.hitokoto.cn/?c=b")
@@ -5,14 +5,16 @@
*/
package cn.rtast.fancybot.commands.record
package cn.rtast.fancybot.commands.misc
import cn.rtast.fancybot.annotations.CommandDescription
import cn.rtast.fancybot.util.file.JrrpManager
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 JrrpCommand : BaseCommand() {
override val commandNames = listOf("/jrrp", "/今日人品")
@@ -24,7 +26,7 @@ class JrrpCommand : BaseCommand() {
.addAt(message.sender.userId)
.addText("你今天已经今日人品过啦, 明天再来吧~")
.build()
listener.sendGroupMessage(message.groupId, msg)
message.reply(msg)
return
}
val randomPoint = jrrpManager.jrrp(message.sender.userId)
@@ -39,6 +41,6 @@ class JrrpCommand : BaseCommand() {
.addAt(message.sender.userId)
.addText("$scoreDesc($randomPoint)")
.build()
listener.sendGroupMessage(message.groupId, msg)
message.reply(msg)
}
}
@@ -7,11 +7,12 @@
package cn.rtast.fancybot.commands.misc
import cn.rtast.fancybot.util.Resources
import cn.rtast.fancybot.util.drawCircularImage
import cn.rtast.fancybot.util.makeGif
import cn.rtast.fancybot.annotations.CommandDescription
import cn.rtast.fancybot.util.misc.Resources
import cn.rtast.fancybot.util.misc.drawCircularImage
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.toBufferedImage
import cn.rtast.rob.entity.GroupMessage
import cn.rtast.rob.enums.ArrayMessageType
import cn.rtast.rob.util.BaseCommand
@@ -21,6 +22,7 @@ import com.madgag.gif.fmsware.GifDecoder
import java.awt.image.BufferedImage
import java.net.URI
@CommandDescription("撅撅你")
class JueCommand : BaseCommand() {
override val commandNames = listOf("")
@@ -60,6 +62,6 @@ class JueCommand : BaseCommand() {
val source = message.sender.userId.toString()
val gif = this.createJueGIF(source, target)
val msg = MessageChain.Builder().addImage(gif, true).build()
listener.sendGroupMessage(message.groupId, msg)
message.reply(msg)
}
}
@@ -0,0 +1,42 @@
/*
* 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.entity.KFC
import cn.rtast.fancybot.util.Http
import cn.rtast.rob.entity.GroupMessage
import cn.rtast.rob.util.BaseCommand
import cn.rtast.rob.util.ob.OneBotListener
import java.time.DayOfWeek
import java.time.Instant
import java.time.LocalDateTime
import java.time.ZoneId
@CommandDescription("今天是疯狂星期四!")
class KFCCommand : BaseCommand() {
override val commandNames = listOf("/fkxqs", "/kfc")
companion object {
fun isThursday(): Boolean {
val dateTime = LocalDateTime.ofInstant(
Instant.now(),
ZoneId.of("Asia/Shanghai")
)
return dateTime.dayOfWeek == DayOfWeek.THURSDAY
}
}
override suspend fun executeGroup(listener: OneBotListener, message: GroupMessage, args: List<String>) {
if (!isThursday()) {
message.reply("很可惜今天并不是星期四~")
return
}
message.reply(Http.get<KFC>("https://api.shadiao.pro/kfc").data.text)
}
}
@@ -0,0 +1,667 @@
/*
* 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.util.BaseCommand
import cn.rtast.rob.util.ob.MessageChain
import cn.rtast.rob.util.ob.OneBotListener
import cn.rtast.rob.util.ob.asMessageChain
import cn.rtast.rob.util.ob.asNode
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(listener: OneBotListener, 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(listener: OneBotListener, 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 = listOf("结果如下:").asMessageChain()
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.asNode(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 = listOf("标题${response.title}的内容如下").asMessageChain()
val footerMsg = listOf(
"数据来源: https://zh.minecraft.wiki",
"协议: ${response.license.title}(${response.license.url})"
).asMessageChain(true)
val bodyMsg = MessageChain.Builder()
.addText(response.source.convertToHTML().extractPlainTextFromHtml())
.build()
messages.add(headerMsg)
messages.add(bodyMsg)
messages.add(footerMsg)
message.reply(messages.asNode(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(listener: OneBotListener, 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(listener: OneBotListener, 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(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(":")
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(listener: OneBotListener, 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(listener: OneBotListener, 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(listener: OneBotListener, message: GroupMessage, args: List<String>) {
message.reply("请在私聊中使用此命令")
}
override suspend fun executePrivate(listener: OneBotListener, 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(listOf("所有配置如下").asMessageChain()) }
allRcons.forEach {
val msg = MessageChain.Builder()
.addText("名称: ${it.name}")
.addNewLine()
.addText("地址: ${it.host}:${it.port}")
.addNewLine()
.addText("密码: *********")
.build()
messages.add(msg)
}
message.reply(messages.asNode(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(listOf("执行结果如下").asMessageChain()) }
.also { it.add(listOf(result).asMessageChain()) }
.asNode(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(listener: OneBotListener, 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,48 @@
/*
* 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
import cn.rtast.rob.util.ob.OneBotListener
@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 "$PBI_API_URL/-/${response.id}"
}
}
override suspend fun executeGroup(listener: OneBotListener, 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,96 @@
/*
* 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.util.BaseCommand
import cn.rtast.rob.util.ob.MessageChain
import cn.rtast.rob.util.ob.OneBotListener
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(listener: OneBotListener, 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(listener: OneBotListener, message: GroupMessage, args: List<String>) {
if (message.sender() !in waitingList) {
waitingList.add(message.sender())
message.reply("继续发送一张二维码图片")
} else {
message.reply("回复错误已经取消本次操作")
}
}
}
@@ -7,12 +7,14 @@
package cn.rtast.fancybot.commands.misc
import cn.rtast.fancybot.annotations.CommandDescription
import cn.rtast.rob.entity.GroupMessage
import cn.rtast.rob.enums.ArrayMessageType
import cn.rtast.rob.util.BaseCommand
import cn.rtast.rob.util.ob.MessageChain
import cn.rtast.rob.util.ob.OneBotListener
@CommandDescription("RUA!")
class RUACommand : BaseCommand() {
override val commandNames = listOf("/rua", "rua")
@@ -23,7 +25,7 @@ class RUACommand : BaseCommand() {
.addText("使用rua @xxxx")
.addText("即可rua某人啦~")
.build()
listener.sendGroupMessage(message.groupId, msg)
message.reply(msg)
return
}
try {
@@ -36,13 +38,13 @@ class RUACommand : BaseCommand() {
.addText(" ")
.addText("rua了你~")
.build()
listener.sendGroupMessage(message.groupId, msg)
message.reply(msg)
} catch (_: NullPointerException) {
val msg = MessageChain.Builder()
.addAt(message.sender.userId)
.addText("要@才有用哦~")
.build()
listener.sendGroupMessage(message.groupId, msg)
message.reply(msg)
}
}
}
@@ -0,0 +1,33 @@
/*
* 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.util.BaseCommand
import cn.rtast.rob.util.ob.MessageChain
import cn.rtast.rob.util.ob.OneBotListener
@CommandDescription("获取随机热榜音乐(网易云)")
class RandomMusicCommand : BaseCommand() {
override val commandNames = listOf("随机音乐", "随机歌曲")
private val ncmApiUrl = "${configManager.ncmAPI}/top/song"
override suspend fun executeGroup(listener: OneBotListener, 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.util.BaseCommand
import cn.rtast.rob.util.ob.OneBotListener
@CommandDescription("让你的消息得到回应(reaction)!")
class ReactionCommand : BaseCommand() {
override val commandNames = listOf("reaction", "回应")
companion object {
suspend fun reaction(message: GroupMessage, limit: Int = 30) {
(0..limit - 1).forEach { message.reaction(QQFace.entries.random()) }
}
}
override suspend fun executeGroup(listener: OneBotListener, message: GroupMessage, args: List<String>) =
reaction(message)
}
@@ -7,15 +7,17 @@
package cn.rtast.fancybot.commands.misc
import cn.rtast.fancybot.entity.enums.CountryList
import cn.rtast.fancybot.util.randomBooleanWithProbability
import cn.rtast.fancybot.annotations.CommandDescription
import cn.rtast.fancybot.enums.CountryList
import cn.rtast.fancybot.util.misc.randomBooleanWithProbability
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 RemakeCommand : BaseCommand() {
override val commandNames = listOf("/remake", "/重开", "remake", "重开")
override val commandNames = listOf("remake")
private val locations = listOf(
"厕所", "首都", "农村", "市区",
@@ -38,6 +40,6 @@ class RemakeCommand : BaseCommand() {
msg.addText("重开成功! 你转生在${country.chineseName}$location, 是一个$role")
} else
msg.addText("转生失败~")
listener.sendGroupMessage(message.groupId, msg.build())
message.reply(msg.build())
}
}
@@ -7,20 +7,23 @@
package cn.rtast.fancybot.commands.misc
import cn.rtast.fancybot.annotations.CommandDescription
import cn.rtast.fancybot.instance
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
class LikeMeCommand : BaseCommand() {
@CommandDescription("资料卡点赞")
class SendLikeCommand : BaseCommand() {
override val commandNames = listOf("赞我")
override suspend fun executeGroup(listener: OneBotListener, message: GroupMessage, args: List<String>) {
listener.sendLike(message.sender.userId, 10)
instance.action.sendLike(message.sender.userId, 10)
val msg = MessageChain.Builder()
.addReply(message.messageId)
.addText("👍了你十下~~😁")
.build()
listener.sendGroupMessage(message.groupId, msg)
message.reply(msg)
}
}
@@ -7,15 +7,18 @@
package cn.rtast.fancybot.commands.misc
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.GroupMessage
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.email.EmailBuilder
import org.simplejavamail.mailer.MailerBuilder
@CommandDescription("发送邮件!")
class SendMailCommand : BaseCommand() {
override val commandNames = listOf("/mail")
@@ -42,27 +45,16 @@ class SendMailCommand : BaseCommand() {
override suspend fun executeGroup(listener: OneBotListener, message: GroupMessage, args: List<String>) {
if (args.isEmpty()) {
val msg = MessageChain.Builder()
.addAt(message.sender.userId)
.addText("发送`/mail <收件人地址>`即可发送测试邮件~")
.build()
listener.sendGroupMessage(message.groupId, msg)
return
}
if (message.sender.userId !in configManager.admins) {
val msg = MessageChain.Builder()
.addReply(message.messageId)
.addText("你不许发邮件")
.build()
listener.sendGroupMessage(message.groupId, msg)
message.reply("发送`/mail <收件人地址>`即可发送测试邮件~")
return
}
if (message.sender.isAdmin || message.sender.isOwner) {
val target = args.first()
this.sendMail(target)
val msg = MessageChain.Builder()
.addAt(message.sender.userId)
.addText("正在发送邮件, 请查收~(可能会出现在垃圾桶中~)")
.build()
listener.sendGroupMessage(message.groupId, msg)
message.reply("正在发送邮件, 请查收~(可能会出现在垃圾桶中~)")
insertActionRecord(CommandAction.SendMail, message.sender.userId, target)
} else {
message.reply("你不许发邮件")
}
}
}
@@ -0,0 +1,46 @@
/*
* Copyright © 2024 RTAkland
* Author: RTAkland
* Date: 2024/9/22
*/
package cn.rtast.fancybot.commands.misc
import cn.rtast.fancybot.API_RTAST_URL
import cn.rtast.fancybot.annotations.CommandDescription
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.str.toJson
import cn.rtast.rob.entity.GroupMessage
import cn.rtast.rob.util.BaseCommand
import cn.rtast.rob.util.ob.OneBotListener
@CommandDescription("生成短链接")
class ShortLinkCommand : BaseCommand() {
override val commandNames = listOf("/s")
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(listener: OneBotListener, message: GroupMessage, args: List<String>) {
if (args.isEmpty()) {
message.reply("发送`/s <url>`来生成短链接`不要放入一些奇怪的链接哦~")
return
}
val target = args.first()
val shortLink = target.makeShortLink()
message.reply(shortLink)
}
}
@@ -0,0 +1,43 @@
/*
* 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.util.BaseCommand
import cn.rtast.rob.util.ob.MessageChain
import cn.rtast.rob.util.ob.OneBotListener
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(listener: OneBotListener, 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)
}
}
@@ -1,48 +1,33 @@
/*
* Copyright © 2024 RTAkland
* Author: RTAkland
* Date: 2024/9/23
* Date: 2024/9/29
*/
package cn.rtast.fancybot.commands.misc
import cn.rtast.fancybot.entity.Setu
import cn.rtast.fancybot.util.Http
import cn.rtast.fancybot.util.str.encodeToBase64
import cn.rtast.fancybot.util.str.fromArrayJson
import cn.rtast.fancybot.annotations.CommandDescription
import cn.rtast.rob.entity.GroupMessage
import cn.rtast.rob.segment.Node
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
@Deprecated("已迁移到兑换列表中")
@CommandDescription("获得十张色图!")
class TenSetuCommand : BaseCommand() {
override val commandNames = listOf("十张色图")
override suspend fun executeGroup(listener: OneBotListener, message: GroupMessage, args: List<String>) {
val nodeMsg = NodeMessageChain.Builder()
val response = Http.get("https://api.rtast.cn/api/setu?size=10").fromArrayJson<List<Setu>>()
response.forEach {
if (it.r18) {
try {
val imageBase64 = URI(it.urls.large).toURL().readBytes().encodeToBase64()
val tempMsg = MessageChain.Builder().addImage(imageBase64, true).build()
val tempNode = Node(Node.Data("", message.userId.toString(), tempMsg.finalArrayMsgList))
nodeMsg.addNode(tempNode)
} catch (_: Exception) {
}
}
}
val footerMsg = MessageChain.Builder()
.addText("图片数量可能不足10张~")
val msg = MessageChain.Builder()
.addText("指令已经迁移到兑换列表中")
.addNewLine()
.addText("因为过滤了R18的图片~")
.addText("使用`/rdm 十张色图`来兑换吧~")
.addNewLine()
.addText("(需要20cm的牛子长度来兑换)")
.addNewLine()
.addText("此指令已废弃")
.build()
val footerNode = Node(Node.Data("", message.userId.toString(), footerMsg.finalArrayMsgList))
nodeMsg.addNode(footerNode)
listener.sendGroupForwardMsg(message.groupId, nodeMsg.build())
message.reply(msg)
}
}
@@ -0,0 +1,69 @@
/*
* 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 cn.rtast.rob.util.ob.OneBotListener
import kotlin.random.Random
@CommandDescription("今天吃什么")
class TodayEatCommand : BaseCommand() {
override val commandNames = listOf("今天吃什么")
private val foodList = listOf(
"炒饭", "火锅", "汉堡", "披萨", "寿司", "拉面", "炸鸡", "米粉",
"牛排", "烤串", "沙拉", "饺子", "意大利面", "烤鸭", "豆腐脑",
"蛋糕", "烧烤", "咖喱饭", "麻辣烫", "烤鱼", "牛肉面", "凉皮",
"西餐", "日式便当", "韩式拌饭", "生煎包", "炸酱面", "煎饼果子",
"酸辣粉", "煲仔饭", "", ""
)
override suspend fun executeGroup(listener: OneBotListener, 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(listener: OneBotListener, message: GroupMessage, args: List<String>) {
message.reply("你吃个结巴, 是吧")
}
}
@CommandDescription("今天喝什么")
class TodayDrinkCommand : BaseCommand() {
override val commandNames = listOf("今天喝什么")
private val drinkList = listOf(
"咖啡", "绿茶", "奶茶", "橙汁", "苹果汁", "可乐", "啤酒", "矿泉水",
"苏打水", "红茶", "酸梅汤", "豆浆", "椰子水", "冰沙", "葡萄汁",
"柠檬水", "奶昔", "热巧克力", "果茶", "乌龙茶", "茉莉花茶", "白开水",
"气泡水", "奶盖茶", "冰可乐", "草莓汁", "芒果汁", "蓝莓汁", "热奶茶", "蜂蜜柚子茶"
)
override suspend fun executeGroup(listener: OneBotListener, message: GroupMessage, args: List<String>) {
message.reply("今天喝${drinkList.random()}吧~")
}
}
@CommandDescription("今天不吃什么")
class TodayDontDrinkCommand : BaseCommand() {
override val commandNames = listOf("今天不喝什么")
override suspend fun executeGroup(listener: OneBotListener, message: GroupMessage, args: List<String>) {
message.reply("你喝个结巴, 是吧")
}
}
@@ -0,0 +1,91 @@
/*
* 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
import cn.rtast.rob.util.ob.OneBotListener
@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(listener: OneBotListener, 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))
}
}
}
@@ -7,6 +7,8 @@
package cn.rtast.fancybot.commands.misc
import cn.rtast.fancybot.annotations.CommandDescription
import cn.rtast.fancybot.instance
import cn.rtast.rob.entity.GroupMessage
import cn.rtast.rob.entity.PrivateMessage
import cn.rtast.rob.util.BaseCommand
@@ -15,6 +17,7 @@ import cn.rtast.rob.util.ob.OneBotListener
val users = mutableMapOf<Long, Long>()
@CommandDescription("自闭去吧你")
class ZiBiCommand : BaseCommand() {
override val commandNames = listOf("/自闭", "自闭")
@@ -29,17 +32,19 @@ class ZiBiCommand : BaseCommand() {
.addText("私聊发送`我想开了`即可取消禁言~")
.build()
message.reply(msg)
users[message.sender.userId] = message.groupId
} catch (_: Exception) {
val msg = MessageChain.Builder()
.addText("你输入有误所以我决定让你自闭1天~")
.build()
message.reply(msg)
message.sender.ban(86400)
} finally {
users[message.sender.userId] = message.groupId
}
}
}
@CommandDescription("我好像突然想开了")
class UnsetZiBiCommand : BaseCommand() {
override val commandNames = listOf("我想开了")
@@ -51,7 +56,7 @@ class UnsetZiBiCommand : BaseCommand() {
message.reply(msg)
return
}
listener.setGroupBan(users.getValue(message.sender.userId), message.sender.userId, 0)
instance.action.setGroupBan(users.getValue(message.sender.userId), message.sender.userId, 0)
val msg = MessageChain.Builder()
.addText("祝你天天开心不要自闭~~")
.build()
@@ -0,0 +1,42 @@
/*
* Copyright © 2024 RTAkland
* Author: RTAkland
* Date: 2024/9/24
*/
package cn.rtast.fancybot.commands.niuzi
import cn.rtast.fancybot.annotations.CommandDescription
import cn.rtast.fancybot.niuziManager
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 MyNiuziCommand : BaseCommand() {
override val commandNames = listOf("我的牛子", "mp")
override suspend fun executeGroup(listener: OneBotListener, message: GroupMessage, args: List<String>) {
val niuzi = niuziManager.getUser(message.sender.userId)
if (niuzi == null) {
message.reply("你还没有牛子呢, 发送`牛子签到`来领取一根专属于你的牛子吧~")
return
}
val msg = MessageChain.Builder()
.addAt(message.sender.userId)
.addText("你的牛子长度为: ${niuzi.length}cm")
val niuziString = when (niuzi.length) {
in -999.0..0.0 -> "你的牛子已经凹进去了!!!!"
in 0.1..6.0 -> "小小的也很可爱呢~"
in 6.1..15.0 -> "好长的牛子!"
in 15.1..30.0 -> "你的牛子已经快突破天际了!!!"
else -> "牛子也太长啦把天捅穿啦!!!!!!"
}
msg.addText(niuziString)
message.reply(msg.build())
}
}
@@ -0,0 +1,164 @@
/*
* Copyright © 2024 RTAkland
* Author: RTAkland
* Date: 2024/9/24
*/
package cn.rtast.fancybot.commands.niuzi
import cn.rtast.fancybot.annotations.CommandDescription
import cn.rtast.fancybot.commands
import cn.rtast.fancybot.instance
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.util.BaseCommand
import cn.rtast.rob.util.ob.MessageChain
import cn.rtast.rob.util.ob.OneBotListener
private suspend fun noAccount(message: GroupMessage) {
message.reply("你还没有银行账户呢, 发送`创建账户`来创建一个账户吧~")
}
private const val INTEREST_RATE = 0.0054
@CommandDescription("牛子银行根命令")
class NiuziBankCommand : BaseCommand() {
override val commandNames = listOf("牛子银行")
override suspend fun executeGroup(listener: OneBotListener, message: GroupMessage, args: List<String>) {
val niuziCommands = commands.filter {
it::class.simpleName?.startsWith("Niuzi")!! && !it::class.simpleName?.lowercase()?.contains("redeem")!!
}.joinToString("\n") { it.commandNames.joinToString(",") { join -> "`$join`" } }
val msg = MessageChain.Builder()
.addText("发送")
.addNewLine()
.addText(niuziCommands)
.addNewLine()
.addText("即可做出对应的操作")
.addNewLine()
.addText("银行利率是0.54%")
.build()
message.reply(msg)
}
}
@CommandDescription("查询牛子银行的余额")
class BankBalanceCommand : BaseCommand() {
override val commandNames = listOf("余额查询")
override suspend fun executeGroup(listener: OneBotListener, message: GroupMessage, args: List<String>) {
val balanceData = niuziBankManager.getUser(message.sender.userId)
if (balanceData == null) {
noAccount(message)
return
}
message.reply("你的账户余额还有: ${balanceData.balance}cm")
}
}
@CommandDescription("创建一个空的牛子银行账户")
class CreateBankAccountCommand : BaseCommand() {
override val commandNames = listOf("创建账户")
override suspend fun executeGroup(listener: OneBotListener, message: GroupMessage, args: List<String>) {
if (niuziBankManager.getUser(message.sender.userId) != null) {
message.reply("你已经有账户了!")
return
}
val userInfo = instance.action.getGroupMemberInfo(message.groupId, message.sender.userId)
niuziBankManager.createBlankAccount(message.sender.userId, userInfo.card ?: userInfo.nickname)
message.reply("成功创建了一个账户!")
}
}
@CommandDescription("用于牛子银行之间转账")
class BankTransferCommand : BaseCommand() {
override val commandNames = listOf("银行转账")
override suspend fun executeGroup(listener: OneBotListener, message: GroupMessage, args: List<String>) {
val currentUserBalance = niuziBankManager.getUser(message.sender.userId)
if (currentUserBalance == null) {
noAccount(message)
return
}
val target = message.message.find { it.type == ArrayMessageType.at }?.data?.qq!!.toLong()
if (niuziBankManager.getUser(target) == null) {
message.reply("他还没有银行账户呢, 提醒他创建一个账户吧~(发送`创建账户`)")
return
}
val amount = args.last().toDouble()
if (amount > currentUserBalance.balance) {
message.reply("你的牛子长度不够转账!")
return
}
val username = listener.getUserName(message.groupId, message.sender.userId)
val result = niuziBankManager.transfer(message.sender.userId, target, amount, username)
message.reply("转账成功! 你在银行内剩余的牛子长度为: ${result?.balance}")
}
}
@CommandDescription("牛子银行提现")
class WithdrawCommand : BaseCommand() {
override val commandNames = listOf("取牛子")
override suspend fun executeGroup(listener: OneBotListener, message: GroupMessage, args: List<String>) {
val amount = args.last().toDouble()
val currentBalance = niuziBankManager.getUser(message.sender.userId)
val niuzi = niuziManager.getUser(message.sender.userId)
if (currentBalance == null) {
noAccount(message)
return
}
if (niuzi == null) {
message.reply("你还没有认领牛子呢, 发送`牛子签到`来领取一根吧")
return
}
if (amount > currentBalance.balance) {
message.reply("你的牛子长度不足无法取出 >>> $amount")
return
}
if (amount <= 0) {
message.reply("取出的长度必须大于0!")
return
}
val username = listener.getUserName(message.groupId, message.sender.userId)
val result = niuziBankManager.withdraw(message.sender.userId, amount, username)
niuziManager.updateLength(message.sender.userId, amount + amount * INTEREST_RATE)
message.reply("取牛子成功, 你现在还剩${result.balance}cm的牛子在银行内")
}
}
@CommandDescription("牛子银行存入")
class DepositCommand : BaseCommand() {
override val commandNames = listOf("存牛子")
override suspend fun executeGroup(listener: OneBotListener, message: GroupMessage, args: List<String>) {
val currentBalance = niuziBankManager.getUser(message.sender.userId)
val niuzi = niuziManager.getUser(message.sender.userId)
val amount = args.last().toDouble()
if (amount <= 0) {
message.reply("存款必须大于0!")
return
}
if (currentBalance == null) {
noAccount(message)
return
}
if (niuzi == null) {
message.reply("你还没有认领牛子呢, 发送`牛子签到`来领取一根吧")
return
}
if (amount > niuzi.length) {
message.reply("你身上的牛子不够用啦, 没办法存进银行! >>> ${niuzi.length}")
return
}
val username = listener.getUserName(message.groupId, message.sender.userId)
val self = niuziManager.updateLength(message.sender.userId, -amount)
val result = niuziBankManager.deposit(message.sender.userId, amount, username)
message.reply("存入成功你身上的牛子还有${self?.length}cm 银行账户内还有: ${result.balance}cm")
}
}
@@ -0,0 +1,75 @@
/*
* Copyright © 2024 RTAkland
* Author: RTAkland
* Date: 2024/9/24
*/
package cn.rtast.fancybot.commands.niuzi
import cn.rtast.fancybot.annotations.CommandDescription
import cn.rtast.fancybot.niuziManager
import cn.rtast.rob.entity.GroupMessage
import cn.rtast.rob.enums.ArrayMessageType
import cn.rtast.rob.util.BaseCommand
import cn.rtast.rob.util.ob.MessageChain
import cn.rtast.rob.util.ob.OneBotListener
@CommandDescription("来一起激情击剑吧!")
class NiuziJiJianCommand : BaseCommand() {
override val commandNames = listOf("击剑", "jj")
override suspend fun executeGroup(listener: OneBotListener, message: GroupMessage, args: List<String>) {
if (args.isEmpty()) {
message.reply("发送`击剑 @xxx`即可对某人进行击剑")
return
}
val target = message.message.find { it.type == ArrayMessageType.at }!!.data
val targetId = target.qq!!.toLong()
if (targetId == 0L) {
val msg = MessageChain.Builder()
.addReply(message.messageId)
.addText("你的只有一根牛子,他们牛子太多啦你战胜不了他们的~")
.addNewLine()
.addText("所以你不能和他们击剑~")
.build()
message.reply(msg)
return
}
if (message.sender.userId == targetId) {
message.reply("你不能和自己击剑啊")
return
}
if (!niuziManager.exists(message.sender.userId)) {
message.reply("你还没有牛子呢, 发送`牛子签到`领取你的牛子吧~")
return
}
if (!niuziManager.exists(targetId)) {
message.reply("对方还没有牛子呢, 提醒他领取一根牛子吧~")
return
}
if (niuziManager.getUser(message.sender.userId)?.length!! < 0.0) {
message.reply("你的牛子已经凹进去了没办法进行击剑~发送`牛子签到`来增加你的牛子长度吧~")
return
}
if (niuziManager.getUser(targetId)?.length!! < 0.0) {
message.reply("他已经没有牛子啦!(他的牛子已经凹进去了!!)你不能和他击剑")
return
}
val result = niuziManager.jijian(message.sender.userId, targetId)
if (result.first) {
message.reply("你以已绝对的长度在这场击剑中获胜了~, 你的牛子增加了${result.second}cm")
} else {
val msg = MessageChain.Builder()
.addAt(message.sender.userId)
.addText("很不幸你的牛子没能战胜${target.name}, 你的牛子缩短了${result.second}cm")
.addNewLine()
.addText("对方的牛子增加了${result.second}cm~")
.build()
message.reply(msg)
}
}
}
@@ -0,0 +1,67 @@
/*
* 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.util.BaseCommand
import cn.rtast.rob.util.ob.MessageChain
import cn.rtast.rob.util.ob.OneBotListener
@CommandDescription("在群内设置某个成员的牛子银行余额或者现金牛子长度")
class NiuziManagerCommand : BaseCommand() {
override val commandNames = listOf("管理牛子")
override suspend fun executeGroup(listener: OneBotListener, message: GroupMessage, args: List<String>) {
if (args.isEmpty()) {
message.reply("此命令可以管理成员的牛子或者银行账户")
return
}
if (message.sender.isAdmin || message.sender.isOwner) {
val target = message.message.find { it.type == ArrayMessageType.at }!!.data.qq!!.toLong()
val method = args[1]
val amount = args.last().toDouble()
when (method) {
"set", "s", "设置" -> {
val type = args[2]
when (type) {
"yh", "银行" -> {
val before = niuziBankManager.getUser(target)
niuziBankManager.deposit(target, amount, listener.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("你没权限呢!")
}
}
}
@@ -0,0 +1,34 @@
/*
* Copyright © 2024 RTAkland
* Author: RTAkland
* Date: 2024/9/24
*/
package cn.rtast.fancybot.commands.niuzi
import cn.rtast.fancybot.annotations.CommandDescription
import cn.rtast.fancybot.niuziManager
import cn.rtast.rob.entity.GroupMessage
import cn.rtast.rob.enums.ArrayMessageType
import cn.rtast.rob.util.BaseCommand
import cn.rtast.rob.util.ob.OneBotListener
@CommandDescription("查询别人的牛子长度")
class NiuziQueryCommand : BaseCommand() {
override val commandNames = listOf("牛子查询")
override suspend fun executeGroup(listener: OneBotListener, message: GroupMessage, args: List<String>) {
if (args.isEmpty()) {
message.reply("输入`牛子查询 @某人`即可查询他的牛子长度")
return
}
val target = message.message.find { it.type == ArrayMessageType.at }?.data?.qq!!.toLong()
val targetNiuzi = niuziManager.getUser(target)
if (targetNiuzi == null) {
message.reply("他还没有牛子")
return
}
message.reply("他的牛子长度为: ${targetNiuzi.length}cm")
}
}
@@ -0,0 +1,45 @@
/*
* Copyright © 2024 RTAkland
* Author: RTAkland
* Date: 2024/9/26
*/
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.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 NiuziRankCommand : BaseCommand() {
override val commandNames = listOf("牛子排行榜", "牛子排行")
override suspend fun executeGroup(listener: OneBotListener, message: GroupMessage, args: List<String>) {
val allNiuzi = niuziManager.getAllNiuzi().sortedBy { it.length }.reversed()
val msg = MessageChain.Builder().addText("牛子长度排行榜如下").addNewLine()
allNiuzi.forEach {
msg.addText("[${it.nickname}] | ${it.length}cm").addNewLine()
}
message.reply(msg.build())
}
}
@CommandDescription("查询所有人的银行账户余额排行榜")
class NiuziBankRankCommand : BaseCommand() {
override val commandNames = listOf("牛子富豪榜", "富豪榜")
override suspend fun executeGroup(listener: OneBotListener, message: GroupMessage, args: List<String>) {
val allNiuziBankAccounts = niuziBankManager.getAllAccount()
.sortedBy { it.balance }.reversed()
val msg = MessageChain.Builder().addText("牛子银行富豪榜排行如下").addNewLine()
allNiuziBankAccounts.forEach {
msg.addText("[${it.nickname}] | ${it.balance}cm").addNewLine()
}
message.reply(msg.build())
}
}
@@ -0,0 +1,77 @@
/*
* Copyright © 2024 RTAkland
* Author: RTAkland
* Date: 2024/9/3
*/
package cn.rtast.fancybot.commands.niuzi
import cn.rtast.fancybot.annotations.CommandDescription
import cn.rtast.fancybot.itemManager
import cn.rtast.fancybot.niuziManager
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 NiuziRedeemCommand : BaseCommand() {
override val commandNames = listOf("/rdm")
override suspend fun executeGroup(listener: OneBotListener, message: GroupMessage, args: List<String>) {
if (args.isEmpty()) {
val msg = MessageChain.Builder()
.addText("发送 /rdm <物品名称> 即可兑换哦!")
.addNewLine()
.addText("发送 /rdm list 即可查看所有可兑换的物品~")
.addNewLine()
.addText("发送 `我的牛子` 可以查看自己的牛子长度~")
.build()
message.reply(msg)
return
}
val status = niuziManager.getUser(message.sender.userId)
if (status == null) {
message.reply("你还没有签到过呢要签到一次才能兑换哦~")
return
}
val item = args.first()
if (item == "list" || item == "列表") {
val priceTable = itemManager.items.joinToString("\n")
{ "[${it.itemNames.joinToString("|")}]:${it.itemPrice}cm" }
message.reply(priceTable)
return
}
val selectedItem = itemManager.items.find { it.itemNames.any { any -> any == item } }
if (selectedItem == null) {
val msg = MessageChain.Builder()
.addText("没有找到你想兑换的东西呢")
.addNewLine()
.addText("你可以发送 /rdm list来查看所有可兑换的物品")
.build()
message.reply(msg)
return
}
val selectedItemPrice = selectedItem.itemPrice
if (selectedItemPrice > status.length) {
val msg = MessageChain.Builder()
.addText("你的牛子长度不够兑换这个物品呢~")
.addNewLine()
.addText("你有: ${status.length}cm, 兑换需要: $selectedItemPrice cm~\"")
.build()
message.reply(msg)
return
}
val afterStatus = niuziManager.redeemItem(message.sender.userId, selectedItemPrice)?.length!!
val action = selectedItem.redeemInGroup(listener, message, afterStatus)
val msg = MessageChain.Builder()
.addText("兑换成功! 你花费了${selectedItemPrice}cm来兑换奖品, 还剩${afterStatus}cm")
.addNewLine()
.addText("-------------------")
.addNewLine()
.addMessageChain(action)
.build()
message.reply(msg)
}
}
@@ -0,0 +1,37 @@
/*
* Copyright © 2024 RTAkland
* Author: RTAkland
* Date: 2024/9/24
*/
package cn.rtast.fancybot.commands.niuzi
import cn.rtast.fancybot.annotations.CommandDescription
import cn.rtast.fancybot.niuziManager
import cn.rtast.fancybot.util.misc.getUserName
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 NiuziSignCommand : BaseCommand() {
override val commandNames = listOf("牛子签到", "签到")
override suspend fun executeGroup(listener: OneBotListener, message: GroupMessage, args: List<String>) {
if (niuziManager.isSigned(message.sender.userId)) {
message.reply("你今天已经对你的牛子使用了签到啦,明天再来吧~")
return
}
val username = listener.getUserName(message.groupId, message.sender.userId)
val afterStatus = niuziManager.sign(message.sender.userId, username)
val msg = MessageChain.Builder()
.addReply(message.messageId)
.addText("签到成功~")
.addNewLine()
.addText("你的牛子增加了${afterStatus.first}cm~ 总长度为: ${afterStatus.second?.length}")
.build()
message.reply(msg)
}
}
@@ -0,0 +1,51 @@
/*
* Copyright © 2024 RTAkland
* Author: RTAkland
* Date: 2024/9/24
*/
package cn.rtast.fancybot.commands.niuzi
import cn.rtast.fancybot.annotations.CommandDescription
import cn.rtast.fancybot.niuziManager
import cn.rtast.rob.entity.GroupMessage
import cn.rtast.rob.enums.ArrayMessageType
import cn.rtast.rob.util.BaseCommand
import cn.rtast.rob.util.ob.OneBotListener
@CommandDescription("将自己的牛子长度转账给别人")
class NiuziTransferCommand : BaseCommand() {
override val commandNames = listOf("牛子转账", "zz")
override suspend fun executeGroup(listener: OneBotListener, message: GroupMessage, args: List<String>) {
if (args.isEmpty()) {
message.reply("发送`牛子转账 @某人 <长度>` 即可把自己的牛子长度转账给TA")
return
}
val target = message.message.find { it.type == ArrayMessageType.at }!!.data.qq!!.toLong()
val targetNiuzi = niuziManager.getUser(target)
if (targetNiuzi == null) niuziManager.createBlankUser(target)
val transferLength = args.last().toDouble()
val currentNiuzi = niuziManager.getUser(message.sender.userId)
if (currentNiuzi == null) {
message.reply("你没有牛子没办法转账")
return
}
if (currentNiuzi.length <= 0.0) {
message.reply("你已经没有牛子可以转账了")
return
}
if (currentNiuzi.length < transferLength) {
message.reply("你的牛子长度不够转账这么多 >>>${transferLength}cm")
return
}
if (transferLength <= 0) {
message.reply("长度必须大于0!")
return
}
niuziManager.updateLength(message.sender.userId, -transferLength)
niuziManager.updateLength(target, transferLength)
message.reply("转账成功, 你的长度减少了: ${transferLength}cm, 对方的长度增加了 ${transferLength}cm")
}
}
@@ -7,13 +7,24 @@
package cn.rtast.fancybot.commands.parse
import cn.rtast.fancybot.annotations.CommandDescription
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.toByteArray
import cn.rtast.rob.entity.GetMessage
import cn.rtast.rob.entity.GroupMessage
import cn.rtast.rob.enums.ArrayMessageType
import cn.rtast.rob.util.BaseCommand
import cn.rtast.rob.util.ob.MessageChain
import cn.rtast.rob.util.ob.OneBotListener
import cn.rtast.rob.util.ob.asMessageChain
import com.madgag.gif.fmsware.GifDecoder
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.awt.Color
import java.awt.Font
import java.awt.image.BufferedImage
@@ -21,12 +32,14 @@ import java.net.URI
import javax.imageio.ImageIO
import kotlin.math.roundToInt
@CommandDescription("将图片转换成ASCII字符画!")
class AsciiArtCommand : BaseCommand() {
override val commandNames = listOf("/ascii", "/asc")
companion object {
private const val FONT_SIZE = 12
private val font = Font("Monospaced", Font.PLAIN, FONT_SIZE)
private val logger = Logger.getLogger<AsciiArtCommand>()
val waitingList = mutableListOf<Long>()
private fun BufferedImage.convertToAscii(): String {
@@ -63,7 +76,7 @@ class AsciiArtCommand : BaseCommand() {
}
private fun String.saveAsciiArtToImage(width: Int, height: Int): String {
private fun String.saveAsciiArtToImage(width: Int, height: Int): BufferedImage {
val imgWidth = width * FONT_SIZE / 2
val imgHeight = height * FONT_SIZE / 2
val img = BufferedImage(imgWidth, imgHeight, BufferedImage.TYPE_INT_RGB)
@@ -77,29 +90,97 @@ class AsciiArtCommand : BaseCommand() {
g2d.drawString(line, 0, (index + 1) * FONT_SIZE)
}
g2d.dispose()
return img.toByteArray().encodeToBase64()
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(URI(url).toURL()) }
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 listOf("GIF处理失败: 内存溢出").asMessageChain()
logger.info("GIF处理失败: 内存溢出")
} catch (e: Exception) {
return listOf("处理GIF失败: ${e.message}").asMessageChain()
logger.info("gif处理失败: ${e.message}")
}
}
suspend fun callback(message: GroupMessage) {
if (message.sender.userId in waitingList) {
if (message.message.any { it.type == ArrayMessageType.image }) {
val url = message.message.find { it.type == ArrayMessageType.image }!!.data.file!!
val bufferedImage = ImageIO.read(URI(url).toURL())
val imageBase64 =
bufferedImage.convertToAscii().saveAsciiArtToImage(bufferedImage.width, bufferedImage.height)
val msg = MessageChain.Builder().addImage(imageBase64, true).build()
if (message.sender.userId !in waitingList) return
waitingList.removeIf { it == message.sender.userId }
if (message.message.any { it.type == ArrayMessageType.image }
|| message.message.any { it.type == ArrayMessageType.mface }) {
val url = this.getImageUrl(message)
val msg = this.generateAsciiArt(url)
message.reply(msg)
} else {
message.reply("回复错误已取消本次操作")
}
waitingList.removeIf { it == message.sender.userId }
}
fun getImageUrl(groupMessage: GroupMessage): String {
return getImageUrlFromUnified(toUnifiedMessage(groupMessage))
}
fun getImageUrl(getMessageData: GetMessage.Data): 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.Data): 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>) {
if (message.sender.userId !in waitingList) {
message.reply("请继续发送一张图片如果输入错误则取消本次操作")
message.reply("请继续发送一张图片, 如果输入错误则取消本次操作, 如果GIF帧数过多可能会需要很长时间(也可能处理失败)")
waitingList.add(message.sender.userId)
return
} else {
@@ -1,180 +0,0 @@
/*
* Copyright © 2024 RTAkland
* Author: RTAkland
* Date: 2024/9/6
*/
package cn.rtast.fancybot.commands.parse
import cn.rtast.fancybot.entity.bili.ShortUrl
import cn.rtast.fancybot.entity.bili.UserStat
import cn.rtast.fancybot.entity.bili.VideoStat
import cn.rtast.fancybot.entity.bili.ViewCount
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.setTruncat
import cn.rtast.fancybot.util.toByteArray
import cn.rtast.rob.entity.GroupMessage
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 = setTruncat(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, bvid: String, message: GroupMessage) {
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.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.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(listener: OneBotListener, message: GroupMessage) {
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()
.addReply(message.messageId)
.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(listener: OneBotListener, 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 {
engine.eval(input)
} catch (_: ScriptException) {
"Invalid expression"
return null
}.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.util.ob.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.entity.github.RepoInfo
import cn.rtast.fancybot.util.Http
import cn.rtast.fancybot.util.Resources
import cn.rtast.fancybot.util.drawCustomImage
import cn.rtast.fancybot.util.drawString
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.toByteArray
import cn.rtast.fancybot.util.str.encodeToBase64
import cn.rtast.fancybot.util.str.formatNumberEnglish
import cn.rtast.fancybot.util.str.setTruncat
import cn.rtast.fancybot.util.toByteArray
import cn.rtast.fancybot.util.str.setTruncate
import cn.rtast.rob.entity.GroupMessage
import cn.rtast.rob.util.ob.MessageChain
import cn.rtast.rob.util.ob.OneBotListener
import java.awt.Color
import java.awt.Font
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 backgroundColor = Color.WHITE
private val textColor = Color(60, 60, 60)
private val githubRegex = Regex("""github\.com/([^/]+)/([^/]+)""")
private val languageColors = mapOf(
"Kotlin" to Color(169, 123, 255),
@@ -52,7 +52,7 @@ object GitHubParseCommand {
"Go" to Color(0, 173, 216),
"Ruby" to Color(112, 21, 22),
"PHP" to Color(79, 93, 149),
"TypeScript" to Color(43, 116, 137),
"TypeScript" to Color(0x3178c6),
"Swift" to Color(240, 81, 56),
"SCSS" to Color(198, 83, 140),
"Visual Basic 6.0" to Color(44, 99, 83),
@@ -63,17 +63,72 @@ object GitHubParseCommand {
"Shell" to Color(137, 224, 81),
"CMake" to Color(218, 52, 52),
"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)
)
private fun getLanguageColor(language: String): Color {
private fun getLanguageColor(language: String?): Color {
return languageColors[language] ?: languageColors["Unknown"]!!
}
private fun getRepoStat(username: String, repo: String): RepoInfo {
return Http.get<RepoInfo>(
"$REPO_INFO_API/$username/$repo",
headers = mapOf("Authorization" to "Bearer ${configManager.githubKey}")
"$REPO_INFO_API/$username/$repo", headers = mapOf("Authorization" to "Bearer ${configManager.githubKey}")
)
}
@@ -100,34 +155,36 @@ object GitHubParseCommand {
if (repoStat.fork) {
g2d.font = forkParentFont
val parentName = repoStat.parent?.fullName!!
val truncatedParentFullName = setTruncat("复刻自 $parentName", g2d, 1000)
val truncatedParentFullName = setTruncate("复刻自 $parentName", g2d, 1000)
g2d.drawString(truncatedParentFullName, 80, 335)
}
g2d.font = titleCustomFont
val truncatedFullNameText = setTruncat(repoStat.fullName, g2d, 1000)
val truncatedFullNameText = setTruncate(repoStat.fullName, g2d, 1000)
g2d.drawString(truncatedFullNameText, 80, 280)
g2d.font = descriptionCustomFont
g2d.drawString(repoStat.description ?: "暂无描述~", 80, 400, 800)
val avatarImage = ImageIO.read(URI(repoStat.owner.avatarUrl).toURL())
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()
return canvas.toByteArray().encodeToBase64()
}
suspend fun parse(listener: OneBotListener, message: GroupMessage) {
val path = message.rawMessage
.replace(".git", "")
.replace("https://github.com/", "")
.replace("git@github.com:", "")
.split("/")
val user = path.first()
val repo = path.last()
suspend fun parse(message: GroupMessage) {
val matchedResult = githubRegex.find(message.rawMessage)
if (message.rawMessage.contains("github.com") && matchedResult != null) {
val (user, repo) = matchedResult.destructured
val repoStat = this.getRepoStat(user, repo)
val image = this.createImage(repoStat)
val msg = MessageChain.Builder()
.addReply(message.messageId)
.addImage(image, true)
.build()
listener.sendGroupMessage(message.groupId, msg)
val msg = MessageChain.Builder().addImage(image, true).build()
message.reply(msg)
}
}
fun creatRepoImage(user: String, repo: String): String {
val repoStat = this.getRepoStat(user, repo)
val image = this.createImage(repoStat)
return image
}
}
@@ -7,24 +7,52 @@
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.GroupMessage
import cn.rtast.rob.enums.ArrayMessageType
import cn.rtast.rob.util.ob.MessageChain
import cn.rtast.rob.util.ob.asNode
object ImageURLCommand {
fun getImageUrl(message: GetMessage.Data): List<String> {
try {
val images = mutableListOf<String>()
message.message.forEach {
if (it.type == ArrayMessageType.image) images.add(it.data.file!!)
if (it.type == ArrayMessageType.mface) images.add(it.data.url!!)
}
return images
} catch (_: NullPointerException) {
return emptyList()
}
}
suspend fun callback(groupMessage: GroupMessage, message: GetMessage.Data) {
val msg = MessageChain.Builder()
.addReply(message.messageId)
.addText("哈哈哈哈图片来咯~")
.addNewLine()
val image = message.message.find { it.type == ArrayMessageType.image }
if (image == null) {
val gif = message.message.find { it.type == ArrayMessageType.mface }!!
msg.addText(gif.data.url!!)
val images = getImageUrl(message)
if (images.isEmpty()) {
groupMessage.reply("这个消息里没有图片呢!")
} else {
msg.addText(image.data.file!!)
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.asNode(configManager.selfId))
}
}
groupMessage.reply(msg.build())
}
}
@@ -7,8 +7,10 @@
package cn.rtast.fancybot.commands.parse
import cn.rtast.fancybot.util.makeGif
import cn.rtast.fancybot.annotations.CommandDescription
import cn.rtast.fancybot.util.misc.makeGif
import cn.rtast.fancybot.util.str.encodeToBase64
import cn.rtast.rob.entity.GetMessage
import cn.rtast.rob.entity.GroupMessage
import cn.rtast.rob.enums.ArrayMessageType
import cn.rtast.rob.util.BaseCommand
@@ -17,6 +19,7 @@ import cn.rtast.rob.util.ob.OneBotListener
import com.madgag.gif.fmsware.GifDecoder
import java.net.URI
@CommandDescription("倒放GIF")
class ReverseGIFCommand : BaseCommand() {
override val commandNames = listOf("倒放")
@@ -32,8 +35,23 @@ class ReverseGIFCommand : BaseCommand() {
return gifBytes.encodeToBase64()
}
suspend fun reverse(message: GroupMessage, getMsg: GetMessage.Data) {
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) {
if (message.sender.userId !in waitingList) return
waitingList.removeIf { it == message.sender.userId }
val gifUrl = if (message.message.find { it.type == ArrayMessageType.mface } == null) {
message.message.find { it.type == ArrayMessageType.image }!!.data.file!!
} else {
@@ -42,14 +60,13 @@ class ReverseGIFCommand : BaseCommand() {
val base64String = this.reverseGif(gifUrl)
val msg = MessageChain.Builder().addImage(base64String, true).build()
message.reply(msg)
waitingList.removeIf { it == message.sender.userId }
}
}
override suspend fun executeGroup(listener: OneBotListener, message: GroupMessage, args: List<String>) {
if (message.sender.userId !in waitingList) {
waitingList.add(message.sender.userId)
message.reply("发送一张动图来继续操作")
waitingList.add(message.sender.userId)
} else {
message.reply("发送错误本次操作已取消")
waitingList.removeIf { it == 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.util.ob.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)
}
}
}
@@ -1,53 +0,0 @@
/*
* Copyright © 2024 RTAkland
* Author: RTAkland
* Date: 2024/9/4
*/
package cn.rtast.fancybot.commands.record
import cn.rtast.fancybot.configManager
import cn.rtast.fancybot.signManager
import cn.rtast.rob.entity.GroupMessage
import cn.rtast.rob.enums.UserRole
import cn.rtast.rob.util.BaseCommand
import cn.rtast.rob.util.ob.MessageChain
import cn.rtast.rob.util.ob.OneBotListener
class MyPointCommand : BaseCommand() {
override val commandNames = listOf("/我的点数", "/mp")
override suspend fun executeGroup(listener: OneBotListener, message: GroupMessage, args: List<String>) {
// query target's point
if (args.isNotEmpty() && (message.sender.role == UserRole.admin || message.sender.userId in configManager.admins)) {
val targetId = args.first().toLong()
val targetData = signManager.getStatus(targetId)
val msg = MessageChain.Builder().addAt(message.sender.userId)
if (targetData == null) {
msg.addText("用户不存在, 需要先签到一次才能查询点数呢~")
} else {
msg.addText("用户: ${targetData.id} 的点数有: ${targetData.points}")
}
listener.sendGroupMessage(message.groupId, msg.build())
return
}
// query self point
val status = signManager.getStatus(message.sender.userId)
if (status == null) {
val msg = MessageChain.Builder()
.addAt(message.sender.userId)
.addText("你还没有签到过呢! 先发送‘/签到’再来查询吧~")
.build()
listener.sendGroupMessage(message.groupId, msg)
return
} else {
val msg = MessageChain.Builder()
.addAt(message.sender.userId)
.addText("你当前的点数为: ${status.points}")
.build()
listener.sendGroupMessage(message.groupId, msg)
}
}
}
@@ -1,153 +0,0 @@
/*
* Copyright © 2024 RTAkland
* Author: RTAkland
* Date: 2024/9/8
*/
package cn.rtast.fancybot.commands.record
import cn.rtast.fancybot.niuziManager
import cn.rtast.rob.entity.GroupMessage
import cn.rtast.rob.enums.ArrayMessageType
import cn.rtast.rob.util.BaseCommand
import cn.rtast.rob.util.ob.MessageChain
import cn.rtast.rob.util.ob.OneBotListener
class NiuziSignCommand : BaseCommand() {
override val commandNames = listOf("牛子签到")
override suspend fun executeGroup(listener: OneBotListener, message: GroupMessage, args: List<String>) {
if (niuziManager.isSigned(message.sender.userId)) {
val msg = MessageChain.Builder()
.addAt(message.sender.userId)
.addText("你今天已经对你的牛子使用了签到啦,明天再来吧~")
.build()
listener.sendGroupMessage(message.groupId, msg)
return
}
val afterStatus = niuziManager.sign(message.sender.userId)
val msg = MessageChain.Builder()
.addAt(message.sender.userId)
.addText("签到成功~")
.addNewLine()
.addText("你的牛子增加了${afterStatus.first}cm~ 总长度为: ${afterStatus.second?.length}")
.build()
listener.sendGroupMessage(message.groupId, msg)
}
}
class JiJianCommand : BaseCommand() {
override val commandNames = listOf("击剑", "jj")
override suspend fun executeGroup(listener: OneBotListener, message: GroupMessage, args: List<String>) {
if (args.isEmpty()) {
val msg = MessageChain.Builder()
.addAt(message.sender.userId)
.addText("发送`击剑 @xxx`即可对某人进行击剑")
.build()
listener.sendGroupMessage(message.groupId, msg)
return
}
val target = message.message.find { it.type == ArrayMessageType.at }!!.data
val targetId = target.qq!!.toLong()
if (targetId == 0L) {
val msg = MessageChain.Builder()
.addAt(message.sender.userId)
.addText("你的只有一根牛子,他们牛子太多啦你战胜不了他们的~")
.addNewLine()
.addText("所以你不能和他们击剑~")
.build()
listener.sendGroupMessage(message.groupId, msg)
return
}
if (message.sender.userId == targetId) {
val msg = MessageChain.Builder()
.addAt(message.sender.userId)
.addText("你不能和自己击剑啊")
.build()
listener.sendGroupMessage(message.groupId, msg)
return
}
if (!niuziManager.exists(message.sender.userId)) {
val msg = MessageChain.Builder()
.addAt(message.sender.userId)
.addText("你还没有牛子呢, 发送`牛子签到`领取你的牛子吧~")
.build()
listener.sendGroupMessage(message.groupId, msg)
return
}
if (!niuziManager.exists(targetId)) {
val msg = MessageChain.Builder()
.addAt(targetId)
.addText("对方还没有牛子呢, 提醒他领取一根牛子吧~")
.build()
listener.sendGroupMessage(message.groupId, msg)
return
}
if (niuziManager.getUser(message.sender.userId)?.length!! < 0.0) {
val msg = MessageChain.Builder()
.addAt(message.sender.userId)
.addText("你的牛子已经凹进去了没办法进行击剑~发送`牛子签到来增加你的牛子长度吧~`")
.build()
listener.sendGroupMessage(message.groupId, msg)
return
}
if (niuziManager.getUser(targetId)?.length!! < 0.0) {
val msg = MessageChain.Builder()
.addAt(message.sender.userId)
.addText("他已经没有牛子啦!(他的牛子已经凹进去了!!)你不能和他击剑")
.build()
listener.sendGroupMessage(message.groupId, msg)
return
}
val result = niuziManager.jijian(message.sender.userId, targetId)
if (result.first) {
val msg = MessageChain.Builder()
.addAt(message.sender.userId)
.addText("你以已绝对的长度在这场击剑中获胜了~, 你的牛子增加了${result.second}cm")
.build()
listener.sendGroupMessage(message.groupId, msg)
} else {
val msg = MessageChain.Builder()
.addAt(message.sender.userId)
.addText("很不幸你的牛子没能战胜${target.name}, 你的牛子缩短了${result.second}cm")
.addNewLine()
.addText("对方的牛子增加了${result.second}cm~")
.build()
listener.sendGroupMessage(message.groupId, msg)
}
}
}
class MyNiuziCommand : BaseCommand() {
override val commandNames = listOf("我的牛子")
override suspend fun executeGroup(listener: OneBotListener, message: GroupMessage, args: List<String>) {
val niuzi = niuziManager.getUser(message.sender.userId)
if (niuzi == null) {
val msg = MessageChain.Builder()
.addAt(message.sender.userId)
.addText("你还没有牛子呢, 发送`牛子签到`来领取一根专属于你的牛子吧~")
.build()
listener.sendGroupMessage(message.groupId, msg)
return
}
val msg = MessageChain.Builder()
.addAt(message.sender.userId)
.addText("你的牛子长度为: ${niuzi.length}cm")
val niuziString = when (niuzi.length) {
in -999.0..0.0 -> "你的牛子已经凹进去了!!!!"
in 0.1..6.0 -> "小小的也很可爱呢~"
in 6.1..15.0 -> "好长的牛子!"
in 15.1..30.0 -> "你的牛子已经快突破天际了!!!"
else -> "牛子也太长啦把天捅穿啦!!!!!!"
}
msg.addText(niuziString)
listener.sendGroupMessage(message.groupId, msg.build())
}
}
@@ -1,86 +0,0 @@
/*
* Copyright © 2024 RTAkland
* Author: RTAkland
* Date: 2024/9/3
*/
package cn.rtast.fancybot.commands.record
import cn.rtast.fancybot.itemManager
import cn.rtast.fancybot.signManager
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
class RedeemCommand : BaseCommand() {
override val commandNames = listOf("/redeem", "/兑换", "/rdm")
override suspend fun executeGroup(listener: OneBotListener, message: GroupMessage, args: List<String>) {
if (args.isEmpty()) {
val msg = MessageChain.Builder()
.addAt(message.sender.userId)
.addText("发送 /rdm <物品名称> 即可兑换哦!")
.addNewLine()
.addText("发送 /rdm list 即可查看所有可兑换的物品~")
.addNewLine()
.addText("发送 /mp 可以查看自己拥有的点数~")
.build()
listener.sendGroupMessage(message.groupId, msg)
return
}
val status = signManager.getStatus(message.sender.userId)
if (status == null) {
val msg = MessageChain.Builder()
.addAt(message.sender.userId)
.addText("你还没有签到过呢要签到一次才能兑换哦~")
.build()
listener.sendGroupMessage(message.groupId, msg)
return
}
val item = args.first()
if (item == "list" || item == "列表") {
val priceTable =
itemManager.items.joinToString("\n") { "[${it.itemNames.joinToString("|")}]:${it.itemPrice}积分" }
val msg = MessageChain.Builder()
.addAt(message.sender.userId)
.addNewLine()
.addText(priceTable)
.build()
listener.sendGroupMessage(message.groupId, msg)
return
}
val selectedItem = itemManager.items.find { it.itemNames.any { any -> any == item } }
if (selectedItem == null) {
val msg = MessageChain.Builder()
.addAt(message.sender.userId)
.addText("没有找到你想兑换的东西呢")
.addNewLine()
.addText("你可以发送 /rdm list来查看所有可兑换的物品")
.build()
listener.sendGroupMessage(message.groupId, msg)
return
}
val selectedItemPrice = selectedItem.itemPrice
if (selectedItemPrice > status.points) {
val msg = MessageChain.Builder()
.addAt(message.sender.userId)
.addText("你的积分不够兑换这个物品呢~")
.addNewLine()
.addText("你有: ${status.points}个积分, 兑换需要: $selectedItemPrice 个积分~\"")
.build()
listener.sendGroupMessage(message.groupId, msg)
return
}
val afterStatus = signManager.redeemItem(message.sender.userId, selectedItemPrice)?.points!!
val msg = MessageChain.Builder()
.addAt(message.sender.userId)
.addText("兑换成功正在发送奖品中~")
.addNewLine()
.addText("你花费了 $selectedItemPrice 点积分来兑换奖品, 还剩 $afterStatus 点积分")
.build()
listener.sendGroupMessage(message.groupId, msg)
selectedItem.redeemGroup(listener, message, afterStatus)
}
}
@@ -1,34 +0,0 @@
/*
* Copyright © 2024 RTAkland
* Author: RTAkland
* Date: 2024/9/22
*/
package cn.rtast.fancybot.commands.record
import cn.rtast.fancybot.entity.ShortLink
import cn.rtast.fancybot.util.Http
import cn.rtast.fancybot.util.str.encodeToBase64
import cn.rtast.rob.entity.GroupMessage
import cn.rtast.rob.util.BaseCommand
import cn.rtast.rob.util.ob.OneBotListener
class ShortLinkCommand : BaseCommand() {
override val commandNames = listOf("/s", "/sl", "/shortlink", "/short")
private val shortLinkApi = "https://api.rtast.cn"
override suspend fun executeGroup(listener: OneBotListener, message: GroupMessage, args: List<String>) {
if (args.isEmpty()) {
message.reply("发送`/s <url>`来生成短链接`不要放入一些奇怪的链接哦~")
return
}
val target = args.first().encodeToBase64()
val shortLink = Http.post<ShortLink>(
"$shortLinkApi/short_link",
params = mapOf("target" to target)
)
message.reply("$shortLinkApi/s?rnd_id=${shortLink.id}")
}
}
@@ -1,38 +0,0 @@
/*
* Copyright © 2024 RTAkland
* Author: RTAkland
* Date: 2024/8/30
*/
package cn.rtast.fancybot.commands.record
import cn.rtast.fancybot.signManager
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
class SignCommand : BaseCommand() {
override val commandNames = listOf("/签到", "/sign")
override suspend fun executeGroup(listener: OneBotListener, message: GroupMessage, args: List<String>) {
if (signManager.isSigned(message.sender.userId)) {
val msg = MessageChain.Builder()
.addAt(message.sender.userId)
.addText("你今天已经签到过啦, 明天再来吧~")
.build()
listener.sendGroupMessage(message.groupId, msg)
return
}
val randomPoint = signManager.sign(message.sender.userId)
val msg = MessageChain.Builder()
.addAt(message.sender.userId)
.addText("你获得了 $randomPoint 点分数!")
.addNewLine()
.addText("发送‘/我的点数’ 或者 ‘/mp’ 即可查看当前的点数")
.build()
listener.sendGroupMessage(message.groupId, 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.util.ob.MessageChain
import cn.rtast.rob.util.ob.asNode
import okio.IOException
object ImageBedCommand {
suspend fun execute(getMsg: GetMessage.Data, 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.asNode(configManager.selfId))
}
} catch (_: IOException) {
message.reply("上传失败: 图片已过期")
} catch (e: Exception) {
e.printStackTrace()
message.reply("上传失败: ${e.message}")
}
}
}
}
@@ -1,19 +1,19 @@
/*
* Copyright © 2024 RTAkland
* Author: RTAkland
* Date: 2024/8/30
* Date: 2024/10/2
*/
package cn.rtast.fancybot.entity.db
package cn.rtast.fancybot.db
import org.jetbrains.exposed.sql.Table
object SignTable : Table("sign") {
object ActionTable : Table("action") {
val id = integer("id").autoIncrement()
val userId = long("userId").uniqueIndex()
val action = varchar("action", 255)
val timestamp = long("timestamp")
val point = long("point")
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
@@ -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 }
}
}
@@ -0,0 +1,28 @@
/*
* Copyright © 2024 RTAkland
* Author: RTAkland
* Date: 2024/9/24
*/
package cn.rtast.fancybot.db
import org.jetbrains.exposed.sql.Table
object NiuziBankTable : Table("niuzi_bank") {
val id = long("id").autoIncrement()
val userId = long("userId").uniqueIndex()
val timestamp = long("timestamp")
val balance = double("balance")
val interestRate = double("interest_rate")
val nickname = varchar("nickname", 255)
override val primaryKey = PrimaryKey(id)
}
data class NiuziBankAccount(
val userId: Long,
val balance: Double,
val interestRate: Double,
val timestamp: Long,
val nickname: String
)
@@ -5,20 +5,22 @@
*/
package cn.rtast.fancybot.entity.db
package cn.rtast.fancybot.db
import org.jetbrains.exposed.sql.Table
object NiuziTable : Table("niuzu") {
object NiuziTable : Table("niuzi") {
val length = double("length")
val id = long("id").autoIncrement()
val userId = long("userId").uniqueIndex()
val timestamp = long("timestamp")
val nickname = varchar("nickname", 255)
override val primaryKey = PrimaryKey(id)
}
data class Niuzi(
val userId: Long,
val length: Double,
val timestamp: Long
val timestamp: Long,
val nickname: String,
)
@@ -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,10 +7,13 @@
package cn.rtast.fancybot.entity
import cn.rtast.fancybot.entity.enums.ImageType
import cn.rtast.fancybot.entity.enums.WSType
import cn.rtast.fancybot.enums.DatabaseType
import cn.rtast.fancybot.enums.ImageBedType
import cn.rtast.fancybot.enums.ImageType
import cn.rtast.fancybot.enums.WSType
data class Config(
val database: Database,
val ncmAPI: String,
val wsType: WSType,
val wsAddress: String,
@@ -22,11 +25,39 @@ data class Config(
val imageType: ImageType,
val openAIAPIHost: String,
val openAIAPIKey: String,
val openAIModel: String,
val smtpHost: String,
val smtpPort: Int,
val smtpUser: String,
val smtpPassword: String,
val smtpFromAddress: String,
val admins: List<Long>,
val enableAntiRevoke: Boolean
val enableAntiRevoke: Boolean,
val llamaUrl: String,
val llamaModel: String,
val qqMusicApiUrl: String,
val noticeUser: 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,
) {
data class Database(
val type: DatabaseType,
val host: String,
val port: Int,
val user: String,
val password: String,
val database: 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,
)
@@ -7,7 +7,7 @@
package cn.rtast.fancybot.entity
data class FKXQS(
data class KFC(
val data: Data,
) {
data class Data(
@@ -1,24 +0,0 @@
/*
* Copyright © 2024 RTAkland
* Author: RTAkland
* Date: 2024/9/3
*/
package cn.rtast.fancybot.entity
data class MCPing(
val version: Version,
val players: Players,
val favicon: String?
) {
data class Version(
val protocol: Int,
val name: String,
)
data class Players(
val online: Int,
val max: Int,
)
}
@@ -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,
)
}
@@ -0,0 +1,26 @@
/*
* Copyright © 2024 RTAkland
* Author: RTAkland
* Date: 2024/9/24
*/
package cn.rtast.fancybot.entity.bgm
import com.google.gson.annotations.SerializedName
data class BGMSearch(
val list: List<Result>
) {
data class Result(
val url: String,
val images: Images,
val name: String,
@SerializedName("name_cn")
val nameCN: String,
)
data class Images(
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,
)
@@ -0,0 +1,32 @@
/*
* Copyright © 2024 RTAkland
* Author: RTAkland
* Date: 2024/10/12
*/
package cn.rtast.fancybot.entity.bili
import com.google.gson.annotations.SerializedName
data class UserInfo(
val data: Data
) {
data class Data(
val card: Card,
)
data class Card(
val face: String,
val name: String,
val fans: Int,
val sign: String,
@SerializedName("level_info")
val levelInfo: LevelInfo
)
data class LevelInfo(
@SerializedName("current_level")
val currentLevel: Int,
)
}
@@ -0,0 +1,8 @@
package cn.rtast.fancybot.entity.bullshit
data class BullShit(
val after: List<String>,
val before: List<String>,
val bosh: List<String>,
val famous: List<String>
)
@@ -0,0 +1,84 @@
/*
* Copyright © 2024 RTAkland
* Author: RTAkland
* Date: 2024/10/13
*/
package cn.rtast.fancybot.entity.douyin
import com.google.gson.annotations.SerializedName
data class DouyinVideo(
val data: Data
) {
data class Data(
@SerializedName("aweme_detail")
val awemeDetail: AwemeDetail
)
data class AwemeDetail(
val author: Author,
val desc: String,
val statistics: Statistics,
val video: Video,
@SerializedName("share_url")
val shareUrl: String,
)
data class Author(
val nickname: String,
@SerializedName("follower_count")
val followerCount: Int,
val signature: String,
@SerializedName("avatar_thumb")
val avatarThumb: AvatarThumb,
)
data class AvatarThumb(
@SerializedName("url_list")
val urlList: List<String>,
)
data class Statistics(
// 收藏数
@SerializedName("collect_count")
val collectCount: Int,
// 评论数
@SerializedName("comment_count")
val commentCount: Int,
// 点赞数
@SerializedName("digg_count")
val diggCount: Int,
// 播放数
@SerializedName("play_count")
val playCount: Int,
// 分享数
@SerializedName("share_count")
val shareCount: Int,
)
data class Video(
@SerializedName("cover_original_scale")
val coverOriginalScale: CoverOriginalScale,
@SerializedName("bit_rate")
val bitRate: List<BitRate>
)
data class BitRate(
@SerializedName("play_addr")
val playAddr: PlayAddr
)
data class PlayAddr(
@SerializedName("url_list")
val urlList: List<String>,
)
data class CoverOriginalScale(
@SerializedName("url_list")
val urlList: List<String>,
val height: Int,
val width: Int
)
}
@@ -0,0 +1,23 @@
/*
* Copyright © 2024 RTAkland
* Author: RTAkland
* Date: 2024/10/3
*/
package cn.rtast.fancybot.entity.github
data class LatestCommit(
val sha: String,
val commit: Commit
) {
data class Commit(
val committer: Committer
)
data class Committer(
val email: String,
val name: String,
val date: String,
)
}
Loaded 100 of 295 files, more files were not shown because too many files have changed in this diff. Show more