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