Compare commits
150
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bc28a4fefc | ||
|
|
f0d92d8fe7 | ||
|
|
184378c907 | ||
|
|
fc864529b5 | ||
|
|
4e0cb17f4e | ||
|
|
2ce9d3dc8f | ||
|
|
f48b2ea8b3 | ||
|
|
57d0737f21 | ||
|
|
0d73a29d34 | ||
|
|
7511dfd37f | ||
|
|
3eabe254af | ||
|
|
97cd846b53 | ||
|
|
d8b4f5003b | ||
|
|
ac96431552 | ||
|
|
8fd92b8fed | ||
|
|
9381ef8637 | ||
|
|
b64f3580d0 | ||
|
|
3fba603b17 | ||
|
|
492f36d6a5 | ||
|
|
10a61fbee7 | ||
|
|
b0d295d97f | ||
|
|
f5d6a3acb7 | ||
|
|
f5cc673836 | ||
|
|
838d95391e | ||
|
|
8999e8912a | ||
|
|
5d3a31dd28 | ||
|
|
28cf80c271 | ||
|
|
b3c3e230bf | ||
|
|
1b7f09732f | ||
|
|
e5b8b42d7a | ||
|
|
f22934c325 | ||
|
|
9a7d3af844 | ||
|
|
bbb552dc14 | ||
|
|
2ac27d29e7 | ||
|
|
087d55ba28 | ||
|
|
fbcdbe301a | ||
|
|
6b4ef38443 | ||
|
|
c4e6f9e348 | ||
|
|
a59a544792 | ||
|
|
c2e1ec91b5 | ||
|
|
88fd259712 | ||
|
|
ae3eae4e71 | ||
|
|
ac88dcc296 | ||
|
|
7f3b1fd1b5 | ||
|
|
e375f20e9c | ||
|
|
bfbeee2880 | ||
|
|
c4d8c0eaf0 | ||
|
|
f96f7b7055 | ||
|
|
6f32323fec | ||
|
|
73b417775a | ||
|
|
c9d2ddeff7 | ||
|
|
f0e6f31faf | ||
|
|
a78a365ebd | ||
|
|
d036c2bdba | ||
|
|
c831a76850 | ||
|
|
ec6e186284 | ||
|
|
95355d92fe | ||
|
|
ff65333a8f | ||
|
|
499eab9549 | ||
|
|
242ce3e60e | ||
|
|
83e3404665 | ||
|
|
6bc77fb4ac | ||
|
|
217364ffb8 | ||
|
|
2ac7d3b20b | ||
|
|
5ac05f052a | ||
|
|
1b1a579f23 | ||
|
|
1aab83ae51 | ||
|
|
c428b84e10 | ||
|
|
e01a98d254 | ||
|
|
f783f8b4ec | ||
|
|
1c3f582c86 | ||
|
|
a0b74d3df4 | ||
|
|
ccb72a7277 | ||
|
|
7fda4dc333 | ||
|
|
68b7c60a4d | ||
|
|
e0e89d1ecb | ||
|
|
6da5553d1f | ||
|
|
90225ae147 | ||
|
|
1c00093560 | ||
|
|
7b8120dbc5 | ||
|
|
af6f580583 | ||
|
|
ac7910d20c | ||
|
|
b8568c00e7 | ||
|
|
52c791d3ed | ||
|
|
f34e12b3d2 | ||
|
|
c280c02a4e | ||
|
|
829f858840 | ||
|
|
fdad0054ef | ||
|
|
cd69d9a18e | ||
|
|
44c622190d | ||
|
|
f9e6bac23d | ||
|
|
06a385db1a | ||
|
|
b660b504fd | ||
|
|
8504533659 | ||
|
|
92bf62347c | ||
|
|
8ae4ce7870 | ||
|
|
54354e0a52 | ||
|
|
a80cbdcb1a | ||
|
|
9ff4f03b24 | ||
|
|
95a6abbfb3 | ||
|
|
f62055d2ac | ||
|
|
9e198c0084 | ||
|
|
7dda9ad4bc | ||
|
|
80557e9ccc | ||
|
|
e4d6e683d3 | ||
|
|
97e62d5422 | ||
|
|
a850ab9152 | ||
|
|
33f4014bae | ||
|
|
327426988b | ||
|
|
938ef0792a | ||
|
|
af9a117561 | ||
|
|
0ad3314ddb | ||
|
|
b0ae938722 | ||
|
|
91906d9711 | ||
|
|
b7f82446f9 | ||
|
|
69743ca42b | ||
|
|
35fd368cb6 | ||
|
|
63c1de608d | ||
|
|
c6f1bf9629 | ||
|
|
ed85575e14 | ||
|
|
2b725ab71e | ||
|
|
0a5c9b63c2 | ||
|
|
262cfa532c | ||
|
|
956b29d076 | ||
|
|
8f7a5e5f0e | ||
|
|
04b6823d7e | ||
|
|
6bb6979467 | ||
|
|
d42fd55228 | ||
|
|
88a513864a | ||
|
|
98a6ebb3d4 | ||
|
|
4078af7c29 | ||
|
|
05efdaebff | ||
|
|
9abbf2830a | ||
|
|
c9c0f8e360 | ||
|
|
b82a895d99 | ||
|
|
5f0921410d | ||
|
|
45fafd4a4d | ||
|
|
d7d7e237e6 | ||
|
|
a349687e4f | ||
|
|
385dcb4328 | ||
|
|
02ab1e816f | ||
|
|
93338a9d64 | ||
|
|
3a033d2694 | ||
|
|
57885473bd | ||
|
|
767bd7c0f3 | ||
|
|
4ac5d6603b | ||
|
|
9ba1c135d8 | ||
|
|
a07560fa37 | ||
|
|
2eb8a3e387 | ||
|
|
805c6d56c2 |
No files matched your search
@@ -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
@@ -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"
|
||||
}
|
||||
@@ -1,27 +1,45 @@
|
||||
[versions]
|
||||
kotlinVersion = "2.0.10"
|
||||
ronebotVersion = "1.11.8"
|
||||
kotlinVersion = "2.0.20"
|
||||
ronebotVersion = "1.14.1"
|
||||
shadowVersion = "8.3.0"
|
||||
okhttpVersion = "5.0.0-alpha.14"
|
||||
exposedVersion = "0.53.0"
|
||||
sqliteJDBCVersion = "3.46.1.0"
|
||||
motdPingerVersion = "1.1.2"
|
||||
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]
|
||||
|
||||
|
||||
@@ -4,29 +4,37 @@
|
||||
* 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.commands.misc.JrrpCommand
|
||||
import cn.rtast.fancybot.commands.niuzi.NiuziRedeemCommand
|
||||
import cn.rtast.fancybot.commands.misc.ShortLinkCommand
|
||||
import cn.rtast.fancybot.entity.Config
|
||||
import cn.rtast.fancybot.entity.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 cn.rtast.rob.ROneBotFactory
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.GsonBuilder
|
||||
import java.time.Instant
|
||||
@@ -37,8 +45,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,
|
||||
@@ -61,14 +80,29 @@ val DEFAULT_CONFIG = Config(
|
||||
llamaUrl = "http://127.0.0.1",
|
||||
llamaModel = "llama3.1",
|
||||
qqMusicApiUrl = "http://127.0.0.1:3200",
|
||||
startUpNoticeUser = 114514L,
|
||||
selfId = 114514L
|
||||
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
|
||||
|
||||
@@ -78,31 +112,33 @@ val items = listOf(
|
||||
TenSetuItem(), TenSetuR18Item()
|
||||
)
|
||||
|
||||
val commands = listOf(
|
||||
EchoCommand(), JrrpCommand(),
|
||||
NiuziRedeemCommand(), HitokotoCommand(),
|
||||
KFCCommand(), QRCodeCommand(),
|
||||
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(),
|
||||
LikeMeCommand(), NiuziTransferCommand(),
|
||||
NiuziSignCommand(), NiuziJiJianCommand(),
|
||||
NiuziQueryCommand(), MyNiuziCommand(),
|
||||
NiuziBankCommand(), WithdrawCommand(),
|
||||
BankTransferCommand(), CreateBankAccountCommand(),
|
||||
BankBalanceCommand(), DepositCommand(),
|
||||
DMSearchCommand(), LlamaCommand(),
|
||||
QQMusicCommand(), BullShitGenerateCommand(),
|
||||
NiuziRankCommand(), NiuziBankRankCommand(),
|
||||
ShotSelfCommand(), TenSetuCommand()
|
||||
val tasks = mapOf(
|
||||
10000000L to suspend {
|
||||
configManager.admins.forEach {
|
||||
ROneBotFactory.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()
|
||||
)
|
||||
@@ -7,14 +7,26 @@
|
||||
|
||||
package cn.rtast.fancybot
|
||||
|
||||
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.entity.enums.WSType
|
||||
import cn.rtast.fancybot.util.initDatabase
|
||||
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.*
|
||||
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
|
||||
@@ -25,10 +37,11 @@ import java.net.URI
|
||||
|
||||
class FancyBot : OneBotListener {
|
||||
|
||||
private val logger = Logger.getLogger<FancyBot>()
|
||||
private val coroutineScope = CoroutineScope(Dispatchers.IO)
|
||||
|
||||
override suspend fun onWebsocketOpenEvent() {
|
||||
this.sendPrivateMessage(configManager.startUpNoticeUser, "FancyBot启动完成~")
|
||||
this.sendPrivateMessage(configManager.noticeUser, "FancyBot启动完成~")
|
||||
}
|
||||
|
||||
override suspend fun onGroupMessage(message: GroupMessage, json: String) {
|
||||
@@ -37,43 +50,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.message.any { it.type == ArrayMessageType.face && it.data.id.toString() == "419" }) {
|
||||
message.reply("你发牛魔的火车呢, 我直接就是打断")
|
||||
}
|
||||
|
||||
if (message.rawMessage.contains("原神")) {
|
||||
(0..10).forEach { _ ->
|
||||
message.reaction(QQFace.entries.random())
|
||||
}
|
||||
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.rawMessage.toList().any { it in arrayListOf('*', '-', '/', '+', '=') }) {
|
||||
val calculateResult = CalculateCommand.parse(message.rawMessage)
|
||||
calculateResult?.let { message.reply(calculateResult) }
|
||||
}
|
||||
|
||||
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 = this.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 (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
|
||||
) {
|
||||
BVParseCommand.parse(this, message)
|
||||
if (command.contains("reaction")) {
|
||||
// 使用reaction刷屏回应一条消息
|
||||
ReactionCommand.reaction(this, message, getMsg.messageId)
|
||||
}
|
||||
if (command.contains("图床")) {
|
||||
// 将一个图片上传到图床
|
||||
ImageBedCommand.execute(getMsg, message)
|
||||
}
|
||||
}
|
||||
|
||||
coroutineScope.launch {
|
||||
@@ -81,7 +123,7 @@ 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())
|
||||
}
|
||||
}
|
||||
@@ -109,7 +151,7 @@ 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) {
|
||||
@@ -123,21 +165,23 @@ class FancyBot : OneBotListener {
|
||||
this.sendGroupMessage(groupId, msg)
|
||||
}
|
||||
|
||||
override suspend fun onPoke(event: PokeEvent) {
|
||||
event.groupId?.let {
|
||||
if (event.targetId == configManager.selfId) {
|
||||
val msg = MessageChain.Builder()
|
||||
.addAt(event.userId)
|
||||
.addText("你${event.action.first()}牛魔呢")
|
||||
.build()
|
||||
this.sendGroupMessage(it, msg)
|
||||
}
|
||||
override suspend fun onGroupPoke(event: PokeEvent) {
|
||||
if (event.targetId == configManager.selfId) {
|
||||
val msg = MessageChain.Builder()
|
||||
.addAt(event.userId)
|
||||
.addText("你${event.action.first()}牛魔呢")
|
||||
.build()
|
||||
this.sendGroupMessage(event.groupId!!, msg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun initFilesDir() {
|
||||
File("./files/images").also { it.mkdirs() }
|
||||
override suspend fun onBeKicked(groupId: Long, operator: Long, time: Long) {
|
||||
blackListManager.insertGroup(groupId, operator, time)
|
||||
this.sendPrivateMessage(
|
||||
configManager.noticeUser,
|
||||
"被: ${groupId}踢出! 操作人: $operator 时间: ${time.convertToDate()} 已将其拉入黑名单!"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun main() {
|
||||
@@ -155,7 +199,8 @@ suspend fun main() {
|
||||
}
|
||||
initDatabase()
|
||||
initFilesDir()
|
||||
val commandManager = rob.commandManager
|
||||
commands.forEach { commandManager.register(it) }
|
||||
items.forEach { itemManager.register(it) }
|
||||
initCommand(rob)
|
||||
initSetuIndex()
|
||||
initItems()
|
||||
initBackgroundTasks(rob)
|
||||
}
|
||||
@@ -8,6 +8,9 @@
|
||||
package cn.rtast.fancybot.commands
|
||||
|
||||
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.OneBotListener
|
||||
@@ -17,6 +20,25 @@ 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) {
|
||||
listener.sendGroupMessage(message.groupId, 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")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -26,13 +26,12 @@ class HelpCommand : BaseCommand() {
|
||||
if (args.isEmpty()) {
|
||||
val node = NodeMessageChain.Builder()
|
||||
val msg = MessageChain.Builder()
|
||||
commands.sortedBy { it::class.simpleName }.forEach {
|
||||
commands.sortedBy { it::class.simpleName }.toMutableList().forEach {
|
||||
val description = if (!it::class.hasAnnotation<CommandDescription>()) "暂无描述"
|
||||
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("[$commandName] [$description] 命令: $commandNames").addNewLine()
|
||||
}
|
||||
msg.addText("共计${commands.size}条命令")
|
||||
node.addMessageChain(msg.build(), configManager.selfId)
|
||||
|
||||
@@ -9,6 +9,8 @@ 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
|
||||
@@ -44,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)
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,9 @@ 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.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
|
||||
@@ -68,6 +70,7 @@ class AICommand : BaseCommand() {
|
||||
.build()
|
||||
nodeMsg.addMessageChain(msg, configManager.selfId)
|
||||
listener.sendGroupForwardMsg(message.groupId, nodeMsg.build())
|
||||
insertActionRecord(CommandAction.AI, message.sender.userId, "$content-$model-GPT")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,5 +97,6 @@ class LlamaCommand : BaseCommand() {
|
||||
.build()
|
||||
nodeMsg.addMessageChain(msg, configManager.selfId)
|
||||
listener.sendGroupForwardMsg(message.groupId, nodeMsg.build())
|
||||
insertActionRecord(CommandAction.AI, message.sender.userId, "$prompt-LLAMA")
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -20,8 +20,8 @@ import cn.rtast.rob.util.ob.OneBotListener
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
|
||||
@CommandDescription("搜索动漫")
|
||||
class DMSearchCommand : BaseCommand() {
|
||||
@CommandDescription("搜索动漫(Bangumi.tv)")
|
||||
class AnimeSearchCommand : BaseCommand() {
|
||||
override val commandNames = listOf("/dm", "动漫")
|
||||
|
||||
private val client = OkHttpClient()
|
||||
@@ -61,6 +61,6 @@ class CigaretteCommand : BaseCommand() {
|
||||
.build()
|
||||
nodeMsg.addMessageChain(footerMsg, configManager.selfId)
|
||||
message.reply(nodeMsg.build())
|
||||
message.sender.groupPoke()
|
||||
message.sender.poke()
|
||||
}
|
||||
}
|
||||
@@ -11,9 +11,9 @@ 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
|
||||
|
||||
@@ -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("查询失败~")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,11 +11,11 @@ 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
|
||||
|
||||
@@ -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,92 +0,0 @@
|
||||
/*
|
||||
* Copyright © 2024 RTAkland
|
||||
* Author: RTAkland
|
||||
* Date: 2024/9/3
|
||||
*/
|
||||
|
||||
|
||||
package cn.rtast.fancybot.commands.lookup
|
||||
|
||||
import cn.rtast.fancybot.annotations.CommandDescription
|
||||
import cn.rtast.motdpinger.BedrockPing
|
||||
import cn.rtast.motdpinger.JavaPing
|
||||
import cn.rtast.motdpinger.removeColorCodes
|
||||
import cn.rtast.rob.entity.GroupMessage
|
||||
import cn.rtast.rob.util.BaseCommand
|
||||
import cn.rtast.rob.util.ob.MessageChain
|
||||
import cn.rtast.rob.util.ob.OneBotListener
|
||||
import java.net.SocketException
|
||||
|
||||
@CommandDescription("PingMC服务器")
|
||||
class MCPingCommand : BaseCommand() {
|
||||
override val commandNames = listOf("/mcping")
|
||||
|
||||
override suspend fun executeGroup(listener: OneBotListener, message: GroupMessage, args: List<String>) {
|
||||
if (args.isEmpty()) {
|
||||
val msg = MessageChain.Builder()
|
||||
.addText("使用方法: /mcping <host>:[port] [java|be]")
|
||||
.addNewLine()
|
||||
.addText("不添加平台默认Java版~")
|
||||
.build()
|
||||
message.reply(msg)
|
||||
return
|
||||
}
|
||||
val platform = if (args.size == 2) args.last() else "java"
|
||||
when (platform) {
|
||||
"java", "je", "Java", "JAVA" -> {
|
||||
val parts = args.first().split(":")
|
||||
val port = if (parts.size == 1) {
|
||||
25565
|
||||
} else {
|
||||
parts.last().toInt()
|
||||
}
|
||||
val host = parts.first()
|
||||
val response = JavaPing().ping(host, port, 10000)
|
||||
if (response == null) {
|
||||
message.reply("无法获取服务器的消息请检查输入是否正确 >>> $host:$port")
|
||||
return
|
||||
}
|
||||
val msg = MessageChain.Builder()
|
||||
response.favicon?.let {
|
||||
msg.addImage(response.favicon!!.replace("data:image/png;base64,", ""), true)
|
||||
}
|
||||
msg.addText("服务器地址: $host:$port | 服务器类型: Java")
|
||||
.addNewLine()
|
||||
.addText("服务器版本: ${response.version.name}/${response.version.protocol}")
|
||||
.addNewLine()
|
||||
.addText("在线玩家数: ${response.players.online}/${response.players.max}")
|
||||
message.reply(msg.build())
|
||||
}
|
||||
|
||||
"be", "bedrock", "BE", "Bedrock" -> {
|
||||
val parts = args.first().split(":")
|
||||
val port = if (parts.size == 1) {
|
||||
19132
|
||||
} else {
|
||||
parts.last().toInt()
|
||||
}
|
||||
val host = parts.first()
|
||||
try {
|
||||
val response = BedrockPing().ping(host, port, 10000)
|
||||
val msg = MessageChain.Builder()
|
||||
.addText("服务器地址: $host:$port | 服务器类型: Bedrock")
|
||||
.addNewLine()
|
||||
.addText("服务器版本: ${response.version}/${response.protocolVersion}")
|
||||
.addNewLine()
|
||||
.addText("在线玩家: ${response.onlinePlayers}/${response.maxPlayers}")
|
||||
.addNewLine()
|
||||
.addText("MOTD: ${response.motd.removeColorCodes()}")
|
||||
.addNewLine()
|
||||
.addText(response.subTitle.removeColorCodes())
|
||||
message.reply(msg.build())
|
||||
} catch (_: SocketException) {
|
||||
message.reply("无法从服务器接收Ping结果请检查服务器地址是否正确 >>> $host:$port")
|
||||
}
|
||||
}
|
||||
|
||||
else -> {
|
||||
message.reply("输入错误请在 `java` 和 `be`中选择 >>> $platform")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,27 +8,92 @@
|
||||
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("/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.joinToString(" ")
|
||||
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 {
|
||||
val msg = MessageChain.Builder()
|
||||
.addText("域名: ${host}解析后的IP地址为: ")
|
||||
.addText(withContext(Dispatchers.IO) { InetAddress.getByName(host) }.hostAddress)
|
||||
.build()
|
||||
message.reply(msg)
|
||||
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()
|
||||
.addText("解析后的地址: $host:$port")
|
||||
.addNewLine()
|
||||
.addText("权重: $weight | 优先级: $priority")
|
||||
.addNewLine()
|
||||
.addText("$host -> $ip")
|
||||
.addNewLine()
|
||||
.addText("国家: ${geo.country} 地区: ${geo.region} 城市: ${geo.city} | 所属组织: ${geo.org}")
|
||||
.build()
|
||||
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) {
|
||||
|
||||
@@ -56,9 +56,11 @@ class PixivCommand : BaseCommand() {
|
||||
} catch (_: Exception) {
|
||||
}
|
||||
}
|
||||
node.addMessageChain(MessageChain.Builder()
|
||||
.addText("图片来源: Pixiv")
|
||||
.build(), configManager.selfId)
|
||||
node.addMessageChain(
|
||||
MessageChain.Builder()
|
||||
.addText("图片来源: Pixiv")
|
||||
.build(), configManager.selfId
|
||||
)
|
||||
message.reply(node.build())
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
/*
|
||||
* Copyright © 2024 RTAkland
|
||||
* Author: RTAkland
|
||||
* Date: 2024/8/30
|
||||
*/
|
||||
|
||||
|
||||
package cn.rtast.fancybot.commands.lookup
|
||||
|
||||
import cn.rtast.fancybot.annotations.CommandDescription
|
||||
import cn.rtast.rob.entity.GroupMessage
|
||||
import cn.rtast.rob.util.BaseCommand
|
||||
import cn.rtast.rob.util.ob.MessageChain
|
||||
import cn.rtast.rob.util.ob.OneBotListener
|
||||
|
||||
@CommandDescription("生成二维码")
|
||||
class QRCodeCommand : BaseCommand() {
|
||||
override val commandNames = listOf("/qr")
|
||||
|
||||
override suspend fun executeGroup(listener: OneBotListener, message: GroupMessage, args: List<String>) {
|
||||
val content = args.joinToString(" ")
|
||||
val msg = MessageChain.Builder()
|
||||
.addImage("https://api.rtast.cn/api/generate_qr?data=$content")
|
||||
.build()
|
||||
message.reply(msg)
|
||||
}
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
@@ -12,12 +12,12 @@ 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
|
||||
|
||||
@@ -10,7 +10,9 @@ 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
|
||||
@@ -23,9 +25,11 @@ class WikipediaCommand : BaseCommand() {
|
||||
|
||||
private val wikipediaAPI = "https://proxy.rtast.cn/https/zh.wikipedia.org/w/api.php"
|
||||
|
||||
private fun String.extractPlainTextFromHtml(): String {
|
||||
val doc = Jsoup.parse(this)
|
||||
return doc.text()
|
||||
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>) {
|
||||
@@ -69,6 +73,8 @@ class WikipediaCommand : BaseCommand() {
|
||||
.addText("没有查询到指定的wiki页面呢 >>> $title")
|
||||
.build()
|
||||
message.reply(msg)
|
||||
} finally {
|
||||
insertActionRecord(CommandAction.Wiki, message.sender.userId, title)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,8 @@
|
||||
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.ArrayMessage
|
||||
import cn.rtast.rob.entity.GroupMessage
|
||||
import cn.rtast.rob.enums.ArrayMessageType
|
||||
@@ -20,13 +21,17 @@ class AntiRevokeCommand : BaseCommand() {
|
||||
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("你不许用防撤回")
|
||||
val messageId = args.first().toLong()
|
||||
val getMsg = listener.getMessage(messageId)
|
||||
val msgList = mutableListOf<ArrayMessage>()
|
||||
msgList.add(ArrayMessage(ArrayMessageType.at, ArrayMessage.Data(qq = getMsg.sender.userId.toString())))
|
||||
msgList.add(ArrayMessage(ArrayMessageType.text, ArrayMessage.Data(text = "\n被撤回的消息如下: \n")))
|
||||
msgList.addAll(getMsg.message)
|
||||
listener.sendGroupMessage(message.groupId, msgList)
|
||||
if (message.sender.isAdmin || message.sender.isOwner) {
|
||||
val messageId = args.first().toLong()
|
||||
val getMsg = listener.getMessage(messageId)
|
||||
val msgList = mutableListOf<ArrayMessage>()
|
||||
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)
|
||||
insertActionRecord(CommandAction.AntiRevoke, message.sender.userId, getMsg.messageId.toString())
|
||||
} else {
|
||||
message.reply("你不许用防撤回")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,9 +7,10 @@
|
||||
|
||||
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.Resources
|
||||
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
|
||||
@@ -18,6 +19,7 @@ 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", "狗屁不通")
|
||||
|
||||
@@ -65,6 +67,6 @@ class BullShitGenerateCommand : BaseCommand() {
|
||||
.addMessageChain(MessageChain.Builder().addText(finalText).build(), configManager.selfId)
|
||||
.build()
|
||||
message.reply(node)
|
||||
message.sender.groupPoke()
|
||||
message.sender.poke()
|
||||
}
|
||||
}
|
||||
@@ -12,16 +12,18 @@ 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("执行不同语言的代码片段")
|
||||
@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"
|
||||
@@ -31,16 +33,22 @@ class CompilerCommand : BaseCommand() {
|
||||
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"),
|
||||
@@ -48,12 +56,18 @@ class CompilerCommand : BaseCommand() {
|
||||
).stdout
|
||||
}
|
||||
}
|
||||
val msg = MessageChain.Builder()
|
||||
.addReply(message.messageId)
|
||||
.addText("执行结果如下:")
|
||||
.addNewLine()
|
||||
.addText(response.replace("<outStream>", "").replace("</outStream>", ""))
|
||||
.build()
|
||||
listener.sendGroupMessage(message.groupId, msg)
|
||||
try {
|
||||
val msg = MessageChain.Builder()
|
||||
.addReply(message.messageId)
|
||||
.addText("执行结果如下:")
|
||||
.addNewLine()
|
||||
.addText(response.replace("<outStream>", "").replace("</outStream>", ""))
|
||||
.build()
|
||||
listener.sendGroupMessage(message.groupId, msg)
|
||||
} catch (e: Exception) {
|
||||
message.reply("执行错误: ${e.message}")
|
||||
} finally {
|
||||
insertActionRecord(CommandAction.Compiler, message.sender.userId)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -8,11 +8,11 @@
|
||||
package cn.rtast.fancybot.commands.misc
|
||||
|
||||
import cn.rtast.fancybot.annotations.CommandDescription
|
||||
import cn.rtast.fancybot.util.Resources
|
||||
import cn.rtast.fancybot.util.drawCircularImage
|
||||
import cn.rtast.fancybot.util.makeGif
|
||||
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
|
||||
|
||||
@@ -22,15 +22,16 @@ import java.time.ZoneId
|
||||
class KFCCommand : BaseCommand() {
|
||||
override val commandNames = listOf("/fkxqs", "/kfc")
|
||||
|
||||
private fun isThursday(): Boolean {
|
||||
val dateTime = LocalDateTime.ofInstant(
|
||||
Instant.now(),
|
||||
ZoneId.of("Asia/Shanghai")
|
||||
)
|
||||
return dateTime.dayOfWeek == DayOfWeek.THURSDAY
|
||||
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("很可惜今天并不是星期四~")
|
||||
|
||||
@@ -0,0 +1,668 @@
|
||||
/*
|
||||
* 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.InetAddress
|
||||
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("回复错误已经取消本次操作")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
listener.sendGroupMessage(message.groupId, msg)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* 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(listener: OneBotListener, message: GroupMessage, messageId: Long, limit: Int = 30) {
|
||||
(0..limit - 1).forEach {
|
||||
listener.reaction(
|
||||
message.groupId,
|
||||
messageId,
|
||||
QQFace.entries.random().id.toString()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun executeGroup(listener: OneBotListener, message: GroupMessage, args: List<String>) =
|
||||
reaction(listener, message, message.messageId)
|
||||
}
|
||||
@@ -8,8 +8,8 @@
|
||||
package cn.rtast.fancybot.commands.misc
|
||||
|
||||
import cn.rtast.fancybot.annotations.CommandDescription
|
||||
import cn.rtast.fancybot.entity.enums.CountryList
|
||||
import cn.rtast.fancybot.util.randomBooleanWithProbability
|
||||
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
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
/*
|
||||
* Copyright © 2024 RTAkland
|
||||
* Author: RTAkland
|
||||
* Date: 2024/10/15
|
||||
*/
|
||||
|
||||
|
||||
package cn.rtast.fancybot.commands.misc
|
||||
|
||||
import cn.rtast.fancybot.annotations.CommandDescription
|
||||
|
||||
@CommandDescription("复读鸡")
|
||||
object RepeatCommand {
|
||||
}
|
||||
+1
-1
@@ -14,7 +14,7 @@ import cn.rtast.rob.util.ob.MessageChain
|
||||
import cn.rtast.rob.util.ob.OneBotListener
|
||||
|
||||
@CommandDescription("资料卡点赞")
|
||||
class LikeMeCommand : BaseCommand() {
|
||||
class SendLikeCommand : BaseCommand() {
|
||||
override val commandNames = listOf("赞我")
|
||||
|
||||
override suspend fun executeGroup(listener: OneBotListener, message: GroupMessage, args: List<String>) {
|
||||
@@ -9,9 +9,10 @@ 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
|
||||
@@ -47,16 +48,13 @@ class SendMailCommand : BaseCommand() {
|
||||
message.reply("发送`/mail <收件人地址>`即可发送测试邮件~")
|
||||
return
|
||||
}
|
||||
if (message.sender.userId !in configManager.admins) {
|
||||
val msg = MessageChain.Builder()
|
||||
.addReply(message.messageId)
|
||||
.addText("你不许发邮件")
|
||||
.build()
|
||||
listener.sendGroupMessage(message.groupId, msg)
|
||||
return
|
||||
if (message.sender.isAdmin || message.sender.isOwner) {
|
||||
val target = args.first()
|
||||
this.sendMail(target)
|
||||
message.reply("正在发送邮件, 请查收~(可能会出现在垃圾桶中~)")
|
||||
insertActionRecord(CommandAction.SendMail, message.sender.userId, target)
|
||||
} else {
|
||||
message.reply("你不许发邮件")
|
||||
}
|
||||
val target = args.first()
|
||||
this.sendMail(target)
|
||||
message.reply("正在发送邮件, 请查收~(可能会出现在垃圾桶中~)")
|
||||
}
|
||||
}
|
||||
@@ -7,10 +7,13 @@
|
||||
|
||||
package cn.rtast.fancybot.commands.misc
|
||||
|
||||
import cn.rtast.fancybot.API_RTAST_URL
|
||||
import cn.rtast.fancybot.annotations.CommandDescription
|
||||
import cn.rtast.fancybot.entity.ShortLink
|
||||
import cn.rtast.fancybot.configManager
|
||||
import cn.rtast.fancybot.entity.shortlink.ShortLinkPayload
|
||||
import cn.rtast.fancybot.entity.shortlink.ShortLinkResponse
|
||||
import cn.rtast.fancybot.util.Http
|
||||
import cn.rtast.fancybot.util.str.encodeToBase64
|
||||
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
|
||||
@@ -19,18 +22,25 @@ import cn.rtast.rob.util.ob.OneBotListener
|
||||
class ShortLinkCommand : BaseCommand() {
|
||||
override val commandNames = listOf("/s")
|
||||
|
||||
private val shortLinkApi = "https://api.rtast.cn"
|
||||
|
||||
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().encodeToBase64()
|
||||
val shortLink = Http.post<ShortLink>(
|
||||
"$shortLinkApi/short_link",
|
||||
params = mapOf("target" to target)
|
||||
)
|
||||
message.reply("$shortLinkApi/s?rnd_id=${shortLink.id}")
|
||||
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()
|
||||
listener.sendGroupMessage(message.groupId, msg)
|
||||
}
|
||||
}
|
||||
@@ -7,11 +7,14 @@
|
||||
|
||||
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.MessageChain
|
||||
import cn.rtast.rob.util.ob.OneBotListener
|
||||
|
||||
@Deprecated("已迁移到兑换列表中")
|
||||
@CommandDescription("获得十张色图!")
|
||||
class TenSetuCommand : BaseCommand() {
|
||||
override val commandNames = listOf("十张色图")
|
||||
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,7 @@ import cn.rtast.fancybot.annotations.CommandDescription
|
||||
import cn.rtast.fancybot.commands
|
||||
import cn.rtast.fancybot.niuziBankManager
|
||||
import cn.rtast.fancybot.niuziManager
|
||||
import cn.rtast.fancybot.util.getUserName
|
||||
import cn.rtast.fancybot.util.misc.getUserName
|
||||
import cn.rtast.rob.entity.GroupMessage
|
||||
import cn.rtast.rob.enums.ArrayMessageType
|
||||
import cn.rtast.rob.util.BaseCommand
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* 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.configManager
|
||||
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("你没权限呢!")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,14 +22,13 @@ class NiuziRedeemCommand : BaseCommand() {
|
||||
override suspend fun executeGroup(listener: OneBotListener, message: GroupMessage, args: List<String>) {
|
||||
if (args.isEmpty()) {
|
||||
val msg = MessageChain.Builder()
|
||||
.addReply(message.messageId)
|
||||
.addText("发送 /rdm <物品名称> 即可兑换哦!")
|
||||
.addNewLine()
|
||||
.addText("发送 /rdm list 即可查看所有可兑换的物品~")
|
||||
.addNewLine()
|
||||
.addText("发送 `我的牛子` 可以查看自己的牛子长度~")
|
||||
.build()
|
||||
listener.sendGroupMessage(message.groupId, msg)
|
||||
message.reply(msg)
|
||||
return
|
||||
}
|
||||
val status = niuziManager.getUser(message.sender.userId)
|
||||
@@ -47,29 +46,26 @@ class NiuziRedeemCommand : BaseCommand() {
|
||||
val selectedItem = itemManager.items.find { it.itemNames.any { any -> any == item } }
|
||||
if (selectedItem == null) {
|
||||
val msg = MessageChain.Builder()
|
||||
.addReply(message.messageId)
|
||||
.addText("没有找到你想兑换的东西呢")
|
||||
.addNewLine()
|
||||
.addText("你可以发送 /rdm list来查看所有可兑换的物品")
|
||||
.build()
|
||||
listener.sendGroupMessage(message.groupId, msg)
|
||||
message.reply(msg)
|
||||
return
|
||||
}
|
||||
val selectedItemPrice = selectedItem.itemPrice
|
||||
if (selectedItemPrice > status.length) {
|
||||
val msg = MessageChain.Builder()
|
||||
.addReply(message.messageId)
|
||||
.addText("你的牛子长度不够兑换这个物品呢~")
|
||||
.addNewLine()
|
||||
.addText("你有: ${status.length}cm, 兑换需要: $selectedItemPrice cm~\"")
|
||||
.build()
|
||||
listener.sendGroupMessage(message.groupId, msg)
|
||||
message.reply(msg)
|
||||
return
|
||||
}
|
||||
val afterStatus = niuziManager.redeemItem(message.sender.userId, selectedItemPrice)?.length!!
|
||||
val action = selectedItem.redeemInGroup(listener, message, afterStatus)
|
||||
val msg = MessageChain.Builder()
|
||||
.addReply(message.messageId)
|
||||
.addText("兑换成功! 你花费了${selectedItemPrice}cm来兑换奖品, 还剩${afterStatus}cm")
|
||||
.addNewLine()
|
||||
.addText("-------------------")
|
||||
|
||||
@@ -9,7 +9,7 @@ package cn.rtast.fancybot.commands.niuzi
|
||||
|
||||
import cn.rtast.fancybot.annotations.CommandDescription
|
||||
import cn.rtast.fancybot.niuziManager
|
||||
import cn.rtast.fancybot.util.getUserName
|
||||
import cn.rtast.fancybot.util.misc.getUserName
|
||||
import cn.rtast.rob.entity.GroupMessage
|
||||
import cn.rtast.rob.util.BaseCommand
|
||||
import cn.rtast.rob.util.ob.MessageChain
|
||||
|
||||
@@ -8,14 +8,20 @@
|
||||
package cn.rtast.fancybot.commands.parse
|
||||
|
||||
import cn.rtast.fancybot.annotations.CommandDescription
|
||||
import cn.rtast.fancybot.util.makeGif
|
||||
import cn.rtast.fancybot.commands.misc.ShortLinkCommand.Companion.makeShortLink
|
||||
import cn.rtast.fancybot.util.Logger
|
||||
import cn.rtast.fancybot.util.file.getFileType
|
||||
import cn.rtast.fancybot.util.misc.ImageBed
|
||||
import cn.rtast.fancybot.util.misc.makeGif
|
||||
import cn.rtast.fancybot.util.misc.toByteArray
|
||||
import cn.rtast.fancybot.util.str.encodeToBase64
|
||||
import cn.rtast.fancybot.util.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
|
||||
@@ -33,6 +39,7 @@ class AsciiArtCommand : BaseCommand() {
|
||||
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 {
|
||||
@@ -86,48 +93,94 @@ class AsciiArtCommand : BaseCommand() {
|
||||
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) return
|
||||
waitingList.removeIf { it == message.sender.userId }
|
||||
if (message.message.any { it.type == ArrayMessageType.image }
|
||||
|| message.message.any { it.type == ArrayMessageType.mface }) {
|
||||
val url = if (message.message.any { it.type == ArrayMessageType.image })
|
||||
message.message.find { it.type == ArrayMessageType.image }!!.data.file!!
|
||||
else message.message.find { it.type == ArrayMessageType.mface }!!.data.url!!
|
||||
val gifStream = withContext(Dispatchers.IO) { URI(url).toURL().openStream() }
|
||||
val decoder = GifDecoder()
|
||||
try {
|
||||
decoder.read(gifStream)
|
||||
if (decoder.frameCount == 0) {
|
||||
val bufferedImage = withContext(Dispatchers.IO) { ImageIO.read(URI(url).toURL()) }
|
||||
val imageBase64 = bufferedImage.convertToAscii()
|
||||
.saveAsciiArtToImage(bufferedImage.width, bufferedImage.height)
|
||||
.toByteArray().encodeToBase64()
|
||||
val msg = MessageChain.Builder().addImage(imageBase64, true).build()
|
||||
message.reply(msg)
|
||||
} else {
|
||||
println("Making gif ascii art, frames: ${decoder.frameCount}")
|
||||
val frames = (0 until decoder.frameCount).map { decoder.getFrame(it) }
|
||||
val asciiFrames = mutableListOf<BufferedImage>()
|
||||
val width = frames.first().width
|
||||
val height = frames.first().height
|
||||
frames.forEach { asciiFrames.add(it.convertToAscii().saveAsciiArtToImage(width, height)) }
|
||||
val gifBytes = decoder.makeGif(asciiFrames)
|
||||
val gifBase64 = gifBytes.encodeToBase64()
|
||||
message.reply(MessageChain.Builder().addImage(gifBase64, true).build())
|
||||
}
|
||||
} catch (_: Exception) {
|
||||
message.reply("处理GIF失败")
|
||||
}
|
||||
val url = this.getImageUrl(message)
|
||||
val msg = this.generateAsciiArt(url)
|
||||
message.reply(msg)
|
||||
} else {
|
||||
message.reply("回复错误已取消本次操作")
|
||||
}
|
||||
}
|
||||
|
||||
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,201 +0,0 @@
|
||||
/*
|
||||
* Copyright © 2024 RTAkland
|
||||
* Author: RTAkland
|
||||
* Date: 2024/9/6
|
||||
*/
|
||||
|
||||
|
||||
package cn.rtast.fancybot.commands.parse
|
||||
|
||||
import cn.rtast.fancybot.entity.bili.*
|
||||
import cn.rtast.fancybot.util.Http
|
||||
import cn.rtast.fancybot.util.Resources
|
||||
import cn.rtast.fancybot.util.drawCustomImage
|
||||
import cn.rtast.fancybot.util.str.encodeToBase64
|
||||
import cn.rtast.fancybot.util.str.formatNumber
|
||||
import cn.rtast.fancybot.util.str.fromJson
|
||||
import cn.rtast.fancybot.util.str.setTruncate
|
||||
import cn.rtast.fancybot.util.toByteArray
|
||||
import cn.rtast.rob.entity.GroupMessage
|
||||
import cn.rtast.rob.enums.ArrayMessageType
|
||||
import cn.rtast.rob.util.ob.MessageChain
|
||||
import cn.rtast.rob.util.ob.OneBotListener
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import java.awt.Color
|
||||
import java.awt.Font
|
||||
import java.awt.RenderingHints
|
||||
import java.awt.image.BufferedImage
|
||||
import java.net.URI
|
||||
import javax.imageio.ImageIO
|
||||
|
||||
object BVParseCommand {
|
||||
|
||||
private const val VIDEO_STAT_URL = "https://api.bilibili.com/x/web-interface/view"
|
||||
private const val USER_STAT_URL = "https://api.bilibili.com/x/relation/stat"
|
||||
private const val SHORT_URL_API_URL = "https://api.bilibili.com/x/share/click"
|
||||
private const val VIEW_COUNT_URL = "https://api.bilibili.com/x/player/online/total"
|
||||
private const val CANVAS_WIDTH = 1000
|
||||
private const val CANVAS_HEIGHT = 600
|
||||
private val backgroundColor = Color(43, 43, 43)
|
||||
private val upBarColor = Color(102, 102, 102)
|
||||
private val textColor = Color.WHITE
|
||||
private val titleFont = Font("Serif", Font.ITALIC, 40)
|
||||
private val numberFont = Font("Serif", Font.ITALIC, 25)
|
||||
private val twoTwoLogo = ImageIO.read(Resources.loadFromResources("bili/22-coin.png"))
|
||||
private val likeIcon = ImageIO.read(Resources.loadFromResources("bili/like.png"))
|
||||
private val coinIcon = ImageIO.read(Resources.loadFromResources("bili/coin.png"))
|
||||
private val viewIcon = ImageIO.read(Resources.loadFromResources("bili/view.png"))
|
||||
private val shareIcon = ImageIO.read(Resources.loadFromResources("bili/share.png"))
|
||||
private val favoriteIcon = ImageIO.read(Resources.loadFromResources("bili/favorite.png"))
|
||||
private val replyIcon = ImageIO.read(Resources.loadFromResources("bili/reply.png"))
|
||||
|
||||
private val tempOkHttpClient = OkHttpClient()
|
||||
|
||||
private fun generateShortUrl(bvid: String, oid: Long): String {
|
||||
val result = Http.post<ShortUrl>(
|
||||
SHORT_URL_API_URL, mapOf(
|
||||
"buvid" to bvid,
|
||||
"build" to 6114514,
|
||||
"platform" to "unix",
|
||||
"share_channel" to "COPY",
|
||||
"share_mode" to 2,
|
||||
"share_id" to "main.ugc-video-detail.0.0.pv",
|
||||
"oid" to oid
|
||||
)
|
||||
)
|
||||
return result.data.content.split(" ").last()
|
||||
}
|
||||
|
||||
private fun getViewCount(bvid: String, cid: Long): String {
|
||||
return Http.get<ViewCount>(
|
||||
VIEW_COUNT_URL, mapOf(
|
||||
"bvid" to bvid,
|
||||
"cid" to cid
|
||||
)
|
||||
).data.total
|
||||
}
|
||||
|
||||
private fun getVideoStat(bvid: String): VideoStat {
|
||||
return Http.get<VideoStat>(VIDEO_STAT_URL, mapOf("bvid" to bvid))
|
||||
}
|
||||
|
||||
private fun createResponseImage(
|
||||
title: String,
|
||||
author: String,
|
||||
authorFace: String,
|
||||
picUrl: String,
|
||||
reply: String,
|
||||
view: String,
|
||||
coin: String,
|
||||
share: String,
|
||||
like: String,
|
||||
favorite: String,
|
||||
fans: Int,
|
||||
viewCount: String,
|
||||
): String {
|
||||
val coverImage = ImageIO.read(URI(picUrl).toURL())
|
||||
val faceImage = ImageIO.read(URI(authorFace).toURL())
|
||||
val canvas = BufferedImage(CANVAS_WIDTH, CANVAS_HEIGHT, BufferedImage.TYPE_INT_RGB)
|
||||
val g2d = canvas.createGraphics()
|
||||
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON)
|
||||
g2d.color = backgroundColor
|
||||
g2d.fillRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT)
|
||||
// draw rect
|
||||
g2d.color = upBarColor
|
||||
g2d.fillRect(0, 0, CANVAS_WIDTH, 120)
|
||||
g2d.color = textColor
|
||||
// draw numbers
|
||||
g2d.font = numberFont
|
||||
g2d.drawString(like, 110, 200)
|
||||
g2d.drawString(coin, 110, 290)
|
||||
g2d.drawString(view, 110, 380)
|
||||
g2d.drawString(favorite, 300, 380)
|
||||
g2d.drawString(reply, 300, 290)
|
||||
g2d.drawString(share, 300, 200)
|
||||
// draw video title
|
||||
val truncatedText = setTruncate(title, g2d)
|
||||
g2d.font = titleFont
|
||||
g2d.drawString(truncatedText, 20, 70)
|
||||
// draw author name
|
||||
g2d.drawString(author, 40, 580)
|
||||
// draw icons
|
||||
g2d.drawCustomImage(favoriteIcon, 230, 340, 50.0, 50.0, false)
|
||||
g2d.drawCustomImage(replyIcon, 230, 250, 50.0, 50.0, false)
|
||||
g2d.drawCustomImage(shareIcon, 230, 160, 50.0, 50.0, false)
|
||||
g2d.drawCustomImage(viewIcon, 40, 340, 50.0, 50.0, false)
|
||||
g2d.drawCustomImage(coinIcon, 40, 250, 50.0, 50.0, false)
|
||||
g2d.drawCustomImage(likeIcon, 40, 160, 50.0, 50.0, false)
|
||||
// draw 22 logo
|
||||
g2d.drawCustomImage(twoTwoLogo, 800, 450, 90.0, 160.0, false)
|
||||
// draw fans count
|
||||
g2d.font = numberFont
|
||||
g2d.drawString(fans.formatNumber() + "粉丝", 120, 520)
|
||||
// draw viewing count
|
||||
g2d.drawString("${viewCount}人正在观看", 40, 460)
|
||||
// draw author face
|
||||
g2d.drawCustomImage(faceImage, 40, 480, 60.0, 60.0, true)
|
||||
// draw cover image
|
||||
g2d.drawCustomImage(coverImage, 430, 140, 600.0, 300.0, true)
|
||||
g2d.dispose()
|
||||
return canvas.toByteArray().encodeToBase64()
|
||||
}
|
||||
|
||||
fun getShortUrlBVID(shortUrl: String): String {
|
||||
val request = Request.Builder().url(shortUrl.split(" ").last()).build()
|
||||
val redirectedUrl = tempOkHttpClient.newCall(request).execute()
|
||||
redirectedUrl.use {
|
||||
return it.request.url.toString().split("/")[4].split("?").first()
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun parse(listener: OneBotListener, message: GroupMessage) {
|
||||
// parse bilibili video with a link, bvid or b23.tv link
|
||||
val bvid = if (message.rawMessage.startsWith("BV")) {
|
||||
message.rawMessage
|
||||
} else if (message.rawMessage.split(" ").last().startsWith("https://b23.tv/")) {
|
||||
val shortUrl = message.rawMessage.split(" ").last()
|
||||
BVParseCommand.getShortUrlBVID(shortUrl)
|
||||
} else if (message.message.find { it.type == ArrayMessageType.json } != null) {
|
||||
val card = message.message.find { it.type == ArrayMessageType.json }!!
|
||||
.data.data!!.toString().fromJson<CardShare>()
|
||||
val shortUrl = if (card.meta.detail == null) {
|
||||
if (!card.meta.news?.tag!!.contains("哔哩哔哩")) return
|
||||
card.meta.news.jumpUrl
|
||||
} else {
|
||||
if (!card.meta.detail.title.contains("哔哩哔哩")) return
|
||||
card.meta.detail.qqDocUrl
|
||||
}
|
||||
BVParseCommand.getShortUrlBVID(shortUrl)
|
||||
} else {
|
||||
message.rawMessage.split("?").first()
|
||||
.split("/")
|
||||
.last { it.isNotEmpty() && it.isNotBlank() }
|
||||
}
|
||||
val videoInfo = this.getVideoStat(bvid)
|
||||
val viewCount = this.getViewCount(bvid, videoInfo.data.cid)
|
||||
val shortUrl = this.generateShortUrl(bvid, videoInfo.data.aid)
|
||||
val fans = Http.get<UserStat>(USER_STAT_URL, mapOf("vmid" to videoInfo.data.owner.mid)).data.follower
|
||||
val authorFace = videoInfo.data.owner.face
|
||||
val title = videoInfo.data.title
|
||||
val author = videoInfo.data.owner.name
|
||||
val coverPicUrl = videoInfo.data.pic
|
||||
val view = videoInfo.data.stat.view.formatNumber()
|
||||
val share = videoInfo.data.stat.share.formatNumber()
|
||||
val like = videoInfo.data.stat.like.formatNumber()
|
||||
val favorite = videoInfo.data.stat.favorite.formatNumber()
|
||||
val coin = videoInfo.data.stat.coin.formatNumber()
|
||||
val reply = videoInfo.data.stat.reply.formatNumber()
|
||||
val image = this.createResponseImage(
|
||||
title, author, authorFace,
|
||||
coverPicUrl, reply, view, coin,
|
||||
share, like, favorite, fans, viewCount
|
||||
)
|
||||
val msg = MessageChain.Builder()
|
||||
.addReply(message.messageId)
|
||||
.addImage(image, true)
|
||||
.addText(shortUrl)
|
||||
.build()
|
||||
listener.sendGroupMessage(message.groupId, msg)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,291 @@
|
||||
/*
|
||||
* Copyright © 2024 RTAkland
|
||||
* Author: RTAkland
|
||||
* Date: 2024/9/6
|
||||
*/
|
||||
|
||||
|
||||
package cn.rtast.fancybot.commands.parse
|
||||
|
||||
import cn.rtast.fancybot.entity.bili.*
|
||||
import cn.rtast.fancybot.util.Http
|
||||
import cn.rtast.fancybot.util.misc.Resources
|
||||
import cn.rtast.fancybot.util.misc.drawCustomImage
|
||||
import cn.rtast.fancybot.util.misc.drawString
|
||||
import cn.rtast.fancybot.util.misc.toBufferedImage
|
||||
import cn.rtast.fancybot.util.misc.toByteArray
|
||||
import cn.rtast.fancybot.util.misc.toURL
|
||||
import cn.rtast.fancybot.util.str.encodeToBase64
|
||||
import cn.rtast.fancybot.util.str.formatNumber
|
||||
import cn.rtast.fancybot.util.str.fromJson
|
||||
import cn.rtast.fancybot.util.str.setTruncate
|
||||
import cn.rtast.rob.entity.GroupMessage
|
||||
import cn.rtast.rob.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()
|
||||
listener.sendGroupMessage(message.groupId, 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()
|
||||
listener.sendGroupMessage(message.groupId, 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.setTruncate
|
||||
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 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}")
|
||||
)
|
||||
}
|
||||
|
||||
@@ -110,24 +165,26 @@ object GitHubParseCommand {
|
||||
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().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)
|
||||
val msg = MessageChain.Builder()
|
||||
.addReply(message.messageId)
|
||||
.addImage(image, true)
|
||||
.build()
|
||||
listener.sendGroupMessage(message.groupId, msg)
|
||||
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 {
|
||||
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!!)
|
||||
} else {
|
||||
msg.addText(image.data.file!!)
|
||||
|
||||
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 images = getImageUrl(message)
|
||||
if (images.isEmpty()) {
|
||||
groupMessage.reply("这个消息里没有图片呢!")
|
||||
} else {
|
||||
if (images.size == 1) {
|
||||
val msg = MessageChain.Builder()
|
||||
.addText(images.first().makeShortLink())
|
||||
.addNewLine(2)
|
||||
.addText(images.first())
|
||||
.build()
|
||||
groupMessage.reply(msg)
|
||||
} else {
|
||||
val messages = mutableListOf<MessageChain>()
|
||||
images.forEach {
|
||||
val msg = MessageChain.Builder()
|
||||
.addText(it)
|
||||
.addNewLine(2)
|
||||
.build()
|
||||
messages.add(msg)
|
||||
}
|
||||
groupMessage.reply(messages.asNode(configManager.selfId))
|
||||
}
|
||||
}
|
||||
groupMessage.reply(msg.build())
|
||||
}
|
||||
}
|
||||
@@ -8,8 +8,9 @@
|
||||
package cn.rtast.fancybot.commands.parse
|
||||
|
||||
import cn.rtast.fancybot.annotations.CommandDescription
|
||||
import cn.rtast.fancybot.util.makeGif
|
||||
import cn.rtast.fancybot.util.misc.makeGif
|
||||
import cn.rtast.fancybot.util.str.encodeToBase64
|
||||
import cn.rtast.rob.entity.GetMessage
|
||||
import cn.rtast.rob.entity.GroupMessage
|
||||
import cn.rtast.rob.enums.ArrayMessageType
|
||||
import cn.rtast.rob.util.BaseCommand
|
||||
@@ -34,6 +35,20 @@ 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 }
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* Copyright © 2024 RTAkland
|
||||
* Author: RTAkland
|
||||
* Date: 2024/10/2
|
||||
*/
|
||||
|
||||
|
||||
package cn.rtast.fancybot.db
|
||||
|
||||
import org.jetbrains.exposed.sql.Table
|
||||
|
||||
object ActionTable : Table("action") {
|
||||
val id = integer("id").autoIncrement()
|
||||
val action = varchar("action", 255)
|
||||
val timestamp = long("timestamp")
|
||||
val triggerId = long("trigger_id")
|
||||
val result = text("result")
|
||||
override val primaryKey = PrimaryKey(id)
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Copyright © 2024 RTAkland
|
||||
* Author: RTAkland
|
||||
* Date: 2024/10/11
|
||||
*/
|
||||
|
||||
|
||||
package cn.rtast.fancybot.db
|
||||
|
||||
import org.jetbrains.exposed.sql.Table
|
||||
|
||||
object BlackListTable : Table("blacklist") {
|
||||
val id = integer("id").autoIncrement()
|
||||
val groupId = long("group_id")
|
||||
val operator = long("operator")
|
||||
val timestamp = long("timestamp")
|
||||
override val primaryKey = PrimaryKey(id)
|
||||
}
|
||||
|
||||
data class BlackListEntity(
|
||||
val groupId: Long,
|
||||
val operator: Long,
|
||||
val timestamp: Long,
|
||||
)
|
||||
+1
-1
@@ -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 }
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -5,7 +5,7 @@
|
||||
*/
|
||||
|
||||
|
||||
package cn.rtast.fancybot.entity.db
|
||||
package cn.rtast.fancybot.db
|
||||
|
||||
import org.jetbrains.exposed.sql.Table
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@
|
||||
*/
|
||||
|
||||
|
||||
package cn.rtast.fancybot.entity.db
|
||||
package cn.rtast.fancybot.db
|
||||
|
||||
import org.jetbrains.exposed.sql.Table
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright © 2024 RTAkland
|
||||
* Author: RTAkland
|
||||
* Date: 2024/10/13
|
||||
*/
|
||||
|
||||
|
||||
package cn.rtast.fancybot.db
|
||||
|
||||
import org.jetbrains.exposed.sql.Table
|
||||
|
||||
object RCONTable : Table("rcon") {
|
||||
val id = integer("id").autoIncrement()
|
||||
val host = varchar("host", 50)
|
||||
val port = integer("port")
|
||||
val userId = long("user_id")
|
||||
val password = varchar("password", 128)
|
||||
val name = varchar("name", 50)
|
||||
override val primaryKey = PrimaryKey(id)
|
||||
}
|
||||
|
||||
data class RCONEntity(
|
||||
val host: String,
|
||||
val port: Int,
|
||||
val userId: Long,
|
||||
val name: String,
|
||||
val password: String
|
||||
)
|
||||
|
||||
@@ -7,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,
|
||||
@@ -33,6 +36,28 @@ data class Config(
|
||||
val llamaUrl: String,
|
||||
val llamaModel: String,
|
||||
val qqMusicApiUrl: String,
|
||||
val startUpNoticeUser: Long,
|
||||
val selfId: Long
|
||||
)
|
||||
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,
|
||||
)
|
||||
@@ -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,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,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,
|
||||
)
|
||||
}
|
||||
@@ -16,7 +16,7 @@ data class RepoInfo(
|
||||
val description: String?,
|
||||
@SerializedName("forks_count")
|
||||
val forksCount: Int,
|
||||
val language: String,
|
||||
val language: String?,
|
||||
@SerializedName("stargazers_count")
|
||||
val starsCount: Int,
|
||||
@SerializedName("open_issues_count")
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
/*
|
||||
* Copyright © 2024 RTAkland
|
||||
* Author: RTAkland
|
||||
* Date: 2024/10/8
|
||||
*/
|
||||
|
||||
|
||||
package cn.rtast.fancybot.entity.github
|
||||
|
||||
data class UploadContentPayload(
|
||||
val message: String,
|
||||
val content: String,
|
||||
)
|
||||
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* Copyright © 2024 RTAkland
|
||||
* Author: RTAkland
|
||||
* Date: 2024/10/8
|
||||
*/
|
||||
|
||||
|
||||
package cn.rtast.fancybot.entity.github
|
||||
|
||||
data class UploadContentResponse(
|
||||
val content: Content
|
||||
) {
|
||||
data class Content(
|
||||
val name: String,
|
||||
)
|
||||
}
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
package cn.rtast.fancybot.entity.gpt
|
||||
|
||||
import cn.rtast.fancybot.entity.enums.GPTUserRole
|
||||
import cn.rtast.fancybot.enums.GPTUserRole
|
||||
|
||||
data class ChatCompletionsPayload(
|
||||
val model: String,
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
package cn.rtast.fancybot.entity.gpt
|
||||
|
||||
import cn.rtast.fancybot.entity.enums.GPTUserRole
|
||||
import cn.rtast.fancybot.enums.GPTUserRole
|
||||
|
||||
data class ChatCompletionsResponse(
|
||||
val choices: List<Choice>
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright © 2024 RTAkland
|
||||
* Author: RTAkland
|
||||
* Date: 2024/10/9
|
||||
*/
|
||||
|
||||
|
||||
package cn.rtast.fancybot.entity.mc
|
||||
|
||||
import cn.rtast.fancybot.enums.mc.MCVersionType
|
||||
|
||||
data class MCVersion(
|
||||
val latest: Latest,
|
||||
val versions: List<Version>
|
||||
) {
|
||||
data class Latest(
|
||||
val release: String,
|
||||
val snapshot: String
|
||||
)
|
||||
|
||||
data class Version(
|
||||
val id: String,
|
||||
val type: MCVersionType,
|
||||
val releaseTime: String
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright © 2024 RTAkland
|
||||
* Author: RTAkland
|
||||
* Date: 2024/10/10
|
||||
*/
|
||||
|
||||
|
||||
package cn.rtast.fancybot.entity.mc
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class MCWikiPageResponse(
|
||||
@SerializedName("redirect_target")
|
||||
val redirectTarget: String?,
|
||||
val httpCode: Int?,
|
||||
val id: Int,
|
||||
val title: String,
|
||||
val latest: Latest,
|
||||
val source: String,
|
||||
val license: License
|
||||
) {
|
||||
data class Latest(
|
||||
val id: Int,
|
||||
val timestamp: String,
|
||||
)
|
||||
|
||||
data class License(
|
||||
val url: String,
|
||||
val title: String,
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Copyright © 2024 RTAkland
|
||||
* Author: RTAkland
|
||||
* Date: 2024/10/9
|
||||
*/
|
||||
|
||||
|
||||
package cn.rtast.fancybot.entity.mc
|
||||
|
||||
data class MCWikiSearchResponse(
|
||||
val pages: List<Page>
|
||||
) {
|
||||
data class Page(
|
||||
val id: Int,
|
||||
val title: String,
|
||||
val thumbnail: Thumbnail?
|
||||
)
|
||||
|
||||
data class Thumbnail(
|
||||
val url: String
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright © 2024 RTAkland
|
||||
* Author: RTAkland
|
||||
* Date: 2024/10/2
|
||||
*/
|
||||
|
||||
|
||||
package cn.rtast.fancybot.entity.mc
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class Skin(
|
||||
val properties: List<Property>,
|
||||
) {
|
||||
data class Property(
|
||||
val value: String,
|
||||
)
|
||||
}
|
||||
|
||||
data class DecodedSkin(
|
||||
val textures: Texture,
|
||||
) {
|
||||
data class Texture(
|
||||
@SerializedName("SKIN")
|
||||
val skin: SKIN,
|
||||
)
|
||||
|
||||
data class SKIN(
|
||||
val url: String,
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
/*
|
||||
* Copyright © 2024 RTAkland
|
||||
* Author: RTAkland
|
||||
* Date: 2024/10/2
|
||||
*/
|
||||
|
||||
|
||||
package cn.rtast.fancybot.entity.mc
|
||||
|
||||
data class Username(val id: String)
|
||||
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* Copyright © 2024 RTAkland
|
||||
* Author: RTAkland
|
||||
* Date: 2024/10/11
|
||||
*/
|
||||
|
||||
|
||||
package cn.rtast.fancybot.entity.mc.oauth
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class DeviceCodeResponse(
|
||||
@SerializedName("user_code")
|
||||
val userCode: String,
|
||||
@SerializedName("device_code")
|
||||
val deviceCode: String,
|
||||
@SerializedName("verification_uri")
|
||||
val verificationUri: String
|
||||
)
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright © 2024 RTAkland
|
||||
* Author: RTAkland
|
||||
* Date: 2024/10/11
|
||||
*/
|
||||
|
||||
|
||||
package cn.rtast.fancybot.entity.mc.oauth
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class LoginXboxLivePayload(
|
||||
@SerializedName("Properties")
|
||||
val properties: Properties,
|
||||
@SerializedName("RelyingParty")
|
||||
val relyingParty: String = "http://auth.xboxlive.com",
|
||||
@SerializedName("TokenType")
|
||||
val tokenType: String = "JWT",
|
||||
) {
|
||||
data class Properties(
|
||||
@SerializedName("RpsTicket")
|
||||
val rpsTicket: String,
|
||||
@SerializedName("AuthMethod")
|
||||
val authMethod: String = "RPS",
|
||||
@SerializedName("SiteName")
|
||||
val siteName: String = "user.auth.xboxlive.com",
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright © 2024 RTAkland
|
||||
* Author: RTAkland
|
||||
* Date: 2024/10/11
|
||||
*/
|
||||
|
||||
|
||||
package cn.rtast.fancybot.entity.mc.oauth
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class LoginXboxLiveResponse(
|
||||
@SerializedName("Token")
|
||||
val token: String,
|
||||
@SerializedName("DisplayClaims")
|
||||
val displayClaims: DisplayClaims,
|
||||
) {
|
||||
data class DisplayClaims(
|
||||
val xui: List<XUI>
|
||||
)
|
||||
|
||||
data class XUI(
|
||||
val uhs: String
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright © 2024 RTAkland
|
||||
* Author: RTAkland
|
||||
* Date: 2024/10/11
|
||||
*/
|
||||
|
||||
|
||||
package cn.rtast.fancybot.entity.mc.oauth
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class ObtainXSTSPayload(
|
||||
@SerializedName("Properties")
|
||||
val properties: Properties,
|
||||
@SerializedName("RelyingParty")
|
||||
val relyingParty: String = "rp://api.minecraftservices.com/",
|
||||
@SerializedName("TokenType")
|
||||
val tokenType: String = "JWT",
|
||||
) {
|
||||
data class Properties(
|
||||
@SerializedName("UserTokens")
|
||||
val userTokens: List<String>,
|
||||
@SerializedName("SandboxId")
|
||||
val sandboxId: String = "RETAIL",
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* Copyright © 2024 RTAkland
|
||||
* Author: RTAkland
|
||||
* Date: 2024/10/11
|
||||
*/
|
||||
|
||||
|
||||
package cn.rtast.fancybot.entity.mc.oauth
|
||||
|
||||
import cn.rtast.fancybot.entity.mc.oauth.LoginXboxLiveResponse.DisplayClaims
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class ObtainXSTSResponse(
|
||||
@SerializedName("Token")
|
||||
val token: String,
|
||||
@SerializedName("DisplayClaims")
|
||||
val displayClaims: DisplayClaims,
|
||||
)
|
||||
@@ -0,0 +1,15 @@
|
||||
/*
|
||||
* Copyright © 2024 RTAkland
|
||||
* Author: RTAkland
|
||||
* Date: 2024/10/11
|
||||
*/
|
||||
|
||||
|
||||
package cn.rtast.fancybot.entity.mc.oauth
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class RedeemCodeResponse(
|
||||
@SerializedName("access_token")
|
||||
val accessToken: String,
|
||||
)
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright © 2024 RTAkland
|
||||
* Author: RTAkland
|
||||
* Date: 2024/10/3
|
||||
*/
|
||||
|
||||
|
||||
package cn.rtast.fancybot.entity.music
|
||||
|
||||
data class TopSong(
|
||||
val data: List<Data>
|
||||
) {
|
||||
data class Data(
|
||||
val privilege: Privilege
|
||||
)
|
||||
|
||||
data class Privilege(
|
||||
val id: Long
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
/*
|
||||
* Copyright © 2024 RTAkland
|
||||
* Author: RTAkland
|
||||
* Date: 2024/10/14
|
||||
*/
|
||||
|
||||
|
||||
package cn.rtast.fancybot.entity.pastebin
|
||||
|
||||
data class PastebinPayload(val content: String)
|
||||
|
||||
data class PastebinResponse(val id: String)
|
||||
+4
-9
@@ -1,22 +1,17 @@
|
||||
/*
|
||||
* Copyright © 2024 RTAkland
|
||||
* Author: RTAkland
|
||||
* Date: 2024/9/29
|
||||
* Date: 2024/10/6
|
||||
*/
|
||||
|
||||
|
||||
package cn.rtast.fancybot.entity.setu
|
||||
|
||||
data class SetuV2(
|
||||
data class SetuV3(
|
||||
val pid: Long,
|
||||
val uid: Long,
|
||||
val title: String,
|
||||
val author: String,
|
||||
val urls: URLS,
|
||||
val url: String,
|
||||
val r18: Boolean,
|
||||
val tags: List<String>
|
||||
) {
|
||||
data class URLS(
|
||||
val regular: String,
|
||||
)
|
||||
}
|
||||
)
|
||||
@@ -0,0 +1,12 @@
|
||||
/*
|
||||
* Copyright © 2024 RTAkland
|
||||
* Author: RTAkland
|
||||
* Date: 2024/10/8
|
||||
*/
|
||||
|
||||
|
||||
package cn.rtast.fancybot.entity.shortlink
|
||||
|
||||
data class ShortLinkPayload(
|
||||
val target: String
|
||||
)
|
||||
@@ -0,0 +1,12 @@
|
||||
/*
|
||||
* Copyright © 2024 RTAkland
|
||||
* Author: RTAkland
|
||||
* Date: 2024/10/8
|
||||
*/
|
||||
|
||||
|
||||
package cn.rtast.fancybot.entity.shortlink
|
||||
|
||||
data class ShortLinkResponse(
|
||||
val id: String
|
||||
)
|
||||
@@ -0,0 +1,12 @@
|
||||
/*
|
||||
* Copyright © 2024 RTAkland
|
||||
* Author: RTAkland
|
||||
* Date: 2024/10/8
|
||||
*/
|
||||
|
||||
|
||||
package cn.rtast.fancybot.entity.thecat
|
||||
|
||||
data class Cat(
|
||||
val url: String,
|
||||
)
|
||||
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Copyright © 2024 RTAkland
|
||||
* Author: RTAkland
|
||||
* Date: 2024/10/8
|
||||
*/
|
||||
|
||||
|
||||
package cn.rtast.fancybot.entity.tianxing
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class HistoryOfToday(
|
||||
val result: Result
|
||||
) {
|
||||
data class Result(
|
||||
val list: List<Element>
|
||||
)
|
||||
|
||||
data class Element(
|
||||
val title: String,
|
||||
@SerializedName("lsdate")
|
||||
val lsDate: String
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
/*
|
||||
* Copyright © 2024 RTAkland
|
||||
* Author: RTAkland
|
||||
* Date: 2024/10/7
|
||||
*/
|
||||
|
||||
|
||||
package cn.rtast.fancybot.entity.tts
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class TTSResponse(
|
||||
@SerializedName("URL")
|
||||
val url: String
|
||||
)
|
||||
Loaded 100 of 261 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in New Issue
Block a user