Author SHA1 Message Date
RTAkland dff2c549dd feat: add stock data api 2024-10-01 11:50:35 +08:00
263 changed files with 754 additions and 4454 deletions

No files matched your search

+5 -19
View File
@@ -15,14 +15,13 @@
# 概述 # 概述
> 这是一个使用了 `ROneBot` 框架的QQ机器人, 包含了超多命令, 例如: 点歌, 今日人品, 签到... > 这是一个使用了 `ROneBot` 框架的QQ机器人, 包含了20多种命令, 例如: 点歌, 今日人品, 签到...
> 你可以在[这里](src/main/kotlin/cn/rtast/fancybot/commands/)来查看所有的命令 > 你可以在[这里](src/main/kotlin/cn/rtast/fancybot/commands/)来查看所有的命令
> ROneBot项目地址: [Gitlab](https://repo.rtast.cn/RTAkland/ronebot) [Github](https://github.com/RTAkland/ROneBot) > ROneBot项目地址: [Gitlab](https://repo.rtast.cn/RTAkland/ronebot) [Github](https://github.com/RTAkland/ROneBot)
# 构建 # 构建
由于项目使用的依赖过多项目构建出的产物在28Mb左右并且项目开启了
由于项目使用的依赖过多项目构建出的产物在40+Mb左右并且项目开启了
Github Action来自动化部署机器人, github Action的作用仅用作判断 Github Action来自动化部署机器人, github Action的作用仅用作判断
构建是否成功以及自动部署机器人, 并不带有上传构建产物的功能(占用的空间太多) 构建是否成功以及自动部署机器人, 并不带有上传构建产物的功能(占用的空间太多)
所以你需要自行构建出机器人的jar包来运行 所以你需要自行构建出机器人的jar包来运行
@@ -34,23 +33,10 @@ $ ./gradlew build clean
> 产物在`build/libs/`目录下你可以找到`-all`结尾的jar包 > 产物在`build/libs/`目录下你可以找到`-all`结尾的jar包
# 部署 # 一些想说的话
> 部署并不是一件很难的事, 只是需要准备的前期工作有点多 OneBot协议本身并不是一个很好的协议, 在Kritor、Laana等新型协议完善后机器人会全面迁移到上述协议中
并且将OneBot版本的分支放置到另外一个分支中不再更新, 后续将主要更新Kritor/Laana的分支
1. 申请一个Github Classic Token (需要`repo`的所有权限)(如果你想使用github作为图床)
2. 申请一个Cloudflare Token (需要`R2`的编辑权限)(如果你想使用cf作为图床)
3. 获取Cloudflare的Account Id以及R2的ID
4. 一个天聚数行API(TianAPI),并开通历史上的今天API权限
5. 一个openai平台的密钥和模型以及镜像地址
6. 和风天气的API key(免费订阅)
7. smtp服务器和端口
8. 网易云音乐API
9. QQ音乐API
10. 一个Azure APP (用于获取MC AccessToken)
11. 一个RTAST api token (用于生短链接, 你可以自行搭建但是源代码不公开)
12. 一个APISpace key
13. llama的api地址
# 开源 # 开源
+15 -25
View File
@@ -4,7 +4,6 @@ import org.jetbrains.kotlin.gradle.dsl.JvmTarget
plugins { plugins {
alias(libs.plugins.kotlin) alias(libs.plugins.kotlin)
alias(libs.plugins.shadow) alias(libs.plugins.shadow)
id("application")
} }
val fancyBotVersion: String by project val fancyBotVersion: String by project
@@ -15,12 +14,8 @@ version = fancyBotVersion
repositories { repositories {
mavenCentral() mavenCentral()
mavenLocal() 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/33/packages/maven")
maven("https://repo.rtast.cn/api/v4/projects/19/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 { tasks.compileKotlin {
@@ -34,26 +29,17 @@ tasks.compileJava {
dependencies { dependencies {
implementation(libs.ronebot) implementation(libs.rOneBot)
implementation(libs.okhttp) implementation(libs.okhttp)
implementation(libs.exposed.core) implementation(libs.exposedCore)
implementation(libs.exposed.jdbc) implementation(libs.exposedJDBC)
implementation(libs.exposed.dao) implementation(libs.sqliteJDBC)
implementation(libs.sqlite.jdbc) implementation(libs.motdPinger)
implementation(libs.mysql.jdbc)
implementation(libs.postgressql.jdbc)
implementation(libs.motd.pinger)
implementation(libs.animated.gif.lib) implementation(libs.animated.gif.lib)
implementation(libs.simple.java.mail) implementation(libs.simple.java.mail)
implementation(libs.jsoup) implementation(libs.jsoup)
implementation(libs.nashorn.core) implementation(libs.nashorn.core)
implementation(libs.zxing.core) implementation(libs.finnhub)
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 { tasks.build {
@@ -64,6 +50,15 @@ tasks.jar {
enabled = false enabled = false
} }
tasks.shadowJar {
manifest {
attributes(
"Main-Class" to "cn.rtast.fancybot.FancyBotKt",
"Manifest-Version" to "1.0"
)
}
}
tasks.register("deployBot") { tasks.register("deployBot") {
val apiKey = System.getenv("MCSM_API_KEY") val apiKey = System.getenv("MCSM_API_KEY")
val apiUrl = System.getenv("MCSM_API_URL") val apiUrl = System.getenv("MCSM_API_URL")
@@ -71,8 +66,3 @@ tasks.register("deployBot") {
val instanceId = System.getenv("MCSM_INSTANCE_ID") val instanceId = System.getenv("MCSM_INSTANCE_ID")
MCSMDeploy(apiUrl, apiKey, daemonId, instanceId).deploy(file("build/libs/FancyBot-1.0.0-all.jar")) MCSMDeploy(apiUrl, apiKey, daemonId, instanceId).deploy(file("build/libs/FancyBot-1.0.0-all.jar"))
} }
application {
mainClass = "cn.rtast.fancybot.FancyBotKt"
applicationName = "FancyBot"
}
+11 -27
View File
@@ -1,45 +1,29 @@
[versions] [versions]
kotlinVersion = "2.0.20" kotlinVersion = "2.0.10"
ronebotVersion = "1.14.1" ronebotVersion = "1.11.8"
shadowVersion = "8.3.0" shadowVersion = "8.3.0"
okhttpVersion = "5.0.0-alpha.14" okhttpVersion = "5.0.0-alpha.14"
exposedVersion = "0.53.0" exposedVersion = "0.53.0"
sqliteJDBCVersion = "3.46.1.0" sqliteJDBCVersion = "3.46.1.0"
motdPingerVersion = "1.1.4" motdPingerVersion = "1.1.2"
animatedGifLibVersion = "1.4" animatedGifLibVersion = "1.4"
simpleJavaMailVersion = "8.12.2" simpleJavaMailVersion = "8.11.3"
jsoupVersion = "1.18.1" jsoupVersion = "1.18.1"
nashornVersion = "15.4" nashornVersion = "15.4"
zxingVersion = "3.5.3" finnhubVersion = "2.0.20"
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] [libraries]
ronebot = { group = "cn.rtast", name = "ROneBot", version.ref = "ronebotVersion" } rOneBot = { group = "cn.rtast", name = "ROneBot", version.ref = "ronebotVersion" }
motd-pinger = { group = "cn.rtast", name = "MotdPinger", version.ref = "motdPingerVersion" } motdPinger = { group = "cn.rtast", name = "MotdPinger", version.ref = "motdPingerVersion" }
okhttp = { group = "com.squareup.okhttp3", name = "okhttp", version.ref = "okhttpVersion" } okhttp = { group = "com.squareup.okhttp3", name = "okhttp", version.ref = "okhttpVersion" }
exposed-core = { group = "org.jetbrains.exposed", name = "exposed-core", version.ref = "exposedVersion" } exposedCore = { group = "org.jetbrains.exposed", name = "exposed-core", version.ref = "exposedVersion" }
exposed-jdbc = { group = "org.jetbrains.exposed", name = "exposed-jdbc", version.ref = "exposedVersion" } exposedJDBC = { group = "org.jetbrains.exposed", name = "exposed-jdbc", version.ref = "exposedVersion" }
exposed-dao = { group = "org.jetbrains.exposed", name = "exposed-dao", version.ref = "exposedVersion" } sqliteJDBC = { group = "org.xerial", name = "sqlite-jdbc", version.ref = "sqliteJDBCVersion" }
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" } animated-gif-lib = { group = "com.madgag", name = "animated-gif-lib", version.ref = "animatedGifLibVersion" }
simple-java-mail = { group = "org.simplejavamail", name = "simple-java-mail", version.ref = "simpleJavaMailVersion" } simple-java-mail = { group = "org.simplejavamail", name = "simple-java-mail", version.ref = "simpleJavaMailVersion" }
jsoup = { group = "org.jsoup", name = "jsoup", version.ref = "jsoupVersion" } jsoup = { group = "org.jsoup", name = "jsoup", version.ref = "jsoupVersion" }
nashorn-core = { group = "org.openjdk.nashorn", name = "nashorn-core", version.ref = "nashornVersion" } nashorn-core = { group = "org.openjdk.nashorn", name = "nashorn-core", version.ref = "nashornVersion" }
zxing-core = { group = "com.google.zxing", name = "core", version.ref = "zxingVersion" } finnhub = { group = "io.finnhub", name = "kotlin-client", version.ref = "finnhubVersion" }
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] [bundles]
+34 -69
View File
@@ -4,37 +4,29 @@
* Date: 2024/8/27 * Date: 2024/8/27
*/ */
@file:Suppress("DEPRECATION")
package cn.rtast.fancybot package cn.rtast.fancybot
import cn.rtast.fancybot.commands.AboutCommand import cn.rtast.fancybot.commands.AboutCommand
import cn.rtast.fancybot.commands.EchoCommand import cn.rtast.fancybot.commands.EchoCommand
import cn.rtast.fancybot.commands.HelpCommand import cn.rtast.fancybot.commands.HelpCommand
import cn.rtast.fancybot.commands.ShuffleEchoCommand
import cn.rtast.fancybot.commands.StatusCommand import cn.rtast.fancybot.commands.StatusCommand
import cn.rtast.fancybot.commands.SupportMeCommand
import cn.rtast.fancybot.commands.lookup.* import cn.rtast.fancybot.commands.lookup.*
import cn.rtast.fancybot.commands.misc.* import cn.rtast.fancybot.commands.misc.*
import cn.rtast.fancybot.commands.misc.JrrpCommand
import cn.rtast.fancybot.commands.misc.ShortLinkCommand
import cn.rtast.fancybot.commands.niuzi.* 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.AsciiArtCommand
import cn.rtast.fancybot.commands.parse.ReverseGIFCommand import cn.rtast.fancybot.commands.parse.ReverseGIFCommand
import cn.rtast.fancybot.commands.misc.JrrpCommand
import cn.rtast.fancybot.commands.niuzi.NiuziRedeemCommand
import cn.rtast.fancybot.commands.misc.ShortLinkCommand
import cn.rtast.fancybot.entity.Config import cn.rtast.fancybot.entity.Config
import cn.rtast.fancybot.enums.DatabaseType import cn.rtast.fancybot.entity.enums.ImageType
import cn.rtast.fancybot.enums.ImageBedType import cn.rtast.fancybot.entity.enums.WSType
import cn.rtast.fancybot.enums.ImageType
import cn.rtast.fancybot.enums.WSType
import cn.rtast.fancybot.items.* import cn.rtast.fancybot.items.*
import cn.rtast.fancybot.util.file.BlackListManager
import cn.rtast.fancybot.util.file.ConfigManager import cn.rtast.fancybot.util.file.ConfigManager
import cn.rtast.fancybot.util.file.NiuziBankManager import cn.rtast.fancybot.util.file.NiuziBankManager
import cn.rtast.fancybot.util.file.NiuziManager import cn.rtast.fancybot.util.file.NiuziManager
import cn.rtast.fancybot.util.file.RCONManager
import cn.rtast.fancybot.util.item.ItemManager import cn.rtast.fancybot.util.item.ItemManager
import cn.rtast.rob.ROneBotFactory
import com.google.gson.Gson import com.google.gson.Gson
import com.google.gson.GsonBuilder import com.google.gson.GsonBuilder
import java.time.Instant import java.time.Instant
@@ -45,19 +37,8 @@ val gson: Gson = GsonBuilder()
.create() .create()
const val ROOT_PATH = "./data" 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( val DEFAULT_CONFIG = Config(
database = Config.Database(
DatabaseType.SQLite,
"127.0.0.1",
3306,
"root",
"114514",
"fancybot"
),
ncmAPI = "https://ncm.rtast.cn", ncmAPI = "https://ncm.rtast.cn",
wsAddress = "ws://127.0.0.1", wsAddress = "ws://127.0.0.1",
wsType = WSType.Client, wsType = WSType.Client,
@@ -80,29 +61,14 @@ val DEFAULT_CONFIG = Config(
llamaUrl = "http://127.0.0.1", llamaUrl = "http://127.0.0.1",
llamaModel = "llama3.1", llamaModel = "llama3.1",
qqMusicApiUrl = "http://127.0.0.1:3200", qqMusicApiUrl = "http://127.0.0.1:3200",
noticeUser = 114514L, startUpNoticeUser = 114514L,
selfId = 114514L, selfId = 114514L
tianXingApiKey = "1145141919810",
githubUser = "RTAkland",
githubImageRepo = "Static-Images",
imageBedType = ImageBedType.Github,
cloudflareAccountId = "111",
cloudflareR2AccessKeyId = "11111",
cloudflareR2SecretKey = "222",
cloudflareR2BucketName = "fancybot",
cloudflareR2PublicUrl = "https://114514.com",
apiSpaceKey = "114514",
apiRtastKey = "114514",
azureAppClientId = "114514",
youtubeDataApiKey = "114514"
) )
val configManager = ConfigManager() val configManager = ConfigManager()
val itemManager = ItemManager() val itemManager = ItemManager()
val niuziManager = NiuziManager() val niuziManager = NiuziManager()
val niuziBankManager = NiuziBankManager() val niuziBankManager = NiuziBankManager()
val blackListManager = BlackListManager()
val rconManager = RCONManager()
val START_UP_TIME = Instant.now().epochSecond val START_UP_TIME = Instant.now().epochSecond
@@ -112,33 +78,32 @@ val items = listOf(
TenSetuItem(), TenSetuR18Item() TenSetuItem(), TenSetuR18Item()
) )
val tasks = mapOf(
10000000L to suspend {
configManager.admins.forEach {
ROneBotFactory.action.sendLike(it, 1)
}
}
)
val commands = listOf( val commands = listOf(
EchoCommand(), JrrpCommand(), NiuziRedeemCommand(), HitokotoCommand(), EchoCommand(), JrrpCommand(),
KFCCommand(), GenerateQRCodeCommand(), AntiRevokeCommand(), MCPingCommand(), NiuziRedeemCommand(), HitokotoCommand(),
WeatherCommand(), CigaretteCommand(), RemakeCommand(), PixivCommand(), KFCCommand(), QRCodeCommand(),
RUACommand(), NslookupCommand(), CompilerCommand(), AICommand(), AntiRevokeCommand(), MCPingCommand(),
GithubUserCommand(), AboutCommand(), MusicPlayUrlCommand(), DomainPriceCommand(), WeatherCommand(), CigaretteCommand(),
SendMailCommand(), ZiBiCommand(), UnsetZiBiCommand(), WikipediaCommand(), RemakeCommand(), PixivCommand(),
AsciiArtCommand(), StatusCommand(), JueCommand(), ShortLinkCommand(), RUACommand(), NslookupCommand(),
ShotOtherCommand(), ReverseGIFCommand(), HelpCommand(), MusicCommand(), CompilerCommand(), AICommand(),
SendLikeCommand(), NiuziTransferCommand(), NiuziSignCommand(), NiuziJiJianCommand(), GithubUserCommand(), AboutCommand(),
NiuziQueryCommand(), MyNiuziCommand(), NiuziBankCommand(), WithdrawCommand(), MusicPlayUrlCommand(), DomainPriceCommand(),
BankTransferCommand(), CreateBankAccountCommand(), BankBalanceCommand(), DepositCommand(), SendMailCommand(), ZiBiCommand(),
AnimeSearchCommand(), LlamaCommand(), QQMusicCommand(), BullShitGenerateCommand(), UnsetZiBiCommand(), WikipediaCommand(),
NiuziRankCommand(), NiuziBankRankCommand(), ShotSelfCommand(), TenSetuCommand(), AsciiArtCommand(), StatusCommand(),
ReactionCommand(), MCSkinCommand(), GithubLatestCommitCommand(), RandomMusicCommand(), JueCommand(), ShortLinkCommand(),
ZDJDCommand(), ScanQRCodeCommand(), TTSCommand(), SlimeChunkHelperCommand(), ShotOtherCommand(), ReverseGIFCommand(),
SupportMeCommand(), TheCatCommand(), TheHistoryOfTodayCommand(), MCVersionCommand(), HelpCommand(), MusicCommand(),
MinecraftWikiCommand(), GaoKaoDaysRemainCommand(), IdiomExplainCommand(), NiuziManagerCommand(), LikeMeCommand(), NiuziTransferCommand(),
TodayEatCommand(), TodayDrinkCommand(), MCLoginCommand(), RCONCommand(), NiuziSignCommand(), NiuziJiJianCommand(),
GravatarCommand(), PastebinCommand(), ShuffleEchoCommand(), TodayDontEatCommand(), NiuziQueryCommand(), MyNiuziCommand(),
TodayDontDrinkCommand(), MCBotCommand(), HTTPCatCommand(), HTTPDogCommand() NiuziBankCommand(), WithdrawCommand(),
BankTransferCommand(), CreateBankAccountCommand(),
BankBalanceCommand(), DepositCommand(),
DMSearchCommand(), LlamaCommand(),
QQMusicCommand(), BullShitGenerateCommand(),
NiuziRankCommand(), NiuziBankRankCommand(),
ShotSelfCommand(), TenSetuCommand(),
StockDataCommand()
) )
+28 -73
View File
@@ -7,26 +7,14 @@
package cn.rtast.fancybot 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.commands.parse.*
import cn.rtast.fancybot.commands.reply.ImageBedCommand import cn.rtast.fancybot.entity.enums.WSType
import cn.rtast.fancybot.enums.WSType import cn.rtast.fancybot.util.initDatabase
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.ROneBotFactory
import cn.rtast.rob.entity.* import cn.rtast.rob.entity.*
import cn.rtast.rob.entity.lagrange.FileEvent import cn.rtast.rob.entity.lagrange.FileEvent
import cn.rtast.rob.entity.lagrange.PokeEvent import cn.rtast.rob.entity.lagrange.PokeEvent
import cn.rtast.rob.enums.ArrayMessageType import cn.rtast.rob.enums.ArrayMessageType
import cn.rtast.rob.enums.QQFace
import cn.rtast.rob.util.ob.MessageChain import cn.rtast.rob.util.ob.MessageChain
import cn.rtast.rob.util.ob.OneBotListener import cn.rtast.rob.util.ob.OneBotListener
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
@@ -37,11 +25,10 @@ import java.net.URI
class FancyBot : OneBotListener { class FancyBot : OneBotListener {
private val logger = Logger.getLogger<FancyBot>()
private val coroutineScope = CoroutineScope(Dispatchers.IO) private val coroutineScope = CoroutineScope(Dispatchers.IO)
override suspend fun onWebsocketOpenEvent() { override suspend fun onWebsocketOpenEvent() {
this.sendPrivateMessage(configManager.noticeUser, "FancyBot启动完成~") this.sendPrivateMessage(configManager.startUpNoticeUser, "FancyBot启动完成~")
} }
override suspend fun onGroupMessage(message: GroupMessage, json: String) { override suspend fun onGroupMessage(message: GroupMessage, json: String) {
@@ -50,72 +37,43 @@ class FancyBot : OneBotListener {
val msg = message.rawMessage val msg = message.rawMessage
val groupId = message.groupId val groupId = message.groupId
val messageId = message.messageId val messageId = message.messageId
logger.info("$sender($senderId: $groupId >>> $messageId): $msg") println("$sender($senderId: $groupId >>> $messageId): $msg")
logger.trace("$sender($senderId: $groupId >>> $messageId: $json")
if (message.message.any { it.type == ArrayMessageType.face && it.data.id.toString() == "419" }) { if (message.message.any { it.type == ArrayMessageType.face && it.data.id.toString() == "419" }) {
message.reply("你发牛魔的火车呢, 我直接就是打断") message.reply("你发牛魔的火车呢, 我直接就是打断")
} }
if (message.rawMessage.contains("原神")) { if (message.rawMessage.contains("原神")) {
(0..10).forEach { _ ->
message.reaction(QQFace.entries.random())
}
message.reply("你原神牛魔呢") message.reply("你原神牛魔呢")
} }
GitHubParseCommand.parse(message)
ReverseGIFCommand.callback(message) ReverseGIFCommand.callback(message)
AsciiArtCommand.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('*', '-', '/', '+', '=') }) { if (message.rawMessage.toList().any { it in arrayListOf('*', '-', '/', '+', '=') }) {
val calculateResult = CalculateCommand.parse(message.rawMessage) val calculateResult = CalculateCommand.parse(message.rawMessage)
calculateResult?.let { message.reply(calculateResult) } calculateResult?.let { message.reply(calculateResult) }
} }
if (message.message.any { it.type == ArrayMessageType.reply }) { if (message.message.any { it.type == ArrayMessageType.reply }) { // Image url parse
val command = message.message.reversed().find { it.type == ArrayMessageType.text }!!.data.text!! val command = message.message.reversed().find { it.type == ArrayMessageType.text }!!.data.text!!
val replyId = message.message.find { it.type == ArrayMessageType.reply }!!.data.id!! 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("图链")) { if (command.contains("图来") || command.contains("图链")) {
// 获取一个或多个图片的链接, 并且生成一个或多个短链接 val getMsg = this.getMessage(replyId.toString().toLong())
ImageURLCommand.callback(message, getMsg) ImageURLCommand.callback(message, getMsg)
} }
if (command.contains("reaction")) {
// 使用reaction刷屏回应一条消息
ReactionCommand.reaction(this, message, getMsg.messageId)
} }
if (command.contains("图床")) {
// 将一个图片上传到图床 if (message.rawMessage.startsWith("https://github.com/") || message.rawMessage.startsWith("git@github.com:")) {
ImageBedCommand.execute(getMsg, message) 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)
} }
coroutineScope.launch { coroutineScope.launch {
@@ -123,7 +81,7 @@ class FancyBot : OneBotListener {
if (it.type == ArrayMessageType.image) { if (it.type == ArrayMessageType.image) {
val filename = it.data.file!!.split("=").last() + ".png" val filename = it.data.file!!.split("=").last() + ".png"
URI(it.data.file!!).toURL().openConnection().inputStream.use { input -> URI(it.data.file!!).toURL().openConnection().inputStream.use { input ->
File("$ROOT_PATH/caches/images/$filename").outputStream().use { output -> File("./files/images/$filename").outputStream().use { output ->
output.write(input.readBytes()) output.write(input.readBytes())
} }
} }
@@ -151,7 +109,7 @@ class FancyBot : OneBotListener {
} }
override suspend fun onGroupFileUpload(event: FileEvent) { override suspend fun onGroupFileUpload(event: FileEvent) {
event.saveTo("$ROOT_PATH/caches/") event.saveTo("./files")
} }
override suspend fun onLeaveEvent(groupId: Long, userId: Long, operator: Long, time: Long) { override suspend fun onLeaveEvent(groupId: Long, userId: Long, operator: Long, time: Long) {
@@ -165,23 +123,21 @@ class FancyBot : OneBotListener {
this.sendGroupMessage(groupId, msg) this.sendGroupMessage(groupId, msg)
} }
override suspend fun onGroupPoke(event: PokeEvent) { override suspend fun onPoke(event: PokeEvent) {
event.groupId?.let {
if (event.targetId == configManager.selfId) { if (event.targetId == configManager.selfId) {
val msg = MessageChain.Builder() val msg = MessageChain.Builder()
.addAt(event.userId) .addAt(event.userId)
.addText("${event.action.first()}牛魔呢") .addText("${event.action.first()}牛魔呢")
.build() .build()
this.sendGroupMessage(event.groupId!!, msg) this.sendGroupMessage(it, msg)
} }
} }
}
}
override suspend fun onBeKicked(groupId: Long, operator: Long, time: Long) { fun initFilesDir() {
blackListManager.insertGroup(groupId, operator, time) File("./files/images").also { it.mkdirs() }
this.sendPrivateMessage(
configManager.noticeUser,
"被: ${groupId}踢出! 操作人: $operator 时间: ${time.convertToDate()} 已将其拉入黑名单!"
)
}
} }
suspend fun main() { suspend fun main() {
@@ -199,8 +155,7 @@ suspend fun main() {
} }
initDatabase() initDatabase()
initFilesDir() initFilesDir()
initCommand(rob) val commandManager = rob.commandManager
initSetuIndex() commands.forEach { commandManager.register(it) }
initItems() items.forEach { itemManager.register(it) }
initBackgroundTasks(rob)
} }
@@ -8,9 +8,6 @@
package cn.rtast.fancybot.commands package cn.rtast.fancybot.commands
import cn.rtast.fancybot.annotations.CommandDescription import cn.rtast.fancybot.annotations.CommandDescription
import cn.rtast.fancybot.configManager
import cn.rtast.fancybot.enums.CommandAction
import cn.rtast.fancybot.util.file.insertActionRecord
import cn.rtast.rob.entity.GroupMessage import cn.rtast.rob.entity.GroupMessage
import cn.rtast.rob.util.BaseCommand import cn.rtast.rob.util.BaseCommand
import cn.rtast.rob.util.ob.OneBotListener import cn.rtast.rob.util.ob.OneBotListener
@@ -20,25 +17,6 @@ class EchoCommand : BaseCommand() {
override val commandNames = listOf("/echo") override val commandNames = listOf("/echo")
override suspend fun executeGroup(listener: OneBotListener, message: GroupMessage, args: List<String>) { override suspend fun executeGroup(listener: OneBotListener, message: GroupMessage, args: List<String>) {
if (message.sender.isAdmin || message.sender.isOwner) {
listener.sendGroupMessage(message.groupId, args.joinToString(" ")) 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,12 +26,13 @@ class HelpCommand : BaseCommand() {
if (args.isEmpty()) { if (args.isEmpty()) {
val node = NodeMessageChain.Builder() val node = NodeMessageChain.Builder()
val msg = MessageChain.Builder() val msg = MessageChain.Builder()
commands.sortedBy { it::class.simpleName }.toMutableList().forEach { commands.sortedBy { it::class.simpleName }.forEach {
val description = if (!it::class.hasAnnotation<CommandDescription>()) "暂无描述" val description = if (!it::class.hasAnnotation<CommandDescription>()) "暂无描述"
else it::class.findAnnotation<CommandDescription>()?.description!! else it::class.findAnnotation<CommandDescription>()?.description!!
val commandName = it::class.simpleName?.replace("Command", "")!! val commandName = it::class.simpleName?.replace("Command", "")!!
val commandNames = it.commandNames.joinToString(",") val commandNames = it.commandNames.joinToString(",")
msg.addText("[$commandName] [$description] 命令: $commandNames").addNewLine() msg.addText("[$commandName] [$description] 命令: $commandNames")
.addNewLine()
} }
msg.addText("共计${commands.size}条命令") msg.addText("共计${commands.size}条命令")
node.addMessageChain(msg.build(), configManager.selfId) node.addMessageChain(msg.build(), configManager.selfId)
@@ -9,8 +9,6 @@ package cn.rtast.fancybot.commands
import cn.rtast.fancybot.START_UP_TIME import cn.rtast.fancybot.START_UP_TIME
import cn.rtast.fancybot.annotations.CommandDescription import cn.rtast.fancybot.annotations.CommandDescription
import cn.rtast.fancybot.enums.CommandAction
import cn.rtast.fancybot.util.file.insertActionRecord
import cn.rtast.rob.entity.GroupMessage import cn.rtast.rob.entity.GroupMessage
import cn.rtast.rob.util.BaseCommand import cn.rtast.rob.util.BaseCommand
import cn.rtast.rob.util.ob.MessageChain import cn.rtast.rob.util.ob.MessageChain
@@ -46,6 +44,5 @@ class StatusCommand : BaseCommand() {
.addText("Java版本: $javaVersion / Kotlin版本: $kotlinVersion") .addText("Java版本: $javaVersion / Kotlin版本: $kotlinVersion")
.build() .build()
message.reply(msg) message.reply(msg)
insertActionRecord(CommandAction.Status, message.sender.userId)
} }
} }
@@ -1,40 +0,0 @@
/*
* 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,9 +13,7 @@ import cn.rtast.fancybot.entity.gpt.ChatCompletionsPayload
import cn.rtast.fancybot.entity.gpt.ChatCompletionsResponse import cn.rtast.fancybot.entity.gpt.ChatCompletionsResponse
import cn.rtast.fancybot.entity.gpt.LlamaResponse import cn.rtast.fancybot.entity.gpt.LlamaResponse
import cn.rtast.fancybot.entity.gpt.ModelList import cn.rtast.fancybot.entity.gpt.ModelList
import cn.rtast.fancybot.enums.CommandAction
import cn.rtast.fancybot.util.Http import cn.rtast.fancybot.util.Http
import cn.rtast.fancybot.util.file.insertActionRecord
import cn.rtast.fancybot.util.str.toJson import cn.rtast.fancybot.util.str.toJson
import cn.rtast.rob.entity.GroupMessage import cn.rtast.rob.entity.GroupMessage
import cn.rtast.rob.util.BaseCommand import cn.rtast.rob.util.BaseCommand
@@ -70,7 +68,6 @@ class AICommand : BaseCommand() {
.build() .build()
nodeMsg.addMessageChain(msg, configManager.selfId) nodeMsg.addMessageChain(msg, configManager.selfId)
listener.sendGroupForwardMsg(message.groupId, nodeMsg.build()) listener.sendGroupForwardMsg(message.groupId, nodeMsg.build())
insertActionRecord(CommandAction.AI, message.sender.userId, "$content-$model-GPT")
} }
} }
@@ -97,6 +94,5 @@ class LlamaCommand : BaseCommand() {
.build() .build()
nodeMsg.addMessageChain(msg, configManager.selfId) nodeMsg.addMessageChain(msg, configManager.selfId)
listener.sendGroupForwardMsg(message.groupId, nodeMsg.build()) listener.sendGroupForwardMsg(message.groupId, nodeMsg.build())
insertActionRecord(CommandAction.AI, message.sender.userId, "$prompt-LLAMA")
} }
} }
@@ -61,6 +61,6 @@ class CigaretteCommand : BaseCommand() {
.build() .build()
nodeMsg.addMessageChain(footerMsg, configManager.selfId) nodeMsg.addMessageChain(footerMsg, configManager.selfId)
message.reply(nodeMsg.build()) message.reply(nodeMsg.build())
message.sender.poke() message.sender.groupPoke()
} }
} }
@@ -20,8 +20,8 @@ import cn.rtast.rob.util.ob.OneBotListener
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
@CommandDescription("搜索动漫(Bangumi.tv)") @CommandDescription("搜索动漫")
class AnimeSearchCommand : BaseCommand() { class DMSearchCommand : BaseCommand() {
override val commandNames = listOf("/dm", "动漫") override val commandNames = listOf("/dm", "动漫")
private val client = OkHttpClient() private val client = OkHttpClient()
@@ -11,9 +11,9 @@ import cn.rtast.fancybot.annotations.CommandDescription
import cn.rtast.fancybot.entity.domain.PricePayload import cn.rtast.fancybot.entity.domain.PricePayload
import cn.rtast.fancybot.entity.domain.PriceResponse import cn.rtast.fancybot.entity.domain.PriceResponse
import cn.rtast.fancybot.util.Http import cn.rtast.fancybot.util.Http
import cn.rtast.fancybot.util.misc.toByteArray
import cn.rtast.fancybot.util.str.encodeToBase64 import cn.rtast.fancybot.util.str.encodeToBase64
import cn.rtast.fancybot.util.str.toJson import cn.rtast.fancybot.util.str.toJson
import cn.rtast.fancybot.util.toByteArray
import cn.rtast.rob.entity.GroupMessage import cn.rtast.rob.entity.GroupMessage
import cn.rtast.rob.util.BaseCommand import cn.rtast.rob.util.BaseCommand
import cn.rtast.rob.util.ob.MessageChain import cn.rtast.rob.util.ob.MessageChain
@@ -1,58 +0,0 @@
/*
* 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.configManager
import cn.rtast.fancybot.entity.github.UserInfo import cn.rtast.fancybot.entity.github.UserInfo
import cn.rtast.fancybot.util.Http import cn.rtast.fancybot.util.Http
import cn.rtast.fancybot.util.misc.drawCustomImage import cn.rtast.fancybot.util.drawCustomImage
import cn.rtast.fancybot.util.misc.drawString import cn.rtast.fancybot.util.drawString
import cn.rtast.fancybot.util.misc.toBufferedImage
import cn.rtast.fancybot.util.misc.toByteArray
import cn.rtast.fancybot.util.str.encodeToBase64 import cn.rtast.fancybot.util.str.encodeToBase64
import cn.rtast.fancybot.util.toBufferedImage
import cn.rtast.fancybot.util.toByteArray
import cn.rtast.rob.entity.GroupMessage import cn.rtast.rob.entity.GroupMessage
import cn.rtast.rob.util.BaseCommand import cn.rtast.rob.util.BaseCommand
import cn.rtast.rob.util.ob.MessageChain import cn.rtast.rob.util.ob.MessageChain
@@ -1,34 +0,0 @@
/*
* 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)
}
}
@@ -1,51 +0,0 @@
/*
* 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("这个词似乎不是一个成语呢~~")
}
}
}
@@ -0,0 +1,92 @@
/*
* 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,92 +8,27 @@
package cn.rtast.fancybot.commands.lookup package cn.rtast.fancybot.commands.lookup
import cn.rtast.fancybot.annotations.CommandDescription 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.entity.GroupMessage
import cn.rtast.rob.util.BaseCommand import cn.rtast.rob.util.BaseCommand
import cn.rtast.rob.util.ob.MessageChain import cn.rtast.rob.util.ob.MessageChain
import cn.rtast.rob.util.ob.OneBotListener import cn.rtast.rob.util.ob.OneBotListener
import cn.rtast.rob.util.ob.asNode
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import java.net.InetAddress import java.net.InetAddress
import java.net.UnknownHostException import java.net.UnknownHostException
import java.util.Hashtable
import javax.naming.directory.InitialDirContext
@CommandDescription("解析域名获取IP地址") @CommandDescription("解析域名获取IP地址")
class NslookupCommand : BaseCommand() { class NslookupCommand : BaseCommand() {
override val commandNames = listOf("/ns") override val commandNames = listOf("/ns")
companion object {
private const val MC_SRV_PREFIX = "_minecraft._tcp"
private const val IP_INFO_API = "https://ipinfo.io/###/json"
private val env = Hashtable<String, String>().also {
it["java.naming.factory.initial"] = "com.sun.jndi.dns.DnsContextFactory"
}
private val dirContext = InitialDirContext(env)
fun srv(host: String): MutableList<String> {
val srvDomain = "$MC_SRV_PREFIX.$host"
val attrs = dirContext.getAttributes(srvDomain, arrayOf("SRV"))
val srvRecords = mutableListOf<String>()
val srvAttr = attrs.get("SRV")
if (srvAttr != null) {
for (i in 0 until srvAttr.size()) {
val srvRecord = srvAttr.get(i) as String
srvRecords.add(srvRecord)
}
}
return srvRecords
}
}
private fun geo(ip: String) = Http.get<IPGeo>(IP_INFO_API.replace("###", ip))
override suspend fun executeGroup(listener: OneBotListener, message: GroupMessage, args: List<String>) { override suspend fun executeGroup(listener: OneBotListener, message: GroupMessage, args: List<String>) {
val host = args.first() val host = args.joinToString(" ")
val nsType = if (args.size == 3) args[1] else "common"
val srvType = if (args.size == 3) args.last() else "mc"
try { try {
if (nsType == "srv") {
when (srvType) {
"mc" -> {
val srvRecords = srv(host)
val messages = mutableListOf<MessageChain>()
srvRecords.forEach {
val srv = it.split(" ")
val priority = srv.first().toInt()
val weight = srv[1].toInt()
val port = srv[2].toInt()
val host = srv.last()
val ip = InetAddress.getByName(host).hostAddress
val geo = this.geo(ip)
val msg = MessageChain.Builder() val msg = MessageChain.Builder()
.addText("解析后的地址: $host:$port") .addText("域名: ${host}解析后的IP地址为: ")
.addNewLine() .addText(withContext(Dispatchers.IO) { InetAddress.getByName(host) }.hostAddress)
.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() .build()
message.reply(msg) message.reply(msg)
}
} catch (_: UnknownHostException) { } catch (_: UnknownHostException) {
message.reply("未知的主机名: $host") message.reply("未知的主机名: $host")
} catch (e: Exception) { } catch (e: Exception) {
@@ -56,11 +56,9 @@ class PixivCommand : BaseCommand() {
} catch (_: Exception) { } catch (_: Exception) {
} }
} }
node.addMessageChain( node.addMessageChain(MessageChain.Builder()
MessageChain.Builder()
.addText("图片来源: Pixiv") .addText("图片来源: Pixiv")
.build(), configManager.selfId .build(), configManager.selfId)
)
message.reply(node.build()) message.reply(node.build())
return return
} }
@@ -0,0 +1,27 @@
/*
* 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,50 @@
/*
* Copyright © 2024 RTAkland
* Author: RTAkland
* Date: 2024/10/1
*/
package cn.rtast.fancybot.commands.lookup
import cn.rtast.fancybot.configManager
import cn.rtast.fancybot.entity.stock.StockSymbolQuery
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
class StockDataCommand : BaseCommand() {
override val commandNames = listOf("sd", "股票")
private val apiUrl = "https://proxy.rtast.cn/https/finnhub.io"
private val apiKey = "crtmph1r01qv68qhn8n0crtmph1r01qv68qhn8ng"
override suspend fun executeGroup(listener: OneBotListener, message: GroupMessage, args: List<String>) {
val method = args.first()
when (method) {
"q" -> {
val messages = mutableListOf<MessageChain>()
val symbol = args.joinToString(" ")
val response = Http.get<StockSymbolQuery>(
"$apiUrl/api/v1/search",
mapOf(
"q" to symbol,
"token" to apiKey,
)
)
messages.add(MessageChain.Builder().addText("共搜索到${response.count}条结果").build())
response.result.forEach {
val tempMsg = MessageChain.Builder()
.addText("股票代码: ${it.displaySymbol}(${it.symbol}, ${it.description})")
.build()
messages.add(tempMsg)
}
message.reply(messages.asNode(configManager.selfId))
}
}
}
}
@@ -1,35 +0,0 @@
/*
* 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)
}
}
@@ -1,52 +0,0 @@
/*
* 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.Geo
import cn.rtast.fancybot.entity.weather.Weather import cn.rtast.fancybot.entity.weather.Weather
import cn.rtast.fancybot.util.Http import cn.rtast.fancybot.util.Http
import cn.rtast.fancybot.util.misc.Resources import cn.rtast.fancybot.util.Resources
import cn.rtast.fancybot.util.misc.drawCenteredText import cn.rtast.fancybot.util.drawCenteredText
import cn.rtast.fancybot.util.misc.drawCustomImage import cn.rtast.fancybot.util.drawCustomImage
import cn.rtast.fancybot.util.misc.toBufferedImage
import cn.rtast.fancybot.util.misc.toByteArray
import cn.rtast.fancybot.util.str.encodeToBase64 import cn.rtast.fancybot.util.str.encodeToBase64
import cn.rtast.fancybot.util.toBufferedImage
import cn.rtast.fancybot.util.toByteArray
import cn.rtast.rob.entity.GroupMessage import cn.rtast.rob.entity.GroupMessage
import cn.rtast.rob.util.BaseCommand import cn.rtast.rob.util.BaseCommand
import cn.rtast.rob.util.ob.MessageChain import cn.rtast.rob.util.ob.MessageChain
@@ -10,9 +10,7 @@ package cn.rtast.fancybot.commands.lookup
import cn.rtast.fancybot.annotations.CommandDescription import cn.rtast.fancybot.annotations.CommandDescription
import cn.rtast.fancybot.entity.wiki.PageInfoResponse import cn.rtast.fancybot.entity.wiki.PageInfoResponse
import cn.rtast.fancybot.entity.wiki.WikipediaResponse import cn.rtast.fancybot.entity.wiki.WikipediaResponse
import cn.rtast.fancybot.enums.CommandAction
import cn.rtast.fancybot.util.Http import cn.rtast.fancybot.util.Http
import cn.rtast.fancybot.util.file.insertActionRecord
import cn.rtast.rob.entity.GroupMessage import cn.rtast.rob.entity.GroupMessage
import cn.rtast.rob.util.BaseCommand import cn.rtast.rob.util.BaseCommand
import cn.rtast.rob.util.ob.MessageChain import cn.rtast.rob.util.ob.MessageChain
@@ -25,12 +23,10 @@ class WikipediaCommand : BaseCommand() {
private val wikipediaAPI = "https://proxy.rtast.cn/https/zh.wikipedia.org/w/api.php" private val wikipediaAPI = "https://proxy.rtast.cn/https/zh.wikipedia.org/w/api.php"
companion object { private fun String.extractPlainTextFromHtml(): String {
fun String.extractPlainTextFromHtml(): String {
val doc = Jsoup.parse(this) val doc = Jsoup.parse(this)
return doc.text() return doc.text()
} }
}
override suspend fun executeGroup(listener: OneBotListener, message: GroupMessage, args: List<String>) { override suspend fun executeGroup(listener: OneBotListener, message: GroupMessage, args: List<String>) {
if (args.isEmpty()) { if (args.isEmpty()) {
@@ -73,8 +69,6 @@ class WikipediaCommand : BaseCommand() {
.addText("没有查询到指定的wiki页面呢 >>> $title") .addText("没有查询到指定的wiki页面呢 >>> $title")
.build() .build()
message.reply(msg) message.reply(msg)
} finally {
insertActionRecord(CommandAction.Wiki, message.sender.userId, title)
} }
} }
} }
@@ -8,8 +8,7 @@
package cn.rtast.fancybot.commands.misc package cn.rtast.fancybot.commands.misc
import cn.rtast.fancybot.annotations.CommandDescription import cn.rtast.fancybot.annotations.CommandDescription
import cn.rtast.fancybot.enums.CommandAction import cn.rtast.fancybot.configManager
import cn.rtast.fancybot.util.file.insertActionRecord
import cn.rtast.rob.entity.ArrayMessage import cn.rtast.rob.entity.ArrayMessage
import cn.rtast.rob.entity.GroupMessage import cn.rtast.rob.entity.GroupMessage
import cn.rtast.rob.enums.ArrayMessageType import cn.rtast.rob.enums.ArrayMessageType
@@ -21,17 +20,13 @@ class AntiRevokeCommand : BaseCommand() {
override val commandNames = listOf("/revoke", "/rv") override val commandNames = listOf("/revoke", "/rv")
override suspend fun executeGroup(listener: OneBotListener, message: GroupMessage, args: List<String>) { override suspend fun executeGroup(listener: OneBotListener, message: GroupMessage, args: List<String>) {
if (message.sender.isAdmin || message.sender.isOwner) { if (message.sender.userId !in configManager.admins) message.reply("你不许用防撤回")
val messageId = args.first().toLong() val messageId = args.first().toLong()
val getMsg = listener.getMessage(messageId) val getMsg = listener.getMessage(messageId)
val msgList = mutableListOf<ArrayMessage>() val msgList = mutableListOf<ArrayMessage>()
msgList.add(ArrayMessage(ArrayMessageType.at, ArrayMessage.Data(qq = message.sender.userId.toString()))) msgList.add(ArrayMessage(ArrayMessageType.at, ArrayMessage.Data(qq = getMsg.sender.userId.toString())))
msgList.add(ArrayMessage(ArrayMessageType.text, ArrayMessage.Data(text = "\n被撤回的消息如下: \n"))) msgList.add(ArrayMessage(ArrayMessageType.text, ArrayMessage.Data(text = "\n被撤回的消息如下: \n")))
msgList.addAll(getMsg.message) msgList.addAll(getMsg.message)
listener.sendGroupMessage(message.groupId, msgList) listener.sendGroupMessage(message.groupId, msgList)
insertActionRecord(CommandAction.AntiRevoke, message.sender.userId, getMsg.messageId.toString())
} else {
message.reply("你不许用防撤回")
}
} }
} }
@@ -7,10 +7,9 @@
package cn.rtast.fancybot.commands.misc package cn.rtast.fancybot.commands.misc
import cn.rtast.fancybot.annotations.CommandDescription
import cn.rtast.fancybot.configManager import cn.rtast.fancybot.configManager
import cn.rtast.fancybot.entity.bullshit.BullShit import cn.rtast.fancybot.entity.bullshit.BullShit
import cn.rtast.fancybot.util.misc.Resources import cn.rtast.fancybot.util.Resources
import cn.rtast.fancybot.util.str.fromJson import cn.rtast.fancybot.util.str.fromJson
import cn.rtast.rob.entity.GroupMessage import cn.rtast.rob.entity.GroupMessage
import cn.rtast.rob.util.BaseCommand import cn.rtast.rob.util.BaseCommand
@@ -19,7 +18,6 @@ import cn.rtast.rob.util.ob.NodeMessageChain
import cn.rtast.rob.util.ob.OneBotListener import cn.rtast.rob.util.ob.OneBotListener
import kotlin.random.Random import kotlin.random.Random
@CommandDescription("狗屁不通文章生成器")
class BullShitGenerateCommand : BaseCommand() { class BullShitGenerateCommand : BaseCommand() {
override val commandNames = listOf("/gpbt", "狗屁不通") override val commandNames = listOf("/gpbt", "狗屁不通")
@@ -67,6 +65,6 @@ class BullShitGenerateCommand : BaseCommand() {
.addMessageChain(MessageChain.Builder().addText(finalText).build(), configManager.selfId) .addMessageChain(MessageChain.Builder().addText(finalText).build(), configManager.selfId)
.build() .build()
message.reply(node) message.reply(node)
message.sender.poke() message.sender.groupPoke()
} }
} }
@@ -12,18 +12,16 @@ import cn.rtast.fancybot.entity.compiler.GLOTPayload
import cn.rtast.fancybot.entity.compiler.GLOTResponse import cn.rtast.fancybot.entity.compiler.GLOTResponse
import cn.rtast.fancybot.entity.compiler.KCSPayload import cn.rtast.fancybot.entity.compiler.KCSPayload
import cn.rtast.fancybot.entity.compiler.KCSResponse import cn.rtast.fancybot.entity.compiler.KCSResponse
import cn.rtast.fancybot.enums.CommandAction
import cn.rtast.fancybot.util.Http import cn.rtast.fancybot.util.Http
import cn.rtast.fancybot.util.file.insertActionRecord
import cn.rtast.fancybot.util.str.toJson import cn.rtast.fancybot.util.str.toJson
import cn.rtast.rob.entity.GroupMessage import cn.rtast.rob.entity.GroupMessage
import cn.rtast.rob.util.BaseCommand import cn.rtast.rob.util.BaseCommand
import cn.rtast.rob.util.ob.MessageChain import cn.rtast.rob.util.ob.MessageChain
import cn.rtast.rob.util.ob.OneBotListener import cn.rtast.rob.util.ob.OneBotListener
@CommandDescription("执行不同语言的代码") @CommandDescription("执行不同语言的代码片段")
class CompilerCommand : BaseCommand() { class CompilerCommand : BaseCommand() {
override val commandNames = listOf("/compiler", "/exec", "/e") override val commandNames = listOf("/compiler", "/exec")
private val kotlinCompilerServer = "https://api.kotlinlang.org/api/2.0.20/compiler/run" private val kotlinCompilerServer = "https://api.kotlinlang.org/api/2.0.20/compiler/run"
private val glotCompilerServer = "https://glot.io/run" private val glotCompilerServer = "https://glot.io/run"
@@ -33,22 +31,16 @@ class CompilerCommand : BaseCommand() {
message.reply("使用`/exec` <语言> <代码> 即可执行~") message.reply("使用`/exec` <语言> <代码> 即可执行~")
return return
} }
var language = args.first().lowercase() val language = args.first()
val code = args.drop(1).joinToString(" ") val code = args.drop(1).joinToString(" ")
val response = when (language) { val response = when (language) {
"kotlin", "kt" -> { "kotlin", "Kotlin", "kt", "Kt", "KT" -> {
Http.post<KCSResponse>( Http.post<KCSResponse>(
kotlinCompilerServer, kotlinCompilerServer, jsonBody = KCSPayload(listOf(KCSPayload.File(code))).toJson()
jsonBody = KCSPayload(listOf(KCSPayload.File(code))).toJson()
).text ).text
} }
else -> { else -> {
if (language == "js") language = "javascript"
if (language == "cs") language = "csharp"
if (language == "sh") language = "bash"
if (language == "asm") language = "assembly"
if (language == "py") language = "python"
Http.post<GLOTResponse>( Http.post<GLOTResponse>(
"$glotCompilerServer/$language", "$glotCompilerServer/$language",
params = mapOf("version" to "latest"), params = mapOf("version" to "latest"),
@@ -56,7 +48,6 @@ class CompilerCommand : BaseCommand() {
).stdout ).stdout
} }
} }
try {
val msg = MessageChain.Builder() val msg = MessageChain.Builder()
.addReply(message.messageId) .addReply(message.messageId)
.addText("执行结果如下:") .addText("执行结果如下:")
@@ -64,10 +55,5 @@ class CompilerCommand : BaseCommand() {
.addText(response.replace("<outStream>", "").replace("</outStream>", "")) .addText(response.replace("<outStream>", "").replace("</outStream>", ""))
.build() .build()
listener.sendGroupMessage(message.groupId, msg) listener.sendGroupMessage(message.groupId, msg)
} catch (e: Exception) {
message.reply("执行错误: ${e.message}")
} finally {
insertActionRecord(CommandAction.Compiler, message.sender.userId)
}
} }
} }
@@ -1,28 +0,0 @@
/*
* Copyright © 2024 RTAkland
* Author: RTAkland
* Date: 2024/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}")
}
}
@@ -1,35 +0,0 @@
/*
* 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)
}
}
@@ -1,35 +0,0 @@
/*
* 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 package cn.rtast.fancybot.commands.misc
import cn.rtast.fancybot.annotations.CommandDescription import cn.rtast.fancybot.annotations.CommandDescription
import cn.rtast.fancybot.util.misc.Resources import cn.rtast.fancybot.util.Resources
import cn.rtast.fancybot.util.misc.drawCircularImage import cn.rtast.fancybot.util.drawCircularImage
import cn.rtast.fancybot.util.misc.makeGif import cn.rtast.fancybot.util.makeGif
import cn.rtast.fancybot.util.misc.toBufferedImage
import cn.rtast.fancybot.util.str.encodeToBase64 import cn.rtast.fancybot.util.str.encodeToBase64
import cn.rtast.fancybot.util.toBufferedImage
import cn.rtast.rob.entity.GroupMessage import cn.rtast.rob.entity.GroupMessage
import cn.rtast.rob.enums.ArrayMessageType import cn.rtast.rob.enums.ArrayMessageType
import cn.rtast.rob.util.BaseCommand import cn.rtast.rob.util.BaseCommand
@@ -22,15 +22,14 @@ import java.time.ZoneId
class KFCCommand : BaseCommand() { class KFCCommand : BaseCommand() {
override val commandNames = listOf("/fkxqs", "/kfc") override val commandNames = listOf("/fkxqs", "/kfc")
companion object { private fun isThursday(): Boolean {
fun isThursday(): Boolean {
val dateTime = LocalDateTime.ofInstant( val dateTime = LocalDateTime.ofInstant(
Instant.now(), Instant.now(),
ZoneId.of("Asia/Shanghai") ZoneId.of("Asia/Shanghai")
) )
return dateTime.dayOfWeek == DayOfWeek.THURSDAY return dateTime.dayOfWeek == DayOfWeek.THURSDAY
} }
}
override suspend fun executeGroup(listener: OneBotListener, message: GroupMessage, args: List<String>) { override suspend fun executeGroup(listener: OneBotListener, message: GroupMessage, args: List<String>) {
if (!isThursday()) { if (!isThursday()) {
@@ -14,7 +14,7 @@ import cn.rtast.rob.util.ob.MessageChain
import cn.rtast.rob.util.ob.OneBotListener import cn.rtast.rob.util.ob.OneBotListener
@CommandDescription("资料卡点赞") @CommandDescription("资料卡点赞")
class SendLikeCommand : BaseCommand() { class LikeMeCommand : BaseCommand() {
override val commandNames = listOf("赞我") override val commandNames = listOf("赞我")
override suspend fun executeGroup(listener: OneBotListener, message: GroupMessage, args: List<String>) { override suspend fun executeGroup(listener: OneBotListener, message: GroupMessage, args: List<String>) {
@@ -1,668 +0,0 @@
/*
* 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) }
}
}
}
}
@@ -1,48 +0,0 @@
/*
* 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)
}
}
@@ -1,96 +0,0 @@
/*
* 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("回复错误已经取消本次操作")
}
}
}
@@ -1,33 +0,0 @@
/*
* 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)
}
}
@@ -1,34 +0,0 @@
/*
* 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 package cn.rtast.fancybot.commands.misc
import cn.rtast.fancybot.annotations.CommandDescription import cn.rtast.fancybot.annotations.CommandDescription
import cn.rtast.fancybot.enums.CountryList import cn.rtast.fancybot.entity.enums.CountryList
import cn.rtast.fancybot.util.misc.randomBooleanWithProbability import cn.rtast.fancybot.util.randomBooleanWithProbability
import cn.rtast.rob.entity.GroupMessage import cn.rtast.rob.entity.GroupMessage
import cn.rtast.rob.util.BaseCommand import cn.rtast.rob.util.BaseCommand
import cn.rtast.rob.util.ob.MessageChain import cn.rtast.rob.util.ob.MessageChain
@@ -1,14 +0,0 @@
/*
* Copyright © 2024 RTAkland
* Author: RTAkland
* Date: 2024/10/15
*/
package cn.rtast.fancybot.commands.misc
import cn.rtast.fancybot.annotations.CommandDescription
@CommandDescription("复读鸡")
object RepeatCommand {
}
@@ -9,10 +9,9 @@ package cn.rtast.fancybot.commands.misc
import cn.rtast.fancybot.annotations.CommandDescription import cn.rtast.fancybot.annotations.CommandDescription
import cn.rtast.fancybot.configManager import cn.rtast.fancybot.configManager
import cn.rtast.fancybot.enums.CommandAction
import cn.rtast.fancybot.util.file.insertActionRecord
import cn.rtast.rob.entity.GroupMessage import cn.rtast.rob.entity.GroupMessage
import cn.rtast.rob.util.BaseCommand import cn.rtast.rob.util.BaseCommand
import cn.rtast.rob.util.ob.MessageChain
import cn.rtast.rob.util.ob.OneBotListener import cn.rtast.rob.util.ob.OneBotListener
import org.simplejavamail.api.mailer.config.TransportStrategy import org.simplejavamail.api.mailer.config.TransportStrategy
import org.simplejavamail.email.EmailBuilder import org.simplejavamail.email.EmailBuilder
@@ -48,13 +47,16 @@ class SendMailCommand : BaseCommand() {
message.reply("发送`/mail <收件人地址>`即可发送测试邮件~") message.reply("发送`/mail <收件人地址>`即可发送测试邮件~")
return return
} }
if (message.sender.isAdmin || message.sender.isOwner) { if (message.sender.userId !in configManager.admins) {
val msg = MessageChain.Builder()
.addReply(message.messageId)
.addText("你不许发邮件")
.build()
listener.sendGroupMessage(message.groupId, msg)
return
}
val target = args.first() val target = args.first()
this.sendMail(target) this.sendMail(target)
message.reply("正在发送邮件, 请查收~(可能会出现在垃圾桶中~)") message.reply("正在发送邮件, 请查收~(可能会出现在垃圾桶中~)")
insertActionRecord(CommandAction.SendMail, message.sender.userId, target)
} else {
message.reply("你不许发邮件")
}
} }
} }
@@ -7,13 +7,10 @@
package cn.rtast.fancybot.commands.misc package cn.rtast.fancybot.commands.misc
import cn.rtast.fancybot.API_RTAST_URL
import cn.rtast.fancybot.annotations.CommandDescription import cn.rtast.fancybot.annotations.CommandDescription
import cn.rtast.fancybot.configManager import cn.rtast.fancybot.entity.ShortLink
import cn.rtast.fancybot.entity.shortlink.ShortLinkPayload
import cn.rtast.fancybot.entity.shortlink.ShortLinkResponse
import cn.rtast.fancybot.util.Http import cn.rtast.fancybot.util.Http
import cn.rtast.fancybot.util.str.toJson import cn.rtast.fancybot.util.str.encodeToBase64
import cn.rtast.rob.entity.GroupMessage import cn.rtast.rob.entity.GroupMessage
import cn.rtast.rob.util.BaseCommand import cn.rtast.rob.util.BaseCommand
import cn.rtast.rob.util.ob.OneBotListener import cn.rtast.rob.util.ob.OneBotListener
@@ -22,25 +19,18 @@ import cn.rtast.rob.util.ob.OneBotListener
class ShortLinkCommand : BaseCommand() { class ShortLinkCommand : BaseCommand() {
override val commandNames = listOf("/s") 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>) { override suspend fun executeGroup(listener: OneBotListener, message: GroupMessage, args: List<String>) {
if (args.isEmpty()) { if (args.isEmpty()) {
message.reply("发送`/s <url>`来生成短链接`不要放入一些奇怪的链接哦~") message.reply("发送`/s <url>`来生成短链接`不要放入一些奇怪的链接哦~")
return return
} }
val target = args.first() val target = args.first().encodeToBase64()
val shortLink = target.makeShortLink() val shortLink = Http.post<ShortLink>(
message.reply(shortLink) "$shortLinkApi/short_link",
params = mapOf("target" to target)
)
message.reply("$shortLinkApi/s?rnd_id=${shortLink.id}")
} }
} }
@@ -1,43 +0,0 @@
/*
* 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,14 +7,11 @@
package cn.rtast.fancybot.commands.misc package cn.rtast.fancybot.commands.misc
import cn.rtast.fancybot.annotations.CommandDescription
import cn.rtast.rob.entity.GroupMessage import cn.rtast.rob.entity.GroupMessage
import cn.rtast.rob.util.BaseCommand import cn.rtast.rob.util.BaseCommand
import cn.rtast.rob.util.ob.MessageChain import cn.rtast.rob.util.ob.MessageChain
import cn.rtast.rob.util.ob.OneBotListener import cn.rtast.rob.util.ob.OneBotListener
@Deprecated("已迁移到兑换列表中")
@CommandDescription("获得十张色图!")
class TenSetuCommand : BaseCommand() { class TenSetuCommand : BaseCommand() {
override val commandNames = listOf("十张色图") override val commandNames = listOf("十张色图")
@@ -1,69 +0,0 @@
/*
* 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("你喝个结巴, 是吧")
}
}
@@ -1,91 +0,0 @@
/*
* 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.commands
import cn.rtast.fancybot.niuziBankManager import cn.rtast.fancybot.niuziBankManager
import cn.rtast.fancybot.niuziManager import cn.rtast.fancybot.niuziManager
import cn.rtast.fancybot.util.misc.getUserName import cn.rtast.fancybot.util.getUserName
import cn.rtast.rob.entity.GroupMessage import cn.rtast.rob.entity.GroupMessage
import cn.rtast.rob.enums.ArrayMessageType import cn.rtast.rob.enums.ArrayMessageType
import cn.rtast.rob.util.BaseCommand import cn.rtast.rob.util.BaseCommand
@@ -1,68 +0,0 @@
/*
* 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,13 +22,14 @@ class NiuziRedeemCommand : BaseCommand() {
override suspend fun executeGroup(listener: OneBotListener, message: GroupMessage, args: List<String>) { override suspend fun executeGroup(listener: OneBotListener, message: GroupMessage, args: List<String>) {
if (args.isEmpty()) { if (args.isEmpty()) {
val msg = MessageChain.Builder() val msg = MessageChain.Builder()
.addReply(message.messageId)
.addText("发送 /rdm <物品名称> 即可兑换哦!") .addText("发送 /rdm <物品名称> 即可兑换哦!")
.addNewLine() .addNewLine()
.addText("发送 /rdm list 即可查看所有可兑换的物品~") .addText("发送 /rdm list 即可查看所有可兑换的物品~")
.addNewLine() .addNewLine()
.addText("发送 `我的牛子` 可以查看自己的牛子长度~") .addText("发送 `我的牛子` 可以查看自己的牛子长度~")
.build() .build()
message.reply(msg) listener.sendGroupMessage(message.groupId, msg)
return return
} }
val status = niuziManager.getUser(message.sender.userId) val status = niuziManager.getUser(message.sender.userId)
@@ -46,26 +47,29 @@ class NiuziRedeemCommand : BaseCommand() {
val selectedItem = itemManager.items.find { it.itemNames.any { any -> any == item } } val selectedItem = itemManager.items.find { it.itemNames.any { any -> any == item } }
if (selectedItem == null) { if (selectedItem == null) {
val msg = MessageChain.Builder() val msg = MessageChain.Builder()
.addReply(message.messageId)
.addText("没有找到你想兑换的东西呢") .addText("没有找到你想兑换的东西呢")
.addNewLine() .addNewLine()
.addText("你可以发送 /rdm list来查看所有可兑换的物品") .addText("你可以发送 /rdm list来查看所有可兑换的物品")
.build() .build()
message.reply(msg) listener.sendGroupMessage(message.groupId, msg)
return return
} }
val selectedItemPrice = selectedItem.itemPrice val selectedItemPrice = selectedItem.itemPrice
if (selectedItemPrice > status.length) { if (selectedItemPrice > status.length) {
val msg = MessageChain.Builder() val msg = MessageChain.Builder()
.addReply(message.messageId)
.addText("你的牛子长度不够兑换这个物品呢~") .addText("你的牛子长度不够兑换这个物品呢~")
.addNewLine() .addNewLine()
.addText("你有: ${status.length}cm, 兑换需要: $selectedItemPrice cm~\"") .addText("你有: ${status.length}cm, 兑换需要: $selectedItemPrice cm~\"")
.build() .build()
message.reply(msg) listener.sendGroupMessage(message.groupId, msg)
return return
} }
val afterStatus = niuziManager.redeemItem(message.sender.userId, selectedItemPrice)?.length!! val afterStatus = niuziManager.redeemItem(message.sender.userId, selectedItemPrice)?.length!!
val action = selectedItem.redeemInGroup(listener, message, afterStatus) val action = selectedItem.redeemInGroup(listener, message, afterStatus)
val msg = MessageChain.Builder() val msg = MessageChain.Builder()
.addReply(message.messageId)
.addText("兑换成功! 你花费了${selectedItemPrice}cm来兑换奖品, 还剩${afterStatus}cm") .addText("兑换成功! 你花费了${selectedItemPrice}cm来兑换奖品, 还剩${afterStatus}cm")
.addNewLine() .addNewLine()
.addText("-------------------") .addText("-------------------")
@@ -9,7 +9,7 @@ package cn.rtast.fancybot.commands.niuzi
import cn.rtast.fancybot.annotations.CommandDescription import cn.rtast.fancybot.annotations.CommandDescription
import cn.rtast.fancybot.niuziManager import cn.rtast.fancybot.niuziManager
import cn.rtast.fancybot.util.misc.getUserName import cn.rtast.fancybot.util.getUserName
import cn.rtast.rob.entity.GroupMessage import cn.rtast.rob.entity.GroupMessage
import cn.rtast.rob.util.BaseCommand import cn.rtast.rob.util.BaseCommand
import cn.rtast.rob.util.ob.MessageChain import cn.rtast.rob.util.ob.MessageChain
@@ -8,20 +8,14 @@
package cn.rtast.fancybot.commands.parse package cn.rtast.fancybot.commands.parse
import cn.rtast.fancybot.annotations.CommandDescription import cn.rtast.fancybot.annotations.CommandDescription
import cn.rtast.fancybot.commands.misc.ShortLinkCommand.Companion.makeShortLink import cn.rtast.fancybot.util.makeGif
import cn.rtast.fancybot.util.Logger
import cn.rtast.fancybot.util.file.getFileType
import cn.rtast.fancybot.util.misc.ImageBed
import cn.rtast.fancybot.util.misc.makeGif
import cn.rtast.fancybot.util.misc.toByteArray
import cn.rtast.fancybot.util.str.encodeToBase64 import cn.rtast.fancybot.util.str.encodeToBase64
import cn.rtast.rob.entity.GetMessage import cn.rtast.fancybot.util.toByteArray
import cn.rtast.rob.entity.GroupMessage import cn.rtast.rob.entity.GroupMessage
import cn.rtast.rob.enums.ArrayMessageType import cn.rtast.rob.enums.ArrayMessageType
import cn.rtast.rob.util.BaseCommand import cn.rtast.rob.util.BaseCommand
import cn.rtast.rob.util.ob.MessageChain import cn.rtast.rob.util.ob.MessageChain
import cn.rtast.rob.util.ob.OneBotListener import cn.rtast.rob.util.ob.OneBotListener
import cn.rtast.rob.util.ob.asMessageChain
import com.madgag.gif.fmsware.GifDecoder import com.madgag.gif.fmsware.GifDecoder
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@@ -39,7 +33,6 @@ class AsciiArtCommand : BaseCommand() {
companion object { companion object {
private const val FONT_SIZE = 12 private const val FONT_SIZE = 12
private val font = Font("Monospaced", Font.PLAIN, FONT_SIZE) private val font = Font("Monospaced", Font.PLAIN, FONT_SIZE)
private val logger = Logger.getLogger<AsciiArtCommand>()
val waitingList = mutableListOf<Long>() val waitingList = mutableListOf<Long>()
private fun BufferedImage.convertToAscii(): String { private fun BufferedImage.convertToAscii(): String {
@@ -93,94 +86,48 @@ class AsciiArtCommand : BaseCommand() {
return img return img
} }
suspend fun generateAsciiArt(url: String): MessageChain { 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 gifStream = withContext(Dispatchers.IO) { URI(url).toURL().openStream() }
val decoder = GifDecoder() val decoder = GifDecoder()
try { try {
decoder.read(gifStream) decoder.read(gifStream)
if (decoder.frameCount == 0) { if (decoder.frameCount == 0) {
val bufferedImage = withContext(Dispatchers.IO) { ImageIO.read(URI(url).toURL()) } val bufferedImage = withContext(Dispatchers.IO) { ImageIO.read(URI(url).toURL()) }
val imageBytes = bufferedImage.convertToAscii() val imageBase64 = bufferedImage.convertToAscii()
.saveAsciiArtToImage(bufferedImage.width, bufferedImage.height) .saveAsciiArtToImage(bufferedImage.width, bufferedImage.height)
.toByteArray() .toByteArray().encodeToBase64()
val imageBedUrl = ImageBed.upload(imageBytes, imageBytes.getFileType()).makeShortLink() val msg = MessageChain.Builder().addImage(imageBase64, true).build()
val msg = MessageChain.Builder() message.reply(msg)
.addImage(imageBytes.encodeToBase64(), true)
.addText(imageBedUrl)
.build()
return msg
} else { } else {
logger.info("制作GIF Ascii art, 总帧数: ${decoder.frameCount}") println("Making gif ascii art, frames: ${decoder.frameCount}")
val frames = (0 until decoder.frameCount).map { decoder.getFrame(it) } val frames = (0 until decoder.frameCount).map { decoder.getFrame(it) }
val asciiFrames = mutableListOf<BufferedImage>() val asciiFrames = mutableListOf<BufferedImage>()
val width = frames.first().width val width = frames.first().width
val height = frames.first().height val height = frames.first().height
frames.forEachIndexed { index, item -> frames.forEach { asciiFrames.add(it.convertToAscii().saveAsciiArtToImage(width, height)) }
asciiFrames.add(item.convertToAscii().saveAsciiArtToImage(width, height))
logger.info("帧:${index}处理完成")
}
logger.info("合成GIF中...")
val gifBytes = decoder.makeGif(asciiFrames) val gifBytes = decoder.makeGif(asciiFrames)
val imageBedUrl = ImageBed.upload(gifBytes, gifBytes.getFileType()).makeShortLink()
logger.info("合并完成")
val gifBase64 = gifBytes.encodeToBase64() val gifBase64 = gifBytes.encodeToBase64()
logger.info("处理后的图片大小: ${(gifBytes.size / 1024 / 1024).toInt()}MB") message.reply(MessageChain.Builder().addImage(gifBase64, true).build())
val msg = MessageChain.Builder()
.addImage(gifBase64, true)
.addText(imageBedUrl)
.build()
return msg
} }
} catch (_: OutOfMemoryError) { } catch (_: Exception) {
return listOf("GIF处理失败: 内存溢出").asMessageChain() message.reply("处理GIF失败")
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 = this.getImageUrl(message)
val msg = this.generateAsciiArt(url)
message.reply(msg)
} else { } else {
message.reply("回复错误已取消本次操作") 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>) { override suspend fun executeGroup(listener: OneBotListener, message: GroupMessage, args: List<String>) {
if (message.sender.userId !in waitingList) { if (message.sender.userId !in waitingList) {
message.reply("请继续发送一张图片, 如果输入错误则取消本次操作, 如果GIF帧数过多可能会需要很长时间(也可能处理失败)") message.reply("请继续发送一张图片如果输入错误则取消本次操作")
waitingList.add(message.sender.userId) waitingList.add(message.sender.userId)
return return
} else { } else {
@@ -0,0 +1,201 @@
/*
* 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)
}
}
@@ -1,291 +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.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,6 +18,7 @@ object CalculateCommand {
return try { return try {
engine.eval(input) engine.eval(input)
} catch (_: ScriptException) { } catch (_: ScriptException) {
"Invalid expression"
return null return null
}.toString() }.toString()
} }
@@ -1,107 +0,0 @@
/*
* 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,15 +10,16 @@ package cn.rtast.fancybot.commands.parse
import cn.rtast.fancybot.configManager import cn.rtast.fancybot.configManager
import cn.rtast.fancybot.entity.github.RepoInfo import cn.rtast.fancybot.entity.github.RepoInfo
import cn.rtast.fancybot.util.Http import cn.rtast.fancybot.util.Http
import cn.rtast.fancybot.util.misc.Resources import cn.rtast.fancybot.util.Resources
import cn.rtast.fancybot.util.misc.drawCustomImage import cn.rtast.fancybot.util.drawCustomImage
import cn.rtast.fancybot.util.misc.drawString import cn.rtast.fancybot.util.drawString
import cn.rtast.fancybot.util.misc.toByteArray
import cn.rtast.fancybot.util.str.encodeToBase64 import cn.rtast.fancybot.util.str.encodeToBase64
import cn.rtast.fancybot.util.str.formatNumberEnglish import cn.rtast.fancybot.util.str.formatNumberEnglish
import cn.rtast.fancybot.util.str.setTruncate import cn.rtast.fancybot.util.str.setTruncate
import cn.rtast.fancybot.util.toByteArray
import cn.rtast.rob.entity.GroupMessage import cn.rtast.rob.entity.GroupMessage
import cn.rtast.rob.util.ob.MessageChain import cn.rtast.rob.util.ob.MessageChain
import cn.rtast.rob.util.ob.OneBotListener
import java.awt.Color import java.awt.Color
import java.awt.Font import java.awt.Font
import java.awt.RenderingHints import java.awt.RenderingHints
@@ -41,7 +42,6 @@ object GitHubParseCommand {
private val descriptionCustomFont = Font("Serif", Font.PLAIN, 40).deriveFont(Font.BOLD or Font.ITALIC) private val descriptionCustomFont = Font("Serif", Font.PLAIN, 40).deriveFont(Font.BOLD or Font.ITALIC)
private val backgroundColor = Color.WHITE private val backgroundColor = Color.WHITE
private val textColor = Color(60, 60, 60) private val textColor = Color(60, 60, 60)
private val githubRegex = Regex("""github\.com/([^/]+)/([^/]+)""")
private val languageColors = mapOf( private val languageColors = mapOf(
"Kotlin" to Color(169, 123, 255), "Kotlin" to Color(169, 123, 255),
@@ -52,7 +52,7 @@ object GitHubParseCommand {
"Go" to Color(0, 173, 216), "Go" to Color(0, 173, 216),
"Ruby" to Color(112, 21, 22), "Ruby" to Color(112, 21, 22),
"PHP" to Color(79, 93, 149), "PHP" to Color(79, 93, 149),
"TypeScript" to Color(0x3178c6), "TypeScript" to Color(43, 116, 137),
"Swift" to Color(240, 81, 56), "Swift" to Color(240, 81, 56),
"SCSS" to Color(198, 83, 140), "SCSS" to Color(198, 83, 140),
"Visual Basic 6.0" to Color(44, 99, 83), "Visual Basic 6.0" to Color(44, 99, 83),
@@ -63,72 +63,17 @@ object GitHubParseCommand {
"Shell" to Color(137, 224, 81), "Shell" to Color(137, 224, 81),
"CMake" to Color(218, 52, 52), "CMake" to Color(218, 52, 52),
"HTML" to Color(227, 76, 38), "HTML" to Color(227, 76, 38),
"C#" to Color(23, 134, 0),
"F#" to Color(184, 69, 252),
"Scala" to Color(194, 45, 64),
"CSS" to Color(86, 61, 124),
"XML" to Color(0, 96, 172),
"Dart" to Color(0, 180, 171),
"Objective-C" to Color(67, 142, 255),
"OCaml" to Color(239, 122, 8),
"Jupyter Notebook" to Color(218, 91, 11),
"Haskell" to Color(239, 122, 8),
"GDScript" to Color(0x355570),
"Elm" to Color(0x60b5cc),
"Dockerfile" to Color(0x384d54),
"C" to Color(0x555555),
"Batchfile" to Color(0xC1, 0xF7, 0x65),
"AngelScript" to Color(0xC7d7dc),
"Vue" to Color(0x41b883),
"SQF" to Color(0x3f3f3f),
"R" to Color(0x198ce7),
"Assembly" to Color(0x6e4c13),
"M" to Color(0x9a6700),
"HCL" to Color(0x844fba),
"Scilab" to Color(0xca0f21),
"TeX" to Color(0x3d6117),
"Makefile" to Color(0x427819),
"Svelte" to Color(0xff3e00),
"Stylus" to Color(0xff6347),
"EJS" to Color(0xa91e50),
"PLpgSQL" to Color(0x336790),
"Markdown" to Color(0x083fa1),
"Lua" to Color(0x000080),
"JSON" to Color(0x292929),
"YAML" to Color(0xcb171e),
"HLSL" to Color(0xaace60),
"GLSL" to Color(0x5686a5),
"Jinja" to Color(0xa52a22),
"Zig" to Color(0xec915c),
"Perl" to Color(0x0298c3),
"Vim Script" to Color(0x199f4b),
"Tcl" to Color(0xe4cc98),
"Roff" to Color(0xecdebe),
"CoffeeScript" to Color(0x244776),
"Matlab" to Color(0xe16737),
"Pascal" to Color(0xE3F171),
"Fortran" to Color(0x4d41b1),
"Prolog" to Color(0x74283c),
"Erlang" to Color(0xB83998),
"D" to Color(0xba595e),
"ActionScript" to Color(0x882B0F),
"Mathematica" to Color(0xdd1100),
"Nim" to Color(0xffc200),
"COBOL" to Color(0x9a6700),
"Cuda" to Color(0x3a4e3a),
"SAS" to Color(0xb34936),
"Visual Basic .NET" to Color(0x945db7),
"Less" to Color(0x1d365d),
"Unknown" to Color(211, 211, 211) "Unknown" to Color(211, 211, 211)
) )
private fun getLanguageColor(language: String?): Color { private fun getLanguageColor(language: String): Color {
return languageColors[language] ?: languageColors["Unknown"]!! return languageColors[language] ?: languageColors["Unknown"]!!
} }
private fun getRepoStat(username: String, repo: String): RepoInfo { private fun getRepoStat(username: String, repo: String): RepoInfo {
return Http.get<RepoInfo>( return Http.get<RepoInfo>(
"$REPO_INFO_API/$username/$repo", headers = mapOf("Authorization" to "Bearer ${configManager.githubKey}") "$REPO_INFO_API/$username/$repo",
headers = mapOf("Authorization" to "Bearer ${configManager.githubKey}")
) )
} }
@@ -165,26 +110,24 @@ object GitHubParseCommand {
g2d.drawString(repoStat.description ?: "暂无描述~", 80, 400, 800) g2d.drawString(repoStat.description ?: "暂无描述~", 80, 400, 800)
val avatarImage = ImageIO.read(URI(repoStat.owner.avatarUrl).toURL()) val avatarImage = ImageIO.read(URI(repoStat.owner.avatarUrl).toURL())
g2d.drawCustomImage(avatarImage, 1200, 160, 300.0, 300.0, true) g2d.drawCustomImage(avatarImage, 1200, 160, 300.0, 300.0, true)
g2d.color = this.getLanguageColor(repoStat.language)
g2d.drawString(repoStat.language ?: "[无语言]", 30, CANVAS_HEIGHT - 50)
g2d.dispose() g2d.dispose()
return canvas.toByteArray().encodeToBase64() return canvas.toByteArray().encodeToBase64()
} }
suspend fun parse(message: GroupMessage) { suspend fun parse(listener: OneBotListener, message: GroupMessage) {
val matchedResult = githubRegex.find(message.rawMessage) val path = message.rawMessage
if (message.rawMessage.contains("github.com") && matchedResult != null) { .replace(".git", "")
val (user, repo) = matchedResult.destructured .replace("https://github.com/", "")
.replace("git@github.com:", "")
.split("/")
val user = path.first()
val repo = path.last()
val repoStat = this.getRepoStat(user, repo) val repoStat = this.getRepoStat(user, repo)
val image = this.createImage(repoStat) val image = this.createImage(repoStat)
val msg = MessageChain.Builder().addImage(image, true).build() val msg = MessageChain.Builder()
message.reply(msg) .addReply(message.messageId)
} .addImage(image, true)
} .build()
listener.sendGroupMessage(message.groupId, msg)
fun creatRepoImage(user: String, repo: String): String {
val repoStat = this.getRepoStat(user, repo)
val image = this.createImage(repoStat)
return image
} }
} }
@@ -7,52 +7,24 @@
package cn.rtast.fancybot.commands.parse package cn.rtast.fancybot.commands.parse
import cn.rtast.fancybot.commands.misc.ShortLinkCommand.Companion.makeShortLink
import cn.rtast.fancybot.configManager
import cn.rtast.rob.entity.GetMessage import cn.rtast.rob.entity.GetMessage
import cn.rtast.rob.entity.GroupMessage import cn.rtast.rob.entity.GroupMessage
import cn.rtast.rob.enums.ArrayMessageType import cn.rtast.rob.enums.ArrayMessageType
import cn.rtast.rob.util.ob.MessageChain import cn.rtast.rob.util.ob.MessageChain
import cn.rtast.rob.util.ob.asNode
object ImageURLCommand { object ImageURLCommand {
fun getImageUrl(message: GetMessage.Data): List<String> {
try {
val images = mutableListOf<String>()
message.message.forEach {
if (it.type == ArrayMessageType.image) images.add(it.data.file!!)
if (it.type == ArrayMessageType.mface) images.add(it.data.url!!)
}
return images
} catch (_: NullPointerException) {
return emptyList()
}
}
suspend fun callback(groupMessage: GroupMessage, message: GetMessage.Data) { 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() val msg = MessageChain.Builder()
.addText(images.first().makeShortLink()) .addReply(message.messageId)
.addNewLine(2) .addText("哈哈哈哈图片来咯~")
.addText(images.first()) .addNewLine()
.build() val image = message.message.find { it.type == ArrayMessageType.image }
groupMessage.reply(msg) if (image == null) {
val gif = message.message.find { it.type == ArrayMessageType.mface }!!
msg.addText(gif.data.url!!)
} else { } else {
val messages = mutableListOf<MessageChain>() msg.addText(image.data.file!!)
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,9 +8,8 @@
package cn.rtast.fancybot.commands.parse package cn.rtast.fancybot.commands.parse
import cn.rtast.fancybot.annotations.CommandDescription import cn.rtast.fancybot.annotations.CommandDescription
import cn.rtast.fancybot.util.misc.makeGif import cn.rtast.fancybot.util.makeGif
import cn.rtast.fancybot.util.str.encodeToBase64 import cn.rtast.fancybot.util.str.encodeToBase64
import cn.rtast.rob.entity.GetMessage
import cn.rtast.rob.entity.GroupMessage import cn.rtast.rob.entity.GroupMessage
import cn.rtast.rob.enums.ArrayMessageType import cn.rtast.rob.enums.ArrayMessageType
import cn.rtast.rob.util.BaseCommand import cn.rtast.rob.util.BaseCommand
@@ -35,20 +34,6 @@ class ReverseGIFCommand : BaseCommand() {
return gifBytes.encodeToBase64() 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) { suspend fun callback(message: GroupMessage) {
if (message.sender.userId !in waitingList) return if (message.sender.userId !in waitingList) return
waitingList.removeIf { it == message.sender.userId } waitingList.removeIf { it == message.sender.userId }
@@ -1,85 +0,0 @@
/*
* Copyright © 2024 RTAkland
* Author: RTAkland
* Date: 2024/10/15
*/
package cn.rtast.fancybot.commands.parse
import cn.rtast.fancybot.configManager
import cn.rtast.fancybot.entity.yt.YoutubeVideoResponse
import cn.rtast.fancybot.util.Http
import cn.rtast.fancybot.util.misc.toBufferedImage
import cn.rtast.fancybot.util.misc.toByteArray
import cn.rtast.fancybot.util.misc.toURL
import cn.rtast.fancybot.util.str.encodeToBase64
import cn.rtast.fancybot.util.str.formatNumber
import cn.rtast.fancybot.util.str.proxy
import cn.rtast.rob.entity.GroupMessage
import cn.rtast.rob.util.ob.MessageChain
import java.awt.Color
import java.awt.Font
import java.awt.RenderingHints
import java.awt.image.BufferedImage
object YoutubeVideoParseCommand {
private const val YOUTUBE_API_URL = "https://proxy.rtast.cn/https/www.googleapis.com/youtube/v3/videos"
private const val IMAGE_WIDTH = 800
private const val IMAGE_HEIGHT = 450
private val dataApiKey = configManager.youtubeDataApiKey
private val youtubeVideoIdRegex =
Regex("(?:https?://)?(?:www\\.)?(?:youtube\\.com/(?:watch\\?v=|embed/|v/|.+\\?v=)|youtu\\.be/)([\\w\\-]{11})")
private fun createVideoCard(videoInfo: YoutubeVideoResponse): String {
val image = BufferedImage(IMAGE_WIDTH, IMAGE_HEIGHT, BufferedImage.TYPE_INT_ARGB)
val g2d = image.createGraphics()
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON)
g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON)
g2d.color = Color(48, 48, 48)
g2d.fillRect(0, 0, IMAGE_WIDTH, IMAGE_HEIGHT)
g2d.color = Color(255, 255, 255)
g2d.font = Font("Serif", Font.BOLD, 24)
val title = videoInfo.items.first().snippet.title
g2d.drawString(title, 20, 40)
g2d.font = Font("Serif", Font.PLAIN, 18)
val channelTitle = videoInfo.items.first().snippet.channelTitle
g2d.drawString(channelTitle, 20, 70)
val thumbnailImage = videoInfo.items.first().snippet.thumbnails.standard.url
.proxy.toURL().readBytes().toBufferedImage()
val scaledWidth = 300
val scaledHeight = (thumbnailImage.height * (scaledWidth / thumbnailImage.width.toDouble())).toInt()
g2d.drawImage(thumbnailImage, 20, 100, scaledWidth, scaledHeight, null)
g2d.color = Color(200, 200, 200)
g2d.font = Font("Serif", Font.PLAIN, 16)
g2d.drawString("观看次数: ${videoInfo.items.first().statistics.viewCount.toInt().formatNumber()}", 340, 120)
g2d.drawString("点赞次数: ${videoInfo.items.first().statistics.likeCount.toInt().formatNumber()}", 340, 150)
g2d.drawString("收藏次数: ${videoInfo.items.first().statistics.favoriteCount.toInt().formatNumber()}", 340, 180)
g2d.drawString("评论数: ${videoInfo.items.first().statistics.commentCount.toInt().formatNumber()}", 340, 210)
g2d.dispose()
return image.toByteArray().encodeToBase64()
}
private fun getVideoInfo(videoId: String): YoutubeVideoResponse {
val response = Http.get<YoutubeVideoResponse>(
YOUTUBE_API_URL,
mapOf(
"id" to videoId,
"key" to dataApiKey,
"part" to "snippet,statistics"
)
)
return response
}
suspend fun parse(message: GroupMessage) {
val matchResult = youtubeVideoIdRegex.find(message.rawMessage)
if (matchResult != null) {
val videoId = matchResult.groupValues[1]
val videoInfo = this.getVideoInfo(videoId)
val image = createVideoCard(videoInfo)
val msg = MessageChain.Builder().addImage(image, true).build()
message.reply(msg)
}
}
}
@@ -1,62 +0,0 @@
/*
* Copyright © 2024 RTAkland
* Author: RTAkland
* Date: 2024/10/13
*/
package cn.rtast.fancybot.commands.reply
import cn.rtast.fancybot.commands.misc.ShortLinkCommand.Companion.makeShortLink
import cn.rtast.fancybot.commands.parse.ImageURLCommand
import cn.rtast.fancybot.configManager
import cn.rtast.fancybot.util.file.getFileType
import cn.rtast.fancybot.util.misc.ImageBed
import cn.rtast.fancybot.util.misc.toURL
import cn.rtast.rob.entity.GetMessage
import cn.rtast.rob.entity.GroupMessage
import cn.rtast.rob.util.ob.MessageChain
import cn.rtast.rob.util.ob.asNode
import okio.IOException
object ImageBedCommand {
suspend fun execute(getMsg: GetMessage.Data, message: GroupMessage) {
val imagesUrl = ImageURLCommand.getImageUrl(getMsg)
if (imagesUrl.isEmpty()) {
message.reply("这个消息里没有图片呢!")
} else {
try {
if (imagesUrl.size == 1) {
val imageByteArray = imagesUrl.first().toURL().readBytes()
val imageFileType = imageByteArray.getFileType()
val imageBedUrl = ImageBed.upload(imageByteArray, imageFileType)
val msg = MessageChain.Builder()
.addText(imageBedUrl.makeShortLink())
.addNewLine(2)
.addText(imageBedUrl)
.build()
message.reply(msg)
} else {
val messages = mutableListOf<MessageChain>()
imagesUrl.forEach {
val imageByteArray = it.toURL().readBytes()
val imageFileType = imageByteArray.getFileType()
val imageBedUrl = ImageBed.upload(imageByteArray, imageFileType)
val msg = MessageChain.Builder()
.addText(imageBedUrl.makeShortLink())
.addNewLine(2)
.addText(imageBedUrl)
.build()
messages.add(msg)
}
message.reply(messages.asNode(configManager.selfId))
}
} catch (_: IOException) {
message.reply("上传失败: 图片已过期")
} catch (e: Exception) {
e.printStackTrace()
message.reply("上传失败: ${e.message}")
}
}
}
}
@@ -1,19 +0,0 @@
/*
* 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)
}
@@ -1,24 +0,0 @@
/*
* 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,63 +0,0 @@
/*
* 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,29 +0,0 @@
/*
* 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,13 +7,10 @@
package cn.rtast.fancybot.entity package cn.rtast.fancybot.entity
import cn.rtast.fancybot.enums.DatabaseType import cn.rtast.fancybot.entity.enums.ImageType
import cn.rtast.fancybot.enums.ImageBedType import cn.rtast.fancybot.entity.enums.WSType
import cn.rtast.fancybot.enums.ImageType
import cn.rtast.fancybot.enums.WSType
data class Config( data class Config(
val database: Database,
val ncmAPI: String, val ncmAPI: String,
val wsType: WSType, val wsType: WSType,
val wsAddress: String, val wsAddress: String,
@@ -36,28 +33,6 @@ data class Config(
val llamaUrl: String, val llamaUrl: String,
val llamaModel: String, val llamaModel: String,
val qqMusicApiUrl: String, val qqMusicApiUrl: String,
val noticeUser: Long, val startUpNoticeUser: Long,
val selfId: Long, val selfId: Long
val tianXingApiKey: String, )
val githubUser: String,
val githubImageRepo: String,
val imageBedType: ImageBedType,
val cloudflareAccountId: String,
val cloudflareR2AccessKeyId: String,
val cloudflareR2SecretKey: String,
val cloudflareR2BucketName: String,
val cloudflareR2PublicUrl: String,
val apiSpaceKey: String,
val apiRtastKey: String,
val azureAppClientId: String,
val youtubeDataApiKey: String,
) {
data class Database(
val type: DatabaseType,
val host: String,
val port: Int,
val user: String,
val password: String,
val database: String,
)
}
@@ -1,15 +0,0 @@
/*
* 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,
)
@@ -0,0 +1,12 @@
/*
* Copyright © 2024 RTAkland
* Author: RTAkland
* Date: 2024/9/22
*/
package cn.rtast.fancybot.entity
data class ShortLink(
val id: String
)
@@ -1,19 +0,0 @@
/*
* 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,
)
}
@@ -1,16 +0,0 @@
/*
* 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,
)
@@ -1,32 +0,0 @@
/*
* 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,
)
}
@@ -5,7 +5,7 @@
*/ */
package cn.rtast.fancybot.db package cn.rtast.fancybot.entity.db
import org.jetbrains.exposed.sql.Table import org.jetbrains.exposed.sql.Table
@@ -5,7 +5,7 @@
*/ */
package cn.rtast.fancybot.db package cn.rtast.fancybot.entity.db
import org.jetbrains.exposed.sql.Table import org.jetbrains.exposed.sql.Table
@@ -5,7 +5,7 @@
*/ */
package cn.rtast.fancybot.db package cn.rtast.fancybot.entity.db
import org.jetbrains.exposed.sql.Table import org.jetbrains.exposed.sql.Table
@@ -1,84 +0,0 @@
/*
* 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
)
}
@@ -5,7 +5,7 @@
*/ */
package cn.rtast.fancybot.enums package cn.rtast.fancybot.entity.enums
enum class CountryList(val chineseName: String) { enum class CountryList(val chineseName: String) {
China("中国"), UnitedStates("美国"), Russia("俄罗斯"), China("中国"), UnitedStates("美国"), Russia("俄罗斯"),
@@ -4,9 +4,8 @@
* Date: 2024/9/15 * Date: 2024/9/15
*/ */
@file:Suppress("unused", "EnumEntryName")
package cn.rtast.fancybot.enums package cn.rtast.fancybot.entity.enums
enum class GPTUserRole { enum class GPTUserRole {
user, system, assistant user, system, assistant
@@ -4,9 +4,8 @@
* Date: 2024/9/14 * Date: 2024/9/14
*/ */
@file:Suppress("unused", "EnumEntryName")
package cn.rtast.fancybot.enums package cn.rtast.fancybot.entity.enums
enum class ImageType(val typeName: String) { enum class ImageType(val typeName: String) {
PNG("png"), JPG("jpg") PNG("png"), JPG("jpg")
@@ -4,9 +4,8 @@
* Date: 2024/8/30 * Date: 2024/8/30
*/ */
@file:Suppress("unused", "EnumEntryName")
package cn.rtast.fancybot.enums package cn.rtast.fancybot.entity.enums
enum class WSType { enum class WSType {
Client, Server Client, Server
@@ -1,23 +0,0 @@
/*
* 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?, val description: String?,
@SerializedName("forks_count") @SerializedName("forks_count")
val forksCount: Int, val forksCount: Int,
val language: String?, val language: String,
@SerializedName("stargazers_count") @SerializedName("stargazers_count")
val starsCount: Int, val starsCount: Int,
@SerializedName("open_issues_count") @SerializedName("open_issues_count")
@@ -1,13 +0,0 @@
/*
* Copyright © 2024 RTAkland
* Author: RTAkland
* Date: 2024/10/8
*/
package cn.rtast.fancybot.entity.github
data class UploadContentPayload(
val message: String,
val content: String,
)
@@ -1,16 +0,0 @@
/*
* 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 package cn.rtast.fancybot.entity.gpt
import cn.rtast.fancybot.enums.GPTUserRole import cn.rtast.fancybot.entity.enums.GPTUserRole
data class ChatCompletionsPayload( data class ChatCompletionsPayload(
val model: String, val model: String,
@@ -7,7 +7,7 @@
package cn.rtast.fancybot.entity.gpt package cn.rtast.fancybot.entity.gpt
import cn.rtast.fancybot.enums.GPTUserRole import cn.rtast.fancybot.entity.enums.GPTUserRole
data class ChatCompletionsResponse( data class ChatCompletionsResponse(
val choices: List<Choice> val choices: List<Choice>
@@ -1,26 +0,0 @@
/*
* 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
)
}
@@ -1,31 +0,0 @@
/*
* 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,
)
}
@@ -1,22 +0,0 @@
/*
* 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
)
}
@@ -1,31 +0,0 @@
/*
* 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,
)
}
@@ -1,10 +0,0 @@
/*
* Copyright © 2024 RTAkland
* Author: RTAkland
* Date: 2024/10/2
*/
package cn.rtast.fancybot.entity.mc
data class Username(val id: String)
@@ -1,19 +0,0 @@
/*
* 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
)
@@ -1,28 +0,0 @@
/*
* 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",
)
}
@@ -1,25 +0,0 @@
/*
* 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
)
}
@@ -1,26 +0,0 @@
/*
* 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",
)
}
@@ -1,18 +0,0 @@
/*
* 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,
)
@@ -1,15 +0,0 @@
/*
* 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,
)
@@ -1,20 +0,0 @@
/*
* 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
)
}
@@ -1,12 +0,0 @@
/*
* 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)
@@ -1,17 +1,22 @@
/* /*
* Copyright © 2024 RTAkland * Copyright © 2024 RTAkland
* Author: RTAkland * Author: RTAkland
* Date: 2024/10/6 * Date: 2024/9/29
*/ */
package cn.rtast.fancybot.entity.setu package cn.rtast.fancybot.entity.setu
data class SetuV3( data class SetuV2(
val pid: Long, val pid: Long,
val uid: Long, val uid: Long,
val title: String, val title: String,
val author: String, val author: String,
val url: String, val urls: URLS,
val r18: Boolean, val r18: Boolean,
) val tags: List<String>
) {
data class URLS(
val regular: String,
)
}
Loaded 100 of 263 files, more files were not shown because too many files have changed in this diff. Show more